From 3c298707b89b87d0fb8a98e012fb32e11db59172 Mon Sep 17 00:00:00 2001 From: Flavian Missi Date: Fri, 9 Jun 2023 12:03:41 +0200 Subject: [PATCH] pkg/storage/azure: add support for auth with workload identity --- pkg/storage/azure/azure.go | 79 ++++++++++++++++++++++++--------- pkg/storage/azure/azure_test.go | 26 +++++++++++ 2 files changed, 83 insertions(+), 22 deletions(-) diff --git a/pkg/storage/azure/azure.go b/pkg/storage/azure/azure.go index 868f5bfb3..7d8681091 100644 --- a/pkg/storage/azure/azure.go +++ b/pkg/storage/azure/azure.go @@ -60,12 +60,13 @@ var storageAccountInvalidCharRe = regexp.MustCompile(`[^0-9A-Za-z]`) // Azure holds configuration used to reach Azure's endpoints. type Azure struct { // IPI - SubscriptionID string - ClientID string - ClientSecret string - TenantID string - ResourceGroup string - Region string + SubscriptionID string + ClientID string + ClientSecret string + TenantID string + ResourceGroup string + Region string + FederatedTokenFile string // UPI AccountKey string @@ -95,14 +96,27 @@ func GetConfig(secLister kcorelisters.SecretNamespaceLister, infraLister configl return nil, fmt.Errorf("unable to get cluster minted credentials: %s", err) } - return &Azure{ - SubscriptionID: string(sec.Data["azure_subscription_id"]), - ClientID: string(sec.Data["azure_client_id"]), - ClientSecret: string(sec.Data["azure_client_secret"]), - TenantID: string(sec.Data["azure_tenant_id"]), - ResourceGroup: string(sec.Data["azure_resourcegroup"]), - Region: string(sec.Data["azure_region"]), - }, nil + cfg := &Azure{ + SubscriptionID: string(sec.Data["azure_subscription_id"]), + ClientID: string(sec.Data["azure_client_id"]), + ClientSecret: string(sec.Data["azure_client_secret"]), + TenantID: string(sec.Data["azure_tenant_id"]), + ResourceGroup: string(sec.Data["azure_resourcegroup"]), + Region: string(sec.Data["azure_region"]), + FederatedTokenFile: string(sec.Data["azure_federated_token_file"]), + } + + // when using azure workload identities, the secret does not contain + // a resource group, as it is not known at the time of its creation. + if cfg.ResourceGroup == "" { + infra, err := util.GetInfrastructure(infraLister) + if err != nil { + return nil, fmt.Errorf("unable to get infrastructure object: %s", err) + } + cfg.ResourceGroup = infra.Status.PlatformStatus.Azure.ResourceGroupName + } + + return cfg, nil } // loads user provided account key. @@ -316,15 +330,36 @@ func (d *driver) storageAccountsClient(cfg *Azure, environment autorestazure.Env }, }, } - options := azidentity.ClientSecretCredentialOptions{ - ClientOptions: azcore.ClientOptions{ - Cloud: cloudConfig, - }, - } - cred, err := azidentity.NewClientSecretCredential(cfg.TenantID, cfg.ClientID, cfg.ClientSecret, &options) - if err != nil { - return storage.AccountsClient{}, err + + var ( + cred azcore.TokenCredential + err error + ) + if strings.TrimSpace(cfg.ClientSecret) == "" { + options := azidentity.WorkloadIdentityCredentialOptions{ + ClientOptions: azcore.ClientOptions{ + Cloud: cloudConfig, + }, + ClientID: cfg.ClientID, + TenantID: cfg.TenantID, + TokenFilePath: cfg.FederatedTokenFile, + } + cred, err = azidentity.NewWorkloadIdentityCredential(&options) + if err != nil { + return storage.AccountsClient{}, err + } + } else { + options := azidentity.ClientSecretCredentialOptions{ + ClientOptions: azcore.ClientOptions{ + Cloud: cloudConfig, + }, + } + cred, err = azidentity.NewClientSecretCredential(cfg.TenantID, cfg.ClientID, cfg.ClientSecret, &options) + if err != nil { + return storage.AccountsClient{}, err + } } + scope := environment.TokenAudience if !strings.HasSuffix(scope, "/.default") { scope += "/.default" diff --git a/pkg/storage/azure/azure_test.go b/pkg/storage/azure/azure_test.go index 53ce5ad98..ae16ae2b8 100644 --- a/pkg/storage/azure/azure_test.go +++ b/pkg/storage/azure/azure_test.go @@ -153,6 +153,32 @@ func TestGetConfig(t *testing.T) { Region: "region", }, }, + { + name: "cloud credentials workload identity", + secrets: []runtime.Object{ + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: defaults.CloudCredentialsName, + Namespace: "test", + }, + Data: map[string][]byte{ + "azure_client_id": []byte("client_id"), + "azure_federated_token_file": []byte("/path/to/token"), + "azure_region": []byte("region"), + "azure_subscription_id": []byte("subscription_id"), + "azure_tenant_id": []byte("tenant_id"), + }, + }, + }, + result: &Azure{ + SubscriptionID: "subscription_id", + ClientID: "client_id", + TenantID: "tenant_id", + ResourceGroup: "resource-group-123", + Region: "region", + FederatedTokenFile: "/path/to/token", + }, + }, } { t.Run(tt.name, func(t *testing.T) { indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{})