Skip to content

Commit 7940044

Browse files
committed
Tweak token credential composition once again
- `authorityHost` and `clientCertificateSendChain` can now be set where applicable. - AZ CLI fields have been removed. - Fallback to `ChainedTokenCredential` with `EnvironmentCredential` and `ManagedIdentityCredential` with defaults if no Secret is given. Tests have not yet been updated. Signed-off-by: Hidde Beydals <[email protected]>
1 parent f5d7a37 commit 7940044

File tree

2 files changed

+71
-75
lines changed

2 files changed

+71
-75
lines changed

pkg/azure/blob.go

Lines changed: 71 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -44,18 +44,15 @@ var (
4444
)
4545

4646
const (
47-
resourceIDField = "resourceId"
48-
clientIDField = "clientId"
49-
tenantIDField = "tenantId"
50-
clientSecretField = "clientSecret"
51-
clientCertificateField = "clientCertificate"
52-
clientCertificatePasswordField = "clientCertificatePassword"
53-
accountKeyField = "accountKey"
54-
55-
// Ref: https://docs.microsoft.com/en-us/azure/aks/kubernetes-service-principal?tabs=azure-cli#manually-create-a-service-principal
56-
tenantField = "tenant"
57-
appIDField = "appId"
58-
passwordField = "password"
47+
resourceIDField = "resourceId"
48+
clientIDField = "clientId"
49+
tenantIDField = "tenantId"
50+
clientSecretField = "clientSecret"
51+
clientCertificateField = "clientCertificate"
52+
clientCertificatePasswordField = "clientCertificatePassword"
53+
clientCertificateSendChainField = "clientCertificateSendChain"
54+
authorityHostField = "authorityHost"
55+
accountKeyField = "accountKey"
5956
)
6057

