Skip to content

Commit 8bb474a

Browse files
committed
pkg/storage/azure: configure registry with workload identity
1 parent c209320 commit 8bb474a

File tree

2 files changed

+99
-2
lines changed

2 files changed

+99
-2
lines changed

pkg/storage/azure/azure.go

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,8 @@ func (d *driver) ConfigEnv() (envs envvar.List, err error) {
406406
}
407407

408408
key := cfg.AccountKey
409-
if key == "" {
409+
federated_token := cfg.FederatedTokenFile
410+
if key == "" && federated_token == "" {
410411
storageAccountsClient, err := d.storageAccountsClient(cfg, environment)
411412
if err != nil {
412413
return nil, err
@@ -418,11 +419,30 @@ func (d *driver) ConfigEnv() (envs envvar.List, err error) {
418419
}
419420
}
420421

422+
if key != "" {
423+
envs = append(envs,
424+
envvar.EnvVar{Name: "REGISTRY_STORAGE_AZURE_ACCOUNTKEY", Value: key, Secret: true},
425+
)
426+
}
427+
428+
// the AZURE_ vars used to configure workload identity are taken
429+
// from https://github.com/distribution/distribution/blob/6a57630cf40122000083e60bcb7e97c50a904c5e/vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/default_azure_credential.go#LL86C43-L86C63
430+
if federated_token != "" {
431+
envs = append(envs,
432+
// NOTE: these env vars are not prepended with REGISTRY_STORAGE
433+
// because they're exported for the azure-sdk, not the registry.
434+
// we do this as a transparent way to support workload identity in the registry.
435+
envvar.EnvVar{Name: "AZURE_CLIENT_ID", Value: cfg.ClientID},
436+
envvar.EnvVar{Name: "AZURE_TENANT_ID", Value: cfg.TenantID},
437+
envvar.EnvVar{Name: "AZURE_FEDERATED_TOKEN_FILE", Value: federated_token},
438+
envvar.EnvVar{Name: "AZURE_AUTHORITY_HOST", Value: environment.ActiveDirectoryEndpoint},
439+
)
440+
}
441+
421442
envs = append(envs,
422443
envvar.EnvVar{Name: "REGISTRY_STORAGE", Value: "azure"},
423444
envvar.EnvVar{Name: "REGISTRY_STORAGE_AZURE_CONTAINER", Value: d.Config.Container},
424445
envvar.EnvVar{Name: "REGISTRY_STORAGE_AZURE_ACCOUNTNAME", Value: d.Config.AccountName},
425-
envvar.EnvVar{Name: "REGISTRY_STORAGE_AZURE_ACCOUNTKEY", Value: key, Secret: true},
426446
)
427447

428448
if d.Config.CloudName != "" {

pkg/storage/azure/azure_test.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,83 @@ func TestConfigEnv(t *testing.T) {
339339
}
340340
}
341341

342+
func TestConfigEnvWorkloadIdentity(t *testing.T) {
343+
ctx := context.Background()
344+
345+
config := &imageregistryv1.ImageRegistryConfigStorageAzure{}
346+
347+
testBuilder := cirofake.NewFixturesBuilder()
348+
testBuilder.AddInfraConfig(&configv1.Infrastructure{
349+
ObjectMeta: metav1.ObjectMeta{
350+
Name: "cluster",
351+
},
352+
Status: configv1.InfrastructureStatus{
353+
PlatformStatus: &configv1.PlatformStatus{
354+
Type: configv1.AzurePlatformType,
355+
Azure: &configv1.AzurePlatformStatus{
356+
ResourceGroupName: "resourcegroup",
357+
CloudName: configv1.AzureUSGovernmentCloud,
358+
},
359+
},
360+
},
361+
})
362+
testBuilder.AddSecrets(&corev1.Secret{
363+
ObjectMeta: metav1.ObjectMeta{
364+
Name: defaults.CloudCredentialsName,
365+
Namespace: defaults.ImageRegistryOperatorNamespace,
366+
},
367+
Data: map[string][]byte{
368+
"azure_client_id": []byte("client_id"),
369+
"azure_federated_token_file": []byte("/path/to/file"),
370+
"azure_region": []byte("region"),
371+
"azure_subscription_id": []byte("subscription_id"),
372+
"azure_tenant_id": []byte("tenant_id"),
373+
},
374+
})
375+
376+
listers := testBuilder.BuildListers()
377+
378+
authorizer := autorest.NullAuthorizer{}
379+
sender := mocks.NewSender()
380+
sender.AppendResponse(mocks.NewResponseWithContent(`{"nameAvailable":true}`))
381+
sender.AppendResponse(mocks.NewResponseWithContent(`?`))
382+
sender.AppendResponse(mocks.NewResponseWithContent(`{"name":"account"}`))
383+
sender.AppendResponse(mocks.NewResponseWithContent(`{"keys":[{"value":"firstKey"}]}`))
384+
sender.AppendResponse(mocks.NewResponseWithContent(`{"keys":[{"value":"firstKey"}]}`))
385+
httpSender := pipeline.FactoryFunc(func(next pipeline.Policy, po *pipeline.PolicyOptions) pipeline.PolicyFunc {
386+
return func(ctx context.Context, request pipeline.Request) (pipeline.Response, error) {
387+
return pipeline.NewHTTPResponse(mocks.NewResponseWithContent(`{}`)), nil
388+
}
389+
})
390+
391+
d := NewDriver(ctx, config, &listers.StorageListers)
392+
d.authorizer = authorizer
393+
d.sender = sender
394+
d.httpSender = httpSender
395+
396+
envvars, err := d.ConfigEnv()
397+
if err != nil {
398+
t.Fatal(err)
399+
}
400+
401+
expectedVars := map[string]interface{}{
402+
"REGISTRY_STORAGE": "azure",
403+
"AZURE_CLIENT_ID": "client_id",
404+
"AZURE_TENANT_ID": "tenant_id",
405+
"AZURE_FEDERATED_TOKEN_FILE": "/path/to/file",
406+
"AZURE_AUTHORITY_HOST": "https://login.microsoftonline.com/", // default for configv1.AzureUSGovernmentCloud
407+
}
408+
for key, value := range expectedVars {
409+
e := findEnvVar(envvars, key)
410+
if e == nil {
411+
t.Fatalf("envvar %s not found, %v", key, envvars)
412+
}
413+
if e.Value != value {
414+
t.Errorf("%s: got %#+v, want %#+v", key, e.Value, value)
415+
}
416+
}
417+
}
418+
342419
func TestConfigEnvWithUserKey(t *testing.T) {
343420
ctx := context.Background()
344421

0 commit comments

Comments
 (0)