From 499ae45d0da9161472c062bab737a10580951065 Mon Sep 17 00:00:00 2001 From: Patryk Diak Date: Tue, 11 Jun 2024 17:27:06 +0200 Subject: [PATCH 1/2] Add support for Azure Managed Service Identity authentication Signed-off-by: Patryk Diak --- pkg/cloudprovider/azure.go | 141 ++++++++++++++++++++++++------------- 1 file changed, 94 insertions(+), 47 deletions(-) diff --git a/pkg/cloudprovider/azure.go b/pkg/cloudprovider/azure.go index d5f2977c1..a84d9cb2f 100644 --- a/pkg/cloudprovider/azure.go +++ b/pkg/cloudprovider/azure.go @@ -51,51 +51,74 @@ type Azure struct { azureWorkloadIdentityEnabled bool } -func (a *Azure) initCredentials() error { - clientID, err := a.readSecretData("azure_client_id") +type azureCredentialsConfig struct { + clientID string + tenantID string + subscriptionID string + resourceGroup string + clientSecret string + tokenFile string +} + +// readAzureCredentialsConfig reads the azure credentials' configuration. +// Some of the returned fields can be empty, and it is up to the caller to ensure that all the required values are set. +func (a *Azure) readAzureCredentialsConfig() (*azureCredentialsConfig, error) { + var cfg azureCredentialsConfig + var err error + + cfg.clientID, err = a.readSecretData("azure_client_id") if err != nil { - // Fallback to using client ID from env variable if not set. - clientID = os.Getenv("AZURE_CLIENT_ID") - if strings.TrimSpace(clientID) == "" { - return err - } + klog.Infof("azure_client_id not found in the secret: %v, falling back to AZURE_CLIENT_ID env", err) + cfg.clientID = os.Getenv("AZURE_CLIENT_ID") } - tenantID, err := a.readSecretData("azure_tenant_id") + + cfg.tenantID, err = a.readSecretData("azure_tenant_id") if err != nil { - // Fallback to using tenant ID from env variable if not set. - tenantID = os.Getenv("AZURE_TENANT_ID") - if strings.TrimSpace(tenantID) == "" { - return err - } + klog.Infof("azure_tenant_id not found in the secret: %v, falling back to AZURE_TENANT_ID env", err) + cfg.tenantID = os.Getenv("AZURE_TENANT_ID") } - clientSecret, err := a.readSecretData("azure_client_secret") + + cfg.clientSecret, err = a.readSecretData("azure_client_secret") if err != nil { - clientSecret = os.Getenv("AZURE_CLIENT_SECRET") - // Skip validation; fallback to token workload identity token if env variable is also not set. - klog.Infof("Attempting to create workload identity client because azure_client_secret is missing") + klog.Infof("azure_client_secret not found in the secret: %v, falling back to AZURE_CLIENT_SECRET env", err) + cfg.clientSecret = os.Getenv("AZURE_CLIENT_SECRET") } - subscriptionID, err := a.readSecretData("azure_subscription_id") + + cfg.tokenFile, err = a.readSecretData("azure_federated_token_file") if err != nil { - return err + klog.Infof("azure_federated_token_file not found in the secret: %v, falling back to AZURE_FEDERATED_TOKEN_FILE env", err) + cfg.tokenFile = os.Getenv("AZURE_FEDERATED_TOKEN_FILE") } - a.resourceGroup, err = a.readSecretData("azure_resourcegroup") + + cfg.subscriptionID, err = a.readSecretData("azure_subscription_id") + if err != nil { + return nil, fmt.Errorf("azure_subscription_id not found in the secret: %v", err) + } + + cfg.resourceGroup, err = a.readSecretData("azure_resourcegroup") if err != nil { if a.platformStatus != nil && len(strings.TrimSpace(a.platformStatus.ResourceGroupName)) > 0 { klog.Infof("Attempting to use resource group from cluster infrastructure because azure_resourcegroup is missing") - a.resourceGroup = strings.TrimSpace(a.platformStatus.ResourceGroupName) + cfg.resourceGroup = strings.TrimSpace(a.platformStatus.ResourceGroupName) } else { - return err + return nil, fmt.Errorf("azure_resourcegroup not found in the platform status and the secret: %v", err) } } - tokenFile, err := a.readSecretData("azure_federated_token_file") + + return &cfg, nil +} +func (a *Azure) initCredentials() error { + cfg, err := a.readAzureCredentialsConfig() if err != nil { - tokenFile = os.Getenv("AZURE_FEDERATED_TOKEN_FILE") - if strings.TrimSpace(tokenFile) == "" { - // Use default value if no configuration is set - tokenFile = "/var/run/secrets/openshift/serviceaccount/token" - } + return err + } + + if strings.TrimSpace(cfg.tokenFile) == "" { + cfg.tokenFile = "/var/run/secrets/openshift/serviceaccount/token" } + a.resourceGroup = cfg.resourceGroup + // Pick the Azure "Environment", which is just a named set of API endpoints. if a.cfg.APIOverride != "" { a.env, err = azure.EnvironmentFromURL(a.cfg.APIOverride) @@ -110,25 +133,25 @@ func (a *Azure) initCredentials() error { return fmt.Errorf("failed to initialize Azure environment: %w", err) } - authorizer, err := a.getAuthorizer(a.env, clientID, clientSecret, tenantID, tokenFile) + authorizer, err := a.getAuthorizer(a.env, cfg) if err != nil { return err } - a.vmClient = compute.NewVirtualMachinesClientWithBaseURI(a.env.ResourceManagerEndpoint, subscriptionID) + a.vmClient = compute.NewVirtualMachinesClientWithBaseURI(a.env.ResourceManagerEndpoint, cfg.subscriptionID) a.vmClient.Authorizer = authorizer _ = a.vmClient.AddToUserAgent(UserAgent) - a.networkClient = network.NewInterfacesClientWithBaseURI(a.env.ResourceManagerEndpoint, subscriptionID) + a.networkClient = network.NewInterfacesClientWithBaseURI(a.env.ResourceManagerEndpoint, cfg.subscriptionID) a.networkClient.Authorizer = authorizer _ = a.networkClient.AddToUserAgent(UserAgent) - a.virtualNetworkClient = network.NewVirtualNetworksClientWithBaseURI(a.env.ResourceManagerEndpoint, subscriptionID) + a.virtualNetworkClient = network.NewVirtualNetworksClientWithBaseURI(a.env.ResourceManagerEndpoint, cfg.subscriptionID) a.virtualNetworkClient.Authorizer = authorizer _ = a.virtualNetworkClient.AddToUserAgent(UserAgent) a.backendAddressPoolClient = network.NewLoadBalancerBackendAddressPoolsClientWithBaseURI( - a.env.ResourceManagerEndpoint, subscriptionID) + a.env.ResourceManagerEndpoint, cfg.subscriptionID) a.backendAddressPoolClient.Authorizer = authorizer _ = a.backendAddressPoolClient.AddToUserAgent(UserAgent) @@ -537,7 +560,7 @@ func (a *Azure) getAddressPrefixes(networkInterface network.Interface) ([]string return *virtualNetwork.AddressSpace.AddressPrefixes, nil } -func (a *Azure) getAuthorizer(env azureapi.Environment, clientID, clientSecret, tenantID, tokenFile string) (autorest.Authorizer, error) { +func (a *Azure) getAuthorizer(env azureapi.Environment, cfg *azureCredentialsConfig) (autorest.Authorizer, error) { var cloudConfig cloud.Configuration switch env { case azureapi.PublicCloud: @@ -562,26 +585,50 @@ func (a *Azure) getAuthorizer(env azureapi.Environment, clientID, clientSecret, cred azcore.TokenCredential err error ) - if a.azureWorkloadIdentityEnabled && strings.TrimSpace(clientSecret) == "" { - options := azidentity.WorkloadIdentityCredentialOptions{ - ClientOptions: azcore.ClientOptions{ - Cloud: cloudConfig, - }, - ClientID: clientID, - TenantID: tenantID, - TokenFilePath: tokenFile, - } - cred, err = azidentity.NewWorkloadIdentityCredential(&options) - if err != nil { - return nil, err + if strings.TrimSpace(cfg.clientSecret) == "" { + if a.azureWorkloadIdentityEnabled && strings.TrimSpace(cfg.tokenFile) != "" { + klog.Infof("Using workload identity authentication") + if cfg.clientID == "" || cfg.tenantID == "" { + return nil, fmt.Errorf("clientID and tenantID are required in workload identity authentication") + } + options := azidentity.WorkloadIdentityCredentialOptions{ + ClientOptions: azcore.ClientOptions{ + Cloud: cloudConfig, + }, + ClientID: cfg.clientID, + TenantID: cfg.tenantID, + TokenFilePath: cfg.tokenFile, + } + cred, err = azidentity.NewWorkloadIdentityCredential(&options) + if err != nil { + return nil, err + } + } else { + klog.Infof("Using managed identity authentication") + options := azidentity.ManagedIdentityCredentialOptions{ + ClientOptions: azcore.ClientOptions{ + Cloud: cloudConfig, + }, + } + if cfg.clientID != "" { + options.ID = azidentity.ClientID(cfg.clientID) + } + cred, err = azidentity.NewManagedIdentityCredential(&options) + if err != nil { + return nil, err + } } } else { + klog.Infof("Using client secret authentication") + if cfg.clientID == "" || cfg.tenantID == "" { + return nil, fmt.Errorf("clientID and tenantID are required in client secret authentication") + } options := azidentity.ClientSecretCredentialOptions{ ClientOptions: azcore.ClientOptions{ Cloud: cloudConfig, }, } - cred, err = azidentity.NewClientSecretCredential(tenantID, clientID, clientSecret, &options) + cred, err = azidentity.NewClientSecretCredential(cfg.tenantID, cfg.clientID, cfg.clientSecret, &options) if err != nil { return nil, err } From 7c0636ce66834a6ce00055df0a45715b906d2d2d Mon Sep 17 00:00:00 2001 From: Bryan Cox Date: Mon, 5 Aug 2024 16:27:04 -0400 Subject: [PATCH 2/2] Enable Azure MSI override for ARO HCP For ARO HCP, we be able to override the authentication type to be MSI. For more information please see openshift/enhancements#1659. --- pkg/cloudprovider/azure.go | 39 +++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/pkg/cloudprovider/azure.go b/pkg/cloudprovider/azure.go index a84d9cb2f..0f005d47a 100644 --- a/pkg/cloudprovider/azure.go +++ b/pkg/cloudprovider/azure.go @@ -88,6 +88,10 @@ func (a *Azure) readAzureCredentialsConfig() (*azureCredentialsConfig, error) { if err != nil { klog.Infof("azure_federated_token_file not found in the secret: %v, falling back to AZURE_FEDERATED_TOKEN_FILE env", err) cfg.tokenFile = os.Getenv("AZURE_FEDERATED_TOKEN_FILE") + + if strings.TrimSpace(cfg.tokenFile) == "" { + cfg.tokenFile = "/var/run/secrets/openshift/serviceaccount/token" + } } cfg.subscriptionID, err = a.readSecretData("azure_subscription_id") @@ -113,10 +117,6 @@ func (a *Azure) initCredentials() error { return err } - if strings.TrimSpace(cfg.tokenFile) == "" { - cfg.tokenFile = "/var/run/secrets/openshift/serviceaccount/token" - } - a.resourceGroup = cfg.resourceGroup // Pick the Azure "Environment", which is just a named set of API endpoints. @@ -585,7 +585,22 @@ func (a *Azure) getAuthorizer(env azureapi.Environment, cfg *azureCredentialsCon cred azcore.TokenCredential err error ) - if strings.TrimSpace(cfg.clientSecret) == "" { + + // MSI Override for ARO HCP + msi := os.Getenv("AZURE_MSI_AUTHENTICATION") + if msi == "true" { + options := azidentity.ManagedIdentityCredentialOptions{ + ClientOptions: azcore.ClientOptions{ + Cloud: cloudConfig, + }, + } + + var err error + cred, err = azidentity.NewManagedIdentityCredential(&options) + if err != nil { + return nil, err + } + } else if strings.TrimSpace(cfg.clientSecret) == "" { if a.azureWorkloadIdentityEnabled && strings.TrimSpace(cfg.tokenFile) != "" { klog.Infof("Using workload identity authentication") if cfg.clientID == "" || cfg.tenantID == "" { @@ -603,20 +618,6 @@ func (a *Azure) getAuthorizer(env azureapi.Environment, cfg *azureCredentialsCon if err != nil { return nil, err } - } else { - klog.Infof("Using managed identity authentication") - options := azidentity.ManagedIdentityCredentialOptions{ - ClientOptions: azcore.ClientOptions{ - Cloud: cloudConfig, - }, - } - if cfg.clientID != "" { - options.ID = azidentity.ClientID(cfg.clientID) - } - cred, err = azidentity.NewManagedIdentityCredential(&options) - if err != nil { - return nil, err - } } } else { klog.Infof("Using client secret authentication")