@@ -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,63 @@ 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
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
+ }
96
114
}
97
115
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 )
102
123
}
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 )
106
126
}
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 )
112
129
}
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 {})
115
136
return
116
137
}
117
138
118
- // Secret does not contain a valid set of credentials, fallback to simple client.
139
+ // Fallback to simple client.
119
140
c .ServiceClient , err = azblob .NewServiceClientWithNoCredential (obj .Spec .Endpoint , nil )
120
141
return
121
142
}
@@ -138,26 +159,19 @@ func ValidateSecret(secret *corev1.Secret) error {
138
159
}
139
160
}
140
161
}
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
162
if _ , hasClientID := secret .Data [clientIDField ]; hasClientID {
152
163
valid = true
153
164
}
154
165
if _ , hasAccountKey := secret .Data [accountKeyField ]; hasAccountKey {
155
166
valid = true
156
167
}
168
+ if _ , hasAuthorityHost := secret .Data [authorityHostField ]; hasAuthorityHost {
169
+ valid = true
170
+ }
157
171
158
172
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 )
161
175
}
162
176
return nil
163
177
}
@@ -289,33 +303,32 @@ func tokenCredentialFromSecret(secret *corev1.Secret) (azcore.TokenCredential, e
289
303
clientID , hasClientID := secret .Data [clientIDField ]
290
304
if tenantID , hasTenantID := secret .Data [tenantIDField ]; hasTenantID && hasClientID {
291
305
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 )
293
311
}
294
312
if clientCertificate , hasClientCertificate := secret .Data [clientCertificateField ]; hasClientCertificate && len (clientCertificate ) > 0 {
295
313
certs , key , err := azidentity .ParseCertificates (clientCertificate , secret .Data [clientCertificatePasswordField ])
296
314
if err != nil {
297
315
return nil , fmt .Errorf ("failed to parse client certificates: %w" , err )
298
316
}
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"
306
323
}
324
+ return azidentity .NewClientCertificateCredential (string (tenantID ), string (clientID ), certs , key , opts )
307
325
}
308
326
}
309
327
if hasClientID {
310
328
return azidentity .NewManagedIdentityCredential (& azidentity.ManagedIdentityCredentialOptions {
311
329
ID : azidentity .ClientID (clientID ),
312
330
})
313
331
}
314
- if resourceID , hasResourceID := secret .Data [resourceIDField ]; hasResourceID {
315
- return azidentity .NewManagedIdentityCredential (& azidentity.ManagedIdentityCredentialOptions {
316
- ID : azidentity .ResourceID (resourceID ),
317
- })
318
- }
319
332
return nil , nil
320
333
}
321
334
0 commit comments