@@ -44,18 +44,15 @@ var (
44
44
)
45
45
46
46
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"
59
56
)
60
57
61
58
// BlobClient is a minimal Azure Blob client for fetching objects.
@@ -83,39 +80,48 @@ type BlobClient struct {
83
80
// - azblob.SharedKeyCredential when an `accountKey` field is found.
84
81
// The account name is extracted from the endpoint specified on the Bucket
85
82
// object.
83
+ // - azidentity.ChainedTokenCredential with azidentity.EnvironmentCredential
84
+ // and azidentity.ManagedIdentityCredential with defaults if no Secret is
85
+ // given.
86
86
//
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.
89
89
func NewClient (obj * sourcev1.Bucket , secret * corev1.Secret ) (c * BlobClient , err error ) {
90
90
c = & BlobClient {}
91
91
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.
99
92
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
- }
107
93
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
+ }
112
114
}
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
116
122
}
117
123
118
- // Secret does not contain a valid set of credentials, fallback to simple client.
124
+ // Fallback to simple client.
119
125
c .ServiceClient , err = azblob .NewServiceClientWithNoCredential (obj .Spec .Endpoint , nil )
120
126
return
121
127
}
@@ -138,26 +144,19 @@ func ValidateSecret(secret *corev1.Secret) error {
138
144
}
139
145
}
140
146
}
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
- }
151
147
if _ , hasClientID := secret .Data [clientIDField ]; hasClientID {
152
148
valid = true
153
149
}
154
150
if _ , hasAccountKey := secret .Data [accountKeyField ]; hasAccountKey {
155
151
valid = true
156
152
}
153
+ if _ , hasAuthorityHost := secret .Data [authorityHostField ]; hasAuthorityHost {
154
+ valid = true
155
+ }
157
156
158
157
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 )
161
160
}
162
161
return nil
163
162
}
@@ -289,33 +288,56 @@ func tokenCredentialFromSecret(secret *corev1.Secret) (azcore.TokenCredential, e
289
288
clientID , hasClientID := secret .Data [clientIDField ]
290
289
if tenantID , hasTenantID := secret .Data [tenantIDField ]; hasTenantID && hasClientID {
291
290
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 )
293
296
}
294
297
if clientCertificate , hasClientCertificate := secret .Data [clientCertificateField ]; hasClientCertificate && len (clientCertificate ) > 0 {
295
298
certs , key , err := azidentity .ParseCertificates (clientCertificate , secret .Data [clientCertificatePasswordField ])
296
299
if err != nil {
297
300
return nil , fmt .Errorf ("failed to parse client certificates: %w" , err )
298
301
}
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"
306
308
}
309
+ return azidentity .NewClientCertificateCredential (string (tenantID ), string (clientID ), certs , key , opts )
307
310
}
308
311
}
309
312
if hasClientID {
310
313
return azidentity .NewManagedIdentityCredential (& azidentity.ManagedIdentityCredentialOptions {
311
314
ID : azidentity .ClientID (clientID ),
312
315
})
313
316
}
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 )
318
332
}
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
+
319
341
return nil , nil
320
342
}
321
343
0 commit comments