-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathauth.go
More file actions
148 lines (126 loc) · 5.5 KB
/
auth.go
File metadata and controls
148 lines (126 loc) · 5.5 KB
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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
package bootstrap
import (
"context"
"encoding/base64"
"fmt"
"strings"
"github.com/Azure/aks-secure-tls-bootstrap/client/internal/cloud"
"github.com/Azure/aks-secure-tls-bootstrap/client/internal/http"
"github.com/Azure/aks-secure-tls-bootstrap/client/internal/log"
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
"go.uber.org/zap"
)
const (
// service principal secrets containing this prefix are PFX certificates which need to be decoded,
// rather than raw password / secret strings
certificateSecretPrefix = "certificate:"
)
const (
// this will be the exact value of the "userAssignedIdentityID" field of the cloud provider config
// when the node is using a (user-assigned) managed identity, rather than a service principal
clientIDForMSI = "msi"
)
// getTokenCredentialFunc creates an azcore.TokenCredential based on the provided bootstrap configuration.
type getTokenCredentialFunc func(ctx context.Context, config *Config) (azcore.TokenCredential, error)
func (c *client) getToken(ctx context.Context, config *Config) (string, error) {
credential, err := c.getTokenCredentialFunc(ctx, config)
if err != nil {
return "", err
}
token, err := credential.GetToken(ctx, policy.TokenRequestOptions{
Scopes: []string{aadResourceToScope(config.AADResource)},
})
if err != nil {
return "", err
}
return token.Token, nil
}
// getTokenCredential builds an azcore.TokenCredential from the provided bootstrap configuration,
// selecting between managed identity and service principal credential types as appropriate.
func getTokenCredential(ctx context.Context, config *Config) (azcore.TokenCredential, error) {
logger := log.MustGetLogger(ctx)
userAssignedID := config.CloudProviderConfig.UserAssignedIdentityID
if config.UserAssignedIdentityID != "" {
userAssignedID = config.UserAssignedIdentityID
}
if userAssignedID != "" {
logger.Info("generating MSI access token", zap.String("clientId", userAssignedID))
cred, err := azidentity.NewManagedIdentityCredential(&azidentity.ManagedIdentityCredentialOptions{
ID: azidentity.ClientID(userAssignedID),
ClientOptions: http.GetManagedIdentityClientOpts(),
})
if err != nil {
return nil, fmt.Errorf("generating MSI access token: %w", err)
}
return cred, nil
}
if config.CloudProviderConfig.ClientID == clientIDForMSI {
return nil, fmt.Errorf("client ID within cloud provider config indicates usage of a managed identity, though no user-assigned identity ID was provided")
}
return getServicePrincipalCredential(ctx, config.CloudProviderConfig)
}
func getServicePrincipalCredential(ctx context.Context, cloudProviderConfig *cloud.ProviderConfig) (azcore.TokenCredential, error) {
logger := log.MustGetLogger(ctx)
secret := maybeB64Decode(cloudProviderConfig.ClientSecret)
cloudConfig, err := cloud.GetCloudConfig(cloudProviderConfig.CloudName)
if err != nil {
return nil, fmt.Errorf("getting azure environment config for cloud %q: %w", cloudProviderConfig.CloudName, err)
}
if !strings.HasPrefix(secret, certificateSecretPrefix) {
logger.Info("generating service principal access token with client secret", zap.String("clientId", cloudProviderConfig.ClientID))
if secret == "" {
return nil, fmt.Errorf("generating service principal access token with client secret: client secret is empty")
}
credential, err := azidentity.NewClientSecretCredential(cloudProviderConfig.TenantID, cloudProviderConfig.ClientID, secret,
&azidentity.ClientSecretCredentialOptions{
ClientOptions: http.GetDefaultAzureClientOptsWithCloud(cloudConfig),
// required since we'll be running in stack/custom clouds
DisableInstanceDiscovery: true,
},
)
if err != nil {
return nil, fmt.Errorf("generating service principal access token with client secret: %w", err)
}
return credential, nil
}
logger.Info("client secret contains certificate data, using certificate to generate service principal access token", zap.String("clientId", cloudProviderConfig.ClientID))
certData, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(secret, certificateSecretPrefix))
if err != nil {
return nil, fmt.Errorf("b64-decoding certificate data in client secret: %w", err)
}
certs, key, err := azidentity.ParseCertificates(certData, nil)
if err != nil {
return nil, fmt.Errorf("decoding pfx certificate data in client secret: %w", err)
}
logger.Info("generating service principal access token with certificate", zap.String("clientId", cloudProviderConfig.ClientID))
credential, err := azidentity.NewClientCertificateCredential(cloudProviderConfig.TenantID, cloudProviderConfig.ClientID, certs, key,
&azidentity.ClientCertificateCredentialOptions{
ClientOptions: http.GetDefaultAzureClientOptsWithCloud(cloudConfig),
// required for SNI-based authentication
SendCertificateChain: true,
// required since we'll be running in stack/custom clouds
DisableInstanceDiscovery: true,
},
)
if err != nil {
return nil, fmt.Errorf("generating service principal access token with certificate: %w", err)
}
return credential, nil
}
func aadResourceToScope(resource string) string {
resource = strings.TrimSuffix(resource, "/")
if !strings.HasSuffix(resource, "/.default") {
resource += "/.default"
}
return resource
}
func maybeB64Decode(str string) string {
if decoded, err := base64.StdEncoding.DecodeString(str); err == nil {
return string(decoded)
}
return str
}