/
config.go
129 lines (121 loc) · 4.82 KB
/
config.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
// Copyright (C) 2022 The Flux authors
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
package azkv
import (
"fmt"
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud"
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
"sigs.k8s.io/yaml"
)
// LoadAADConfigFromBytes attempts to load the given bytes into the given AADConfig.
// By first decoding it if UTF-16, and then unmarshalling it into the given struct.
// It returns an error for any failure.
func LoadAADConfigFromBytes(b []byte, s *AADConfig) error {
b, err := decode(b)
if err != nil {
return fmt.Errorf("failed to decode Azure authentication file bytes: %w", err)
}
if err = yaml.Unmarshal(b, s); err != nil {
err = fmt.Errorf("failed to unmarshal Azure authentication file: %w", err)
}
return err
}
// AADConfig contains the selection of fields from an Azure authentication file
// required for Active Directory authentication.
type AADConfig struct {
AZConfig
TenantID string `json:"tenantId,omitempty"`
ClientID string `json:"clientId,omitempty"`
ClientSecret string `json:"clientSecret,omitempty"`
ClientCertificate string `json:"clientCertificate,omitempty"`
ClientCertificatePassword string `json:"clientCertificatePassword,omitempty"`
ClientCertificateSendChain bool `json:"clientCertificateSendChain,omitempty"`
AuthorityHost string `json:"authorityHost,omitempty"`
}
// AZConfig contains the Service Principal fields as generated by `az`.
// Ref: https://docs.microsoft.com/en-us/azure/aks/kubernetes-service-principal?tabs=azure-cli#manually-create-a-service-principal
type AZConfig struct {
AppID string `json:"appId,omitempty"`
Tenant string `json:"tenant,omitempty"`
Password string `json:"password,omitempty"`
}
// TokenFromAADConfig attempts to construct a Token using the AADConfig values.
// It detects credentials in the following order:
//
// - azidentity.ClientSecretCredential when `tenantId`, `clientId` and
// `clientSecret` fields are found.
// - azidentity.ClientCertificateCredential when `tenantId`,
// `clientCertificate` (and optionally `clientCertificatePassword`) fields
// are found.
// - azidentity.ClientSecretCredential when AZConfig fields are found.
// - azidentity.ManagedIdentityCredential for a User ID, when a `clientId`
// field but no `tenantId` is found.
//
// If no set of credentials is found or the azcore.TokenCredential can not be
// created, an error is returned.
func TokenFromAADConfig(c AADConfig) (_ *Token, err error) {
var token azcore.TokenCredential
if c.TenantID != "" && c.ClientID != "" {
if c.ClientSecret != "" {
if token, err = azidentity.NewClientSecretCredential(c.TenantID, c.ClientID, c.ClientSecret, &azidentity.ClientSecretCredentialOptions{
ClientOptions: azcore.ClientOptions{
Cloud: c.GetCloudConfig(),
},
}); err != nil {
return
}
return NewToken(token), nil
}
if c.ClientCertificate != "" {
certs, pk, err := azidentity.ParseCertificates([]byte(c.ClientCertificate), []byte(c.ClientCertificatePassword))
if err != nil {
return nil, err
}
if token, err = azidentity.NewClientCertificateCredential(c.TenantID, c.ClientID, certs, pk, &azidentity.ClientCertificateCredentialOptions{
SendCertificateChain: c.ClientCertificateSendChain,
ClientOptions: azcore.ClientOptions{
Cloud: c.GetCloudConfig(),
},
}); err != nil {
return nil, err
}
return NewToken(token), nil
}
}
switch {
case c.Tenant != "" && c.AppID != "" && c.Password != "":
if token, err = azidentity.NewClientSecretCredential(c.Tenant, c.AppID, c.Password, &azidentity.ClientSecretCredentialOptions{
ClientOptions: azcore.ClientOptions{
Cloud: c.GetCloudConfig(),
},
}); err != nil {
return
}
return NewToken(token), nil
case c.ClientID != "":
if token, err = azidentity.NewManagedIdentityCredential(&azidentity.ManagedIdentityCredentialOptions{
ID: azidentity.ClientID(c.ClientID),
}); err != nil {
return
}
return NewToken(token), nil
default:
return nil, fmt.Errorf("invalid data: requires a '%s' field, a combination of '%s', '%s' and '%s', or '%s', '%s' and '%s'",
"clientId", "tenantId", "clientId", "clientSecret", "tenantId", "clientId", "clientCertificate")
}
}
// GetCloudConfig returns a cloud.Configuration with the AuthorityHost, or the
// Azure Public Cloud default.
func (s AADConfig) GetCloudConfig() cloud.Configuration {
if s.AuthorityHost != "" {
return cloud.Configuration{
ActiveDirectoryAuthorityHost: s.AuthorityHost,
Services: map[cloud.ServiceName]cloud.ServiceConfiguration{},
}
}
return cloud.AzurePublic
}