|  | // Copyright (c) HashiCorp, Inc. | 
|  | // SPDX-License-Identifier: MPL-2.0 | 
|  |  | 
|  | package azure | 
|  |  | 
|  | import ( | 
|  | "context" | 
|  | "fmt" | 
|  | "log" | 
|  | "os" | 
|  | "time" | 
|  |  | 
|  | "github.com/Azure/azure-sdk-for-go/profiles/2017-03-09/resources/mgmt/resources" | 
|  | armStorage "github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2021-01-01/storage" | 
|  | "github.com/Azure/go-autorest/autorest" | 
|  | "github.com/Azure/go-autorest/autorest/azure" | 
|  | "github.com/hashicorp/go-azure-helpers/authentication" | 
|  | "github.com/hashicorp/go-azure-helpers/sender" | 
|  | "github.com/hashicorp/terraform/internal/httpclient" | 
|  | "github.com/hashicorp/terraform/version" | 
|  | "github.com/manicminer/hamilton/environments" | 
|  | "github.com/tombuildsstuff/giovanni/storage/2018-11-09/blob/blobs" | 
|  | "github.com/tombuildsstuff/giovanni/storage/2018-11-09/blob/containers" | 
|  | ) | 
|  |  | 
|  | type ArmClient struct { | 
|  | // These Clients are only initialized if an Access Key isn't provided | 
|  | groupsClient          *resources.GroupsClient | 
|  | storageAccountsClient *armStorage.AccountsClient | 
|  | containersClient      *containers.Client | 
|  | blobsClient           *blobs.Client | 
|  |  | 
|  | // azureAdStorageAuth is only here if we're using AzureAD Authentication but is an Authorizer for Storage | 
|  | azureAdStorageAuth *autorest.Authorizer | 
|  |  | 
|  | accessKey          string | 
|  | environment        azure.Environment | 
|  | resourceGroupName  string | 
|  | storageAccountName string | 
|  | sasToken           string | 
|  | } | 
|  |  | 
|  | func buildArmClient(ctx context.Context, config BackendConfig) (*ArmClient, error) { | 
|  | env, err := authentication.AzureEnvironmentByNameFromEndpoint(ctx, config.MetadataHost, config.Environment) | 
|  | if err != nil { | 
|  | return nil, err | 
|  | } | 
|  |  | 
|  | client := ArmClient{ | 
|  | environment:        *env, | 
|  | resourceGroupName:  config.ResourceGroupName, | 
|  | storageAccountName: config.StorageAccountName, | 
|  | } | 
|  |  | 
|  | // if we have an Access Key - we don't need the other clients | 
|  | if config.AccessKey != "" { | 
|  | client.accessKey = config.AccessKey | 
|  | return &client, nil | 
|  | } | 
|  |  | 
|  | // likewise with a SAS token | 
|  | if config.SasToken != "" { | 
|  | client.sasToken = config.SasToken | 
|  | return &client, nil | 
|  | } | 
|  |  | 
|  | builder := authentication.Builder{ | 
|  | ClientID:                      config.ClientID, | 
|  | SubscriptionID:                config.SubscriptionID, | 
|  | TenantID:                      config.TenantID, | 
|  | CustomResourceManagerEndpoint: config.CustomResourceManagerEndpoint, | 
|  | MetadataHost:                  config.MetadataHost, | 
|  | Environment:                   config.Environment, | 
|  | ClientSecretDocsLink:          "https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/guides/service_principal_client_secret", | 
|  |  | 
|  | // Service Principal (Client Certificate) | 
|  | ClientCertPassword: config.ClientCertificatePassword, | 
|  | ClientCertPath:     config.ClientCertificatePath, | 
|  |  | 
|  | // Service Principal (Client Secret) | 
|  | ClientSecret: config.ClientSecret, | 
|  |  | 
|  | // Managed Service Identity | 
|  | MsiEndpoint: config.MsiEndpoint, | 
|  |  | 
|  | // OIDC | 
|  | IDToken:             config.OIDCToken, | 
|  | IDTokenFilePath:     config.OIDCTokenFilePath, | 
|  | IDTokenRequestURL:   config.OIDCRequestURL, | 
|  | IDTokenRequestToken: config.OIDCRequestToken, | 
|  |  | 
|  | // Feature Toggles | 
|  | SupportsAzureCliToken:          true, | 
|  | SupportsClientCertAuth:         true, | 
|  | SupportsClientSecretAuth:       true, | 
|  | SupportsManagedServiceIdentity: config.UseMsi, | 
|  | SupportsOIDCAuth:               config.UseOIDC, | 
|  | UseMicrosoftGraph:              true, | 
|  | } | 
|  | armConfig, err := builder.Build() | 
|  | if err != nil { | 
|  | return nil, fmt.Errorf("Error building ARM Config: %+v", err) | 
|  | } | 
|  |  | 
|  | oauthConfig, err := armConfig.BuildOAuthConfig(env.ActiveDirectoryEndpoint) | 
|  | if err != nil { | 
|  | return nil, err | 
|  | } | 
|  |  | 
|  | hamiltonEnv, err := environments.EnvironmentFromString(config.Environment) | 
|  | if err != nil { | 
|  | return nil, err | 
|  | } | 
|  |  | 
|  | sender := sender.BuildSender("backend/remote-state/azure") | 
|  | log.Printf("[DEBUG] Obtaining an MSAL / Microsoft Graph token for Resource Manager..") | 
|  | auth, err := armConfig.GetMSALToken(ctx, hamiltonEnv.ResourceManager, sender, oauthConfig, env.TokenAudience) | 
|  | if err != nil { | 
|  | return nil, err | 
|  | } | 
|  |  | 
|  | if config.UseAzureADAuthentication { | 
|  | log.Printf("[DEBUG] Obtaining an MSAL / Microsoft Graph token for Storage..") | 
|  | storageAuth, err := armConfig.GetMSALToken(ctx, hamiltonEnv.Storage, sender, oauthConfig, env.ResourceIdentifiers.Storage) | 
|  | if err != nil { | 
|  | return nil, err | 
|  | } | 
|  | client.azureAdStorageAuth = &storageAuth | 
|  | } | 
|  |  | 
|  | accountsClient := armStorage.NewAccountsClientWithBaseURI(env.ResourceManagerEndpoint, armConfig.SubscriptionID) | 
|  | client.configureClient(&accountsClient.Client, auth) | 
|  | client.storageAccountsClient = &accountsClient | 
|  |  | 
|  | groupsClient := resources.NewGroupsClientWithBaseURI(env.ResourceManagerEndpoint, armConfig.SubscriptionID) | 
|  | client.configureClient(&groupsClient.Client, auth) | 
|  | client.groupsClient = &groupsClient | 
|  |  | 
|  | return &client, nil | 
|  | } | 
|  |  | 
|  | func (c ArmClient) getBlobClient(ctx context.Context) (*blobs.Client, error) { | 
|  | if c.sasToken != "" { | 
|  | log.Printf("[DEBUG] Building the Blob Client from a SAS Token") | 
|  | storageAuth, err := autorest.NewSASTokenAuthorizer(c.sasToken) | 
|  | if err != nil { | 
|  | return nil, fmt.Errorf("Error building Authorizer: %+v", err) | 
|  | } | 
|  |  | 
|  | blobsClient := blobs.NewWithEnvironment(c.environment) | 
|  | c.configureClient(&blobsClient.Client, storageAuth) | 
|  | return &blobsClient, nil | 
|  | } | 
|  |  | 
|  | if c.azureAdStorageAuth != nil { | 
|  | blobsClient := blobs.NewWithEnvironment(c.environment) | 
|  | c.configureClient(&blobsClient.Client, *c.azureAdStorageAuth) | 
|  | return &blobsClient, nil | 
|  | } | 
|  |  | 
|  | accessKey := c.accessKey | 
|  | if accessKey == "" { | 
|  | log.Printf("[DEBUG] Building the Blob Client from an Access Token (using user credentials)") | 
|  | keys, err := c.storageAccountsClient.ListKeys(ctx, c.resourceGroupName, c.storageAccountName, "") | 
|  | if err != nil { | 
|  | return nil, fmt.Errorf("Error retrieving keys for Storage Account %q: %s", c.storageAccountName, err) | 
|  | } | 
|  |  | 
|  | if keys.Keys == nil { | 
|  | return nil, fmt.Errorf("Nil key returned for storage account %q", c.storageAccountName) | 
|  | } | 
|  |  | 
|  | accessKeys := *keys.Keys | 
|  | accessKey = *accessKeys[0].Value | 
|  | } | 
|  |  | 
|  | storageAuth, err := autorest.NewSharedKeyAuthorizer(c.storageAccountName, accessKey, autorest.SharedKey) | 
|  | if err != nil { | 
|  | return nil, fmt.Errorf("Error building Authorizer: %+v", err) | 
|  | } | 
|  |  | 
|  | blobsClient := blobs.NewWithEnvironment(c.environment) | 
|  | c.configureClient(&blobsClient.Client, storageAuth) | 
|  | return &blobsClient, nil | 
|  | } | 
|  |  | 
|  | func (c ArmClient) getContainersClient(ctx context.Context) (*containers.Client, error) { | 
|  | if c.sasToken != "" { | 
|  | log.Printf("[DEBUG] Building the Container Client from a SAS Token") | 
|  | storageAuth, err := autorest.NewSASTokenAuthorizer(c.sasToken) | 
|  | if err != nil { | 
|  | return nil, fmt.Errorf("Error building Authorizer: %+v", err) | 
|  | } | 
|  |  | 
|  | containersClient := containers.NewWithEnvironment(c.environment) | 
|  | c.configureClient(&containersClient.Client, storageAuth) | 
|  | return &containersClient, nil | 
|  | } | 
|  |  | 
|  | if c.azureAdStorageAuth != nil { | 
|  | containersClient := containers.NewWithEnvironment(c.environment) | 
|  | c.configureClient(&containersClient.Client, *c.azureAdStorageAuth) | 
|  | return &containersClient, nil | 
|  | } | 
|  |  | 
|  | accessKey := c.accessKey | 
|  | if accessKey == "" { | 
|  | log.Printf("[DEBUG] Building the Container Client from an Access Token (using user credentials)") | 
|  | keys, err := c.storageAccountsClient.ListKeys(ctx, c.resourceGroupName, c.storageAccountName, "") | 
|  | if err != nil { | 
|  | return nil, fmt.Errorf("Error retrieving keys for Storage Account %q: %s", c.storageAccountName, err) | 
|  | } | 
|  |  | 
|  | if keys.Keys == nil { | 
|  | return nil, fmt.Errorf("Nil key returned for storage account %q", c.storageAccountName) | 
|  | } | 
|  |  | 
|  | accessKeys := *keys.Keys | 
|  | accessKey = *accessKeys[0].Value | 
|  | } | 
|  |  | 
|  | storageAuth, err := autorest.NewSharedKeyAuthorizer(c.storageAccountName, accessKey, autorest.SharedKey) | 
|  | if err != nil { | 
|  | return nil, fmt.Errorf("Error building Authorizer: %+v", err) | 
|  | } | 
|  |  | 
|  | containersClient := containers.NewWithEnvironment(c.environment) | 
|  | c.configureClient(&containersClient.Client, storageAuth) | 
|  | return &containersClient, nil | 
|  | } | 
|  |  | 
|  | func (c *ArmClient) configureClient(client *autorest.Client, auth autorest.Authorizer) { | 
|  | client.UserAgent = buildUserAgent() | 
|  | client.Authorizer = auth | 
|  | client.Sender = buildSender() | 
|  | client.SkipResourceProviderRegistration = false | 
|  | client.PollingDuration = 60 * time.Minute | 
|  | } | 
|  |  | 
|  | func buildUserAgent() string { | 
|  | userAgent := httpclient.TerraformUserAgent(version.Version) | 
|  |  | 
|  | // append the CloudShell version to the user agent if it exists | 
|  | if azureAgent := os.Getenv("AZURE_HTTP_USER_AGENT"); azureAgent != "" { | 
|  | userAgent = fmt.Sprintf("%s %s", userAgent, azureAgent) | 
|  | } | 
|  |  | 
|  | return userAgent | 
|  | } |