github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/backend/remote-state/azure/arm_client.go (about) 1 package azure 2 3 import ( 4 "context" 5 "fmt" 6 "log" 7 "net/url" 8 "os" 9 "strings" 10 "time" 11 12 "github.com/Azure/azure-sdk-for-go/profiles/2017-03-09/resources/mgmt/resources" 13 armStorage "github.com/Azure/azure-sdk-for-go/profiles/2017-03-09/storage/mgmt/storage" 14 "github.com/Azure/azure-sdk-for-go/storage" 15 "github.com/Azure/go-autorest/autorest" 16 "github.com/Azure/go-autorest/autorest/azure" 17 "github.com/hashicorp/go-azure-helpers/authentication" 18 "github.com/hashicorp/go-azure-helpers/sender" 19 "github.com/hashicorp/terraform/httpclient" 20 ) 21 22 type ArmClient struct { 23 // These Clients are only initialized if an Access Key isn't provided 24 groupsClient *resources.GroupsClient 25 storageAccountsClient *armStorage.AccountsClient 26 27 accessKey string 28 environment azure.Environment 29 resourceGroupName string 30 storageAccountName string 31 sasToken string 32 } 33 34 func buildArmClient(config BackendConfig) (*ArmClient, error) { 35 env, err := buildArmEnvironment(config) 36 if err != nil { 37 return nil, err 38 } 39 40 client := ArmClient{ 41 environment: *env, 42 resourceGroupName: config.ResourceGroupName, 43 storageAccountName: config.StorageAccountName, 44 } 45 46 // if we have an Access Key - we don't need the other clients 47 if config.AccessKey != "" { 48 client.accessKey = config.AccessKey 49 return &client, nil 50 } 51 52 // likewise with a SAS token 53 if config.SasToken != "" { 54 client.sasToken = config.SasToken 55 return &client, nil 56 } 57 58 builder := authentication.Builder{ 59 ClientID: config.ClientID, 60 ClientSecret: config.ClientSecret, 61 SubscriptionID: config.SubscriptionID, 62 TenantID: config.TenantID, 63 CustomResourceManagerEndpoint: config.CustomResourceManagerEndpoint, 64 Environment: config.Environment, 65 MsiEndpoint: config.MsiEndpoint, 66 67 // Feature Toggles 68 SupportsAzureCliToken: true, 69 SupportsClientSecretAuth: true, 70 SupportsManagedServiceIdentity: config.UseMsi, 71 // TODO: support for Client Certificate auth 72 } 73 armConfig, err := builder.Build() 74 if err != nil { 75 return nil, fmt.Errorf("Error building ARM Config: %+v", err) 76 } 77 78 oauthConfig, err := armConfig.BuildOAuthConfig(env.ActiveDirectoryEndpoint) 79 if err != nil { 80 return nil, err 81 } 82 83 auth, err := armConfig.GetAuthorizationToken(sender.BuildSender("backend/remote-state/azure"), oauthConfig, env.TokenAudience) 84 if err != nil { 85 return nil, err 86 } 87 88 accountsClient := armStorage.NewAccountsClientWithBaseURI(env.ResourceManagerEndpoint, armConfig.SubscriptionID) 89 client.configureClient(&accountsClient.Client, auth) 90 client.storageAccountsClient = &accountsClient 91 92 groupsClient := resources.NewGroupsClientWithBaseURI(env.ResourceManagerEndpoint, armConfig.SubscriptionID) 93 client.configureClient(&groupsClient.Client, auth) 94 client.groupsClient = &groupsClient 95 96 return &client, nil 97 } 98 99 func buildArmEnvironment(config BackendConfig) (*azure.Environment, error) { 100 if config.CustomResourceManagerEndpoint != "" { 101 log.Printf("[DEBUG] Loading Environment from Endpoint %q", config.CustomResourceManagerEndpoint) 102 return authentication.LoadEnvironmentFromUrl(config.CustomResourceManagerEndpoint) 103 } 104 105 log.Printf("[DEBUG] Loading Environment %q", config.Environment) 106 return authentication.DetermineEnvironment(config.Environment) 107 } 108 109 func (c ArmClient) getBlobClient(ctx context.Context) (*storage.BlobStorageClient, error) { 110 if c.accessKey != "" { 111 log.Printf("[DEBUG] Building the Blob Client from an Access Token") 112 storageClient, err := storage.NewBasicClientOnSovereignCloud(c.storageAccountName, c.accessKey, c.environment) 113 if err != nil { 114 return nil, fmt.Errorf("Error creating storage client for storage account %q: %s", c.storageAccountName, err) 115 } 116 client := storageClient.GetBlobService() 117 return &client, nil 118 } 119 120 if c.sasToken != "" { 121 log.Printf("[DEBUG] Building the Blob Client from a SAS Token") 122 token := strings.TrimPrefix(c.sasToken, "?") 123 uri, err := url.ParseQuery(token) 124 if err != nil { 125 return nil, fmt.Errorf("Error parsing SAS Token: %+v", err) 126 } 127 128 storageClient := storage.NewAccountSASClient(c.storageAccountName, uri, c.environment) 129 client := storageClient.GetBlobService() 130 return &client, nil 131 } 132 133 log.Printf("[DEBUG] Building the Blob Client from an Access Token (using user credentials)") 134 keys, err := c.storageAccountsClient.ListKeys(ctx, c.resourceGroupName, c.storageAccountName) 135 if err != nil { 136 return nil, fmt.Errorf("Error retrieving keys for Storage Account %q: %s", c.storageAccountName, err) 137 } 138 139 if keys.Keys == nil { 140 return nil, fmt.Errorf("Nil key returned for storage account %q", c.storageAccountName) 141 } 142 143 accessKeys := *keys.Keys 144 accessKey := accessKeys[0].Value 145 146 storageClient, err := storage.NewBasicClientOnSovereignCloud(c.storageAccountName, *accessKey, c.environment) 147 if err != nil { 148 return nil, fmt.Errorf("Error creating storage client for storage account %q: %s", c.storageAccountName, err) 149 } 150 client := storageClient.GetBlobService() 151 return &client, nil 152 } 153 154 func (c *ArmClient) configureClient(client *autorest.Client, auth autorest.Authorizer) { 155 client.UserAgent = buildUserAgent() 156 client.Authorizer = auth 157 client.Sender = buildSender() 158 client.SkipResourceProviderRegistration = false 159 client.PollingDuration = 60 * time.Minute 160 } 161 162 func buildUserAgent() string { 163 userAgent := httpclient.UserAgentString() 164 165 // append the CloudShell version to the user agent if it exists 166 if azureAgent := os.Getenv("AZURE_HTTP_USER_AGENT"); azureAgent != "" { 167 userAgent = fmt.Sprintf("%s %s", userAgent, azureAgent) 168 } 169 170 return userAgent 171 }