@@ -44,18 +44,15 @@ var (
4444)
4545
4646const (
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.
8989func 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
0 commit comments