@@ -57,6 +57,10 @@ const (
57
57
azureCredentialsKey = "AzureCredentials"
58
58
)
59
59
60
+ // globalAzureCredentials caches User Assigned Managed Identity (UAMI) credentials across driver instances so that
61
+ // reconciles do not recreate the credential repeatedly.
62
+ var globalAzureCredentials sync.Map
63
+
60
64
// storageAccountInvalidCharRe is a regular expression for characters that
61
65
// cannot be used in Azure storage accounts names (i.e. that are not
62
66
// numbers nor lower-case letters) and that are not upper-case letters. If
@@ -318,10 +322,6 @@ type driver struct {
318
322
// policies is for new Azure Client Pipeline execution.
319
323
// Added as a member to the struct to allow injection for testing.
320
324
policies []policy.Policy
321
-
322
- // azureCredentials keeps track if we have already loaded an Azure
323
- // credentials token when using UAMI for managed Azure on HCP.
324
- azureCredentials sync.Map
325
325
}
326
326
327
327
// NewDriver creates a new storage driver for Azure Blob Storage.
@@ -334,7 +334,7 @@ func NewDriver(ctx context.Context, c *imageregistryv1.ImageRegistryConfigStorag
334
334
}
335
335
336
336
func (d * driver ) newAzClient (cfg * Azure , environment autorestazure.Environment , tagset map [string ]* string ) (* azureclient.Client , error ) {
337
- client , err := azureclient . New ( & azureclient.Options {
337
+ clientOptions := & azureclient.Options {
338
338
Environment : environment ,
339
339
TenantID : cfg .TenantID ,
340
340
ClientID : cfg .ClientID ,
@@ -343,10 +343,19 @@ func (d *driver) newAzClient(cfg *Azure, environment autorestazure.Environment,
343
343
SubscriptionID : cfg .SubscriptionID ,
344
344
TagSet : tagset ,
345
345
Policies : d .policies ,
346
- })
346
+ }
347
+
348
+ if cred , ok , err := d .ensureUAMICredentials (d .Context , environment ); err != nil {
349
+ return nil , err
350
+ } else if ok {
351
+ clientOptions .Creds = cred
352
+ }
353
+
354
+ client , err := azureclient .New (clientOptions )
347
355
if err != nil {
348
356
return nil , err
349
357
}
358
+
350
359
return client , nil
351
360
}
352
361
@@ -381,25 +390,10 @@ func (d *driver) storageAccountsClient(cfg *Azure, environment autorestazure.Env
381
390
// UserAssignedIdentityCredentials is specifically for managed Azure HCP
382
391
userAssignedIdentityCredentialsFilePath := os .Getenv ("MANAGED_AZURE_HCP_CREDENTIALS_FILE_PATH" )
383
392
if userAssignedIdentityCredentialsFilePath != "" {
384
- var ok bool
385
-
386
- // We need to only store the Azure credentials once and reuse them after that.
387
- storedCreds , found := d .azureCredentials .Load (azureCredentialsKey )
388
- if ! found {
389
- klog .V (2 ).Info ("Using UserAssignedIdentityCredentials for Azure authentication for managed Azure HCP" )
390
- clientOptions := azcore.ClientOptions {
391
- Cloud : cloudConfig ,
392
- }
393
- cred , err = dataplane .NewUserAssignedIdentityCredential (context .Background (), userAssignedIdentityCredentialsFilePath , dataplane .WithClientOpts (clientOptions ))
394
- if err != nil {
395
- return storage.AccountsClient {}, err
396
- }
397
- d .azureCredentials .Store (azureCredentialsKey , cred )
398
- } else {
399
- cred , ok = storedCreds .(azcore.TokenCredential )
400
- if ! ok {
401
- return storage.AccountsClient {}, fmt .Errorf ("expected %T to be a TokenCredential" , storedCreds )
402
- }
393
+ if c , ok , err := d .ensureUAMICredentials (d .Context , environment ); err != nil {
394
+ return storage.AccountsClient {}, err
395
+ } else if ok {
396
+ cred = c
403
397
}
404
398
} else if strings .TrimSpace (cfg .ClientSecret ) == "" {
405
399
options := azidentity.WorkloadIdentityCredentialOptions {
@@ -1237,14 +1231,30 @@ func (d *driver) RemoveStorage(cr *imageregistryv1.Config) (retry bool, err erro
1237
1231
}
1238
1232
1239
1233
if d .Config .NetworkAccess != nil && d .Config .NetworkAccess .Internal != nil && d .Config .NetworkAccess .Internal .PrivateEndpointName != "" {
1240
- azclient , err := azureclient . New ( & azureclient.Options {
1234
+ clientOptions := & azureclient.Options {
1241
1235
Environment : environment ,
1242
1236
TenantID : cfg .TenantID ,
1243
1237
ClientID : cfg .ClientID ,
1244
1238
ClientSecret : cfg .ClientSecret ,
1245
1239
FederatedTokenFile : cfg .FederatedTokenFile ,
1246
1240
SubscriptionID : cfg .SubscriptionID ,
1247
- })
1241
+ }
1242
+
1243
+ if cred , ok , err := d .ensureUAMICredentials (d .Context , environment ); err != nil {
1244
+ util .UpdateCondition (
1245
+ cr ,
1246
+ defaults .StorageExists ,
1247
+ operatorapiv1 .ConditionUnknown ,
1248
+ storageExistsReasonAzureError ,
1249
+ fmt .Sprintf ("Unable to get azure client: %s" , err ),
1250
+ )
1251
+ return false , err
1252
+ } else if ok {
1253
+ klog .V (2 ).Infof ("Using cached UAMI credential for RemoveStorage client" )
1254
+ clientOptions .Creds = cred
1255
+ }
1256
+
1257
+ azclient , err := azureclient .New (clientOptions )
1248
1258
if err != nil {
1249
1259
util .UpdateCondition (
1250
1260
cr ,
@@ -1320,3 +1330,49 @@ func (d *driver) RemoveStorage(cr *imageregistryv1.Config) (retry bool, err erro
1320
1330
func (d * driver ) ID () string {
1321
1331
return d .Config .Container
1322
1332
}
1333
+
1334
+ // ensureUAMICredentials obtains and caches an Azure TokenCredential using a
1335
+ // User Assigned Managed Identity (UAMI).
1336
+ //
1337
+ // If MANAGED_AZURE_HCP_CREDENTIALS_FILE_PATH is unset, it returns (nil, false, nil).
1338
+ // When set, it loads a credential from a process-wide cache or creates one using the
1339
+ // provided Azure environment endpoints, stores it, and returns it.
1340
+ //
1341
+ // The bool result is true when a UAMI credential is available. An error is returned if
1342
+ // credential creation fails or a cached value has an unexpected type.
1343
+ //
1344
+ // ctx controls cancellation of credential creation. env supplies Azure endpoints.
1345
+ func (d * driver ) ensureUAMICredentials (ctx context.Context , env autorestazure.Environment ) (azcore.TokenCredential , bool , error ) {
1346
+ if stored , ok := globalAzureCredentials .Load (azureCredentialsKey ); ok {
1347
+ if cred , ok := stored .(azcore.TokenCredential ); ok {
1348
+ klog .V (2 ).Infof ("Loaded UAMI credentials from cache" )
1349
+ return cred , true , nil
1350
+ }
1351
+ return nil , false , fmt .Errorf ("expected cached credential to be azcore.TokenCredential" )
1352
+ }
1353
+ if os .Getenv ("MANAGED_AZURE_HCP_CREDENTIALS_FILE_PATH" ) == "" {
1354
+ return nil , false , nil
1355
+ }
1356
+ cloudConfig := cloud.Configuration {
1357
+ ActiveDirectoryAuthorityHost : env .ActiveDirectoryEndpoint ,
1358
+ Services : map [cloud.ServiceName ]cloud.ServiceConfiguration {
1359
+ cloud .ResourceManager : {
1360
+ Audience : env .TokenAudience ,
1361
+ Endpoint : env .ResourceManagerEndpoint ,
1362
+ },
1363
+ },
1364
+ }
1365
+ cred , err := dataplane .NewUserAssignedIdentityCredential (
1366
+ ctx ,
1367
+ os .Getenv ("MANAGED_AZURE_HCP_CREDENTIALS_FILE_PATH" ),
1368
+ dataplane .WithClientOpts (azcore.ClientOptions {Cloud : cloudConfig }),
1369
+ )
1370
+ if err != nil {
1371
+ return nil , false , err
1372
+ }
1373
+ if actual , loaded := globalAzureCredentials .LoadOrStore (azureCredentialsKey , cred ); loaded {
1374
+ return actual .(azcore.TokenCredential ), true , nil
1375
+ }
1376
+ klog .V (2 ).Infof ("Storing UAMI credentials to global cache" )
1377
+ return cred , true , nil
1378
+ }
0 commit comments