6158
// BlobClient is a minimal Azure Blob client for fetching objects.
@@ -83,39 +80,63 @@ type BlobClient struct {
8380
// - azblob.SharedKeyCredential when an `accountKey` field is found.
8481
// The account name is extracted from the endpoint specified on the Bucket
8582
// object.
83+
// - azidentity.ChainedTokenCredential with azidentity.EnvironmentCredential
84+
// and azidentity.ManagedIdentityCredential with defaults if no Secret is
85+
// given.
8686
//
87-
// If no credentials are found, a simple client without credentials is
88-
// returned.
87+
// If no credentials are found, and the azidentity.ChainedTokenCredential can
88+
// not be established. A simple client without credentials is returned.
8989
func NewClient(obj *sourcev1.Bucket, secret *corev1.Secret) (c *BlobClient, err error) {
9090
c = &BlobClient{}
9191

92-
// Without a Secret, we can return a simple client.
93-
if secret == nil || len(secret.Data) == 0 {
94-
c.ServiceClient, err = azblob.NewServiceClientWithNoCredential(obj.Spec.Endpoint, nil)
95-
return
92+
var token azcore.TokenCredential
93+
94+
if secret != nil && len(secret.Data) > 0 {
95+
// Attempt AAD Token Credential options first.
96+
if token, err = tokenCredentialFromSecret(secret); err != nil {
97+
err = fmt.Errorf("failed to create token credential from '%s' Secret: %w", secret.Name, err)
98+
return
99+
}
100+
if token != nil {
101+
c.ServiceClient, err = azblob.NewServiceClient(obj.Spec.Endpoint, token, nil)
102+
return
103+
}
104+
105+
// Fallback to Shared Key Credential.
106+
var cred *azblob.SharedKeyCredential
107+
if cred, err = sharedCredentialFromSecret(obj.Spec.Endpoint, secret); err != nil {
108+
return
109+
}
110+
if cred != nil {
111+
c.ServiceClient, err = azblob.NewServiceClientWithSharedKey(obj.Spec.Endpoint, cred, &azblob.ClientOptions{})
112+
return
113+
}
96114
}
97115

98-
// Attempt AAD Token Credential options first.
99-
var token azcore.TokenCredential
100-
if token, err = tokenCredentialFromSecret(secret); err != nil {
101-
return
116+
// Compose token chain based on environment.
117+
// This functions as a replacement for azidentity.NewDefaultAzureCredential
118+
// to not shell out.
119+
var creds []azcore.TokenCredential
120+
credOpts := &azidentity.EnvironmentCredentialOptions{}
121+
if authorityHost, hasAuthorityHost := secret.Data[authorityHostField]; hasAuthorityHost {
122+
credOpts.AuthorityHost = azidentity.AuthorityHost(authorityHost)
102123
}
103-
if token != nil {
104-
c.ServiceClient, err = azblob.NewServiceClient(obj.Spec.Endpoint, token, nil)
105-
return
124+
if token, _ = azidentity.NewEnvironmentCredential(credOpts); token != nil {
125+
creds = append(creds, token)
106126
}
107-
108-
// Fallback to Shared Key Credential.
109-
cred, err := sharedCredentialFromSecret(obj.Spec.Endpoint, secret)
110-
if err != nil {
111-
return
127+
if token, _ = azidentity.NewManagedIdentityCredential(nil); token != nil {
128+
creds = append(creds, token)
112129
}
113-
if cred != nil {
114-
c.ServiceClient, err = azblob.NewServiceClientWithSharedKey(obj.Spec.Endpoint, cred, &azblob.ClientOptions{})
130+
if len(creds) > 0 {
131+
if token, err = azidentity.NewChainedTokenCredential(creds, nil); err != nil {
132+
err = fmt.Errorf("failed to create environment credential chain: %w", err)
133+
return
134+
}
135+
c.ServiceClient, err = azblob.NewServiceClient(obj.Spec.Endpoint, token, &azblob.ClientOptions{})
115136
return
116137
}
117138

118-
// Secret does not contain a valid set of credentials, fallback to simple client.
139+
// Fallback to simple client.
119140
c.ServiceClient, err = azblob.NewServiceClientWithNoCredential(obj.Spec.Endpoint, nil)
120141
return
121142
}
@@ -138,26 +159,19 @@ func ValidateSecret(secret *corev1.Secret) error {
138159
}
139160
}
140161
}
141-
if _, hasTenant := secret.Data[tenantField]; hasTenant {
142-
if _, hasAppID := secret.Data[appIDField]; hasAppID {
143-
if _, hasPassword := secret.Data[passwordField]; hasPassword {
144-
valid = true
145-
}
146-
}
147-
}
148-
if _, hasResourceID := secret.Data[resourceIDField]; hasResourceID {
149-
valid = true
150-
}
151162
if _, hasClientID := secret.Data[clientIDField]; hasClientID {
152163
valid = true
153164
}
154165
if _, hasAccountKey := secret.Data[accountKeyField]; hasAccountKey {
155166
valid = true
156167
}
168+
if _, hasAuthorityHost := secret.Data[authorityHostField]; hasAuthorityHost {
169+
valid = true
170+
}
157171

158172
if !valid {
159-
return fmt.Errorf("invalid '%s' secret data: requires a '%s', '%s', or '%s' field, a combination of '%s', '%s' and '%s', or '%s', '%s' and '%s'",
160-
secret.Name, resourceIDField, clientIDField, accountKeyField, tenantIDField, clientIDField, clientSecretField, tenantIDField, clientIDField, clientCertificateField)
173+
return fmt.Errorf("invalid '%s' secret data: requires a '%s' or '%s' field, a combination of '%s', '%s' and '%s', or '%s', '%s' and '%s'",
174+
secret.Name, clientIDField, accountKeyField, tenantIDField, clientIDField, clientSecretField, tenantIDField, clientIDField, clientCertificateField)
161175
}
162176
return nil
163177
}
@@ -289,33 +303,32 @@ func tokenCredentialFromSecret(secret *corev1.Secret) (azcore.TokenCredential, e
289303
clientID, hasClientID := secret.Data[clientIDField]
290304
if tenantID, hasTenantID := secret.Data[tenantIDField]; hasTenantID && hasClientID {
291305
if clientSecret, hasClientSecret := secret.Data[clientSecretField]; hasClientSecret && len(clientSecret) > 0 {
292-
return azidentity.NewClientSecretCredential(string(tenantID), string(clientID), string(clientSecret), nil)
306+
opts := &azidentity.ClientSecretCredentialOptions{}
307+
if authorityHost, hasAuthorityHost := secret.Data[authorityHostField]; hasAuthorityHost {
308+
opts.AuthorityHost = azidentity.AuthorityHost(authorityHost)
309+
}
310+
return azidentity.NewClientSecretCredential(string(tenantID), string(clientID), string(clientSecret), opts)
293311
}
294312
if clientCertificate, hasClientCertificate := secret.Data[clientCertificateField]; hasClientCertificate && len(clientCertificate) > 0 {
295313
certs, key, err := azidentity.ParseCertificates(clientCertificate, secret.Data[clientCertificatePasswordField])
296314
if err != nil {
297315
return nil, fmt.Errorf("failed to parse client certificates: %w", err)
298316
}
299-
return azidentity.NewClientCertificateCredential(string(tenantID), string(clientID), certs, key, nil)
300-
}
301-
}
302-
if tenant, hasTenant := secret.Data[tenantField]; hasTenant {
303-
if appId, hasAppID := secret.Data[appIDField]; hasAppID {
304-
if password, hasPassword := secret.Data[passwordField]; hasPassword {
305-
return azidentity.NewClientSecretCredential(string(tenant), string(appId), string(password), nil)
317+
opts := &azidentity.ClientCertificateCredentialOptions{}
318+
if authorityHost, hasAuthorityHost := secret.Data[authorityHostField]; hasAuthorityHost {
319+
opts.AuthorityHost = azidentity.AuthorityHost(authorityHost)
320+
}
321+
if v, sendChain := secret.Data[clientCertificateSendChainField]; sendChain {
322+
opts.SendCertificateChain = string(v) == "1" || strings.ToLower(string(v)) == "true"
306323
}
324+
return azidentity.NewClientCertificateCredential(string(tenantID), string(clientID), certs, key, opts)
307325
}
308326
}
309327
if hasClientID {
310328
return azidentity.NewManagedIdentityCredential(&azidentity.ManagedIdentityCredentialOptions{
311329
ID: azidentity.ClientID(clientID),
312330
})
313331
}
314-
if resourceID, hasResourceID := secret.Data[resourceIDField]; hasResourceID {
315-
return azidentity.NewManagedIdentityCredential(&azidentity.ManagedIdentityCredentialOptions{
316-
ID: azidentity.ResourceID(resourceID),
317-
})
318-
}
319332
return nil, nil
320333
}
321334

pkg/azure/blob_test.go

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -76,16 +76,6 @@ func TestValidateSecret(t *testing.T) {
7676
},
7777
},
7878
},
79-
{
80-
name: "valid ServicePrincipal Secret",
81-
secret: &corev1.Secret{
82-
Data: map[string][]byte{
83-
tenantField: []byte("some-tenant-id-"),
84-
appIDField: []byte("some-client-id-"),
85-
passwordField: []byte("some-client-secret-"),
86-
},
87-
},
88-
},
8979
{
9080
name: "valid SharedKey Secret",
9181
secret: &corev1.Secret{
@@ -242,13 +232,6 @@ func Test_tokenCredentialFromSecret(t *testing.T) {
242232
},
243233
{
244234
name: "with Tenant, AppID and Password fields",
245-
secret: &corev1.Secret{
246-
Data: map[string][]byte{
247-
appIDField: []byte("client-id"),
248-
tenantField: []byte("tenant-id"),
249-
passwordField: []byte("client-secret"),
250-
},
251-
},
252235
want: &azidentity.ClientSecretCredential{},
253236
},
254237
{

0 commit comments

Comments
 (0)