Skip to content

Commit 696dcc7

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 696dcc7

File tree

2 files changed

+82
-77
lines changed

2 files changed

+82
-77
lines changed

pkg/azure/blob.go

Lines changed: 82 additions & 60 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,48 @@ 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
96-
}
97-
98-
// Attempt AAD Token Credential options first.
9992
var token azcore.TokenCredential
100-
if token, err = tokenCredentialFromSecret(secret); err != nil {
101-
return
102-
}
103-
if token != nil {
104-
c.ServiceClient, err = azblob.NewServiceClient(obj.Spec.Endpoint, token, nil)
105-
return
106-
}
10793

108-
// Fallback to Shared Key Credential.
109-
cred, err := sharedCredentialFromSecret(obj.Spec.Endpoint, secret)
110-
if err != nil {
111-
return
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+
}
112114
}
113-
if cred != nil {
114-
c.ServiceClient, err = azblob.NewServiceClientWithSharedKey(obj.Spec.Endpoint, cred, &azblob.ClientOptions{})
115-
return
115+
116+
// Compose token chain based on environment.
117+
// This functions as a replacement for azidentity.NewDefaultAzureCredential
118+
// to not shell out.
119+
if token, err = chainCredentialWithSecret(secret); err != nil {
120+
err = fmt.Errorf("failed to create environment credential chain: %w", err)
121+
return nil, err
116122
}
117123

118-
// Secret does not contain a valid set of credentials, fallback to simple client.
124+
// Fallback to simple client.
119125
c.ServiceClient, err = azblob.NewServiceClientWithNoCredential(obj.Spec.Endpoint, nil)
120126
return
121127
}
@@ -138,26 +144,19 @@ func ValidateSecret(secret *corev1.Secret) error {
138144
}
139145
}
140146
}
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-
}
151147
if _, hasClientID := secret.Data[clientIDField]; hasClientID {
152148
valid = true
153149
}
154150
if _, hasAccountKey := secret.Data[accountKeyField]; hasAccountKey {
155151
valid = true
156152
}
153+
if _, hasAuthorityHost := secret.Data[authorityHostField]; hasAuthorityHost {
154+
valid = true
155+
}
157156

158157
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)
158+
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'",
159+
secret.Name, clientIDField, accountKeyField, tenantIDField, clientIDField, clientSecretField, tenantIDField, clientIDField, clientCertificateField)
161160
}
162161
return nil
163162
}
@@ -289,33 +288,56 @@ func tokenCredentialFromSecret(secret *corev1.Secret) (azcore.TokenCredential, e
289288
clientID, hasClientID := secret.Data[clientIDField]
290289
if tenantID, hasTenantID := secret.Data[tenantIDField]; hasTenantID && hasClientID {
291290
if clientSecret, hasClientSecret := secret.Data[clientSecretField]; hasClientSecret && len(clientSecret) > 0 {
292-
return azidentity.NewClientSecretCredential(string(tenantID), string(clientID), string(clientSecret), nil)
291+
opts := &azidentity.ClientSecretCredentialOptions{}
292+
if authorityHost, hasAuthorityHost := secret.Data[authorityHostField]; hasAuthorityHost {
293+
opts.AuthorityHost = azidentity.AuthorityHost(authorityHost)
294+
}
295+
return azidentity.NewClientSecretCredential(string(tenantID), string(clientID), string(clientSecret), opts)
293296
}
294297
if clientCertificate, hasClientCertificate := secret.Data[clientCertificateField]; hasClientCertificate && len(clientCertificate) > 0 {
295298
certs, key, err := azidentity.ParseCertificates(clientCertificate, secret.Data[clientCertificatePasswordField])
296299
if err != nil {
297300
return nil, fmt.Errorf("failed to parse client certificates: %w", err)
298301
}
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)
302+
opts := &azidentity.ClientCertificateCredentialOptions{}
303+
if authorityHost, hasAuthorityHost := secret.Data[authorityHostField]; hasAuthorityHost {
304+
opts.AuthorityHost = azidentity.AuthorityHost(authorityHost)
305+
}
306+
if v, sendChain := secret.Data[clientCertificateSendChainField]; sendChain {
307+
opts.SendCertificateChain = string(v) == "1" || strings.ToLower(string(v)) == "true"
306308
}
309+
return azidentity.NewClientCertificateCredential(string(tenantID), string(clientID), certs, key, opts)
307310
}
308311
}
309312
if hasClientID {
310313
return azidentity.NewManagedIdentityCredential(&azidentity.ManagedIdentityCredentialOptions{
311314
ID: azidentity.ClientID(clientID),
312315
})
313316
}
314-
if resourceID, hasResourceID := secret.Data[resourceIDField]; hasResourceID {
315-
return azidentity.NewManagedIdentityCredential(&azidentity.ManagedIdentityCredentialOptions{
316-
ID: azidentity.ResourceID(resourceID),
317-
})
317+
return nil, nil
318+
}
319+
320+
func chainCredentialWithSecret(secret *corev1.Secret) (azcore.TokenCredential, error) {
321+
var creds []azcore.TokenCredential
322+
323+
credOpts := &azidentity.EnvironmentCredentialOptions{}
324+
if secret != nil {
325+
if authorityHost, hasAuthorityHost := secret.Data[authorityHostField]; hasAuthorityHost {
326+
credOpts.AuthorityHost = azidentity.AuthorityHost(authorityHost)
327+
}
328+
}
329+
330+
if token, _ := azidentity.NewEnvironmentCredential(credOpts); token != nil {
331+
creds = append(creds, token)
318332
}
333+
if token, _ := azidentity.NewManagedIdentityCredential(nil); token != nil {
334+
creds = append(creds, token)
335+
}
336+
337+
if len(creds) > 0 {
338+
return azidentity.NewChainedTokenCredential(creds, nil)
339+
}
340+
319341
return nil, nil
320342
}
321343

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)