github.com/jaredpalmer/terraform@v1.1.0-alpha20210908.0.20210911170307-88705c943a03/internal/backend/remote-state/azure/arm_client.go (about) 1 package azure 2 3 import ( 4 "context" 5 "fmt" 6 "log" 7 "os" 8 "time" 9 10 "github.com/tombuildsstuff/giovanni/storage/2018-11-09/blob/blobs" 11 "github.com/tombuildsstuff/giovanni/storage/2018-11-09/blob/containers" 12 13 "github.com/Azure/azure-sdk-for-go/profiles/2017-03-09/resources/mgmt/resources" 14 armStorage "github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2021-01-01/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/internal/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 containersClient *containers.Client 27 blobsClient *blobs.Client 28 29 // azureAdStorageAuth is only here if we're using AzureAD Authentication but is an Authorizer for Storage 30 azureAdStorageAuth *autorest.Authorizer 31 32 accessKey string 33 environment azure.Environment 34 resourceGroupName string 35 storageAccountName string 36 sasToken string 37 } 38 39 func buildArmClient(ctx context.Context, config BackendConfig) (*ArmClient, error) { 40 env, err := authentication.AzureEnvironmentByNameFromEndpoint(ctx, config.MetadataHost, config.Environment) 41 if err != nil { 42 return nil, err 43 } 44 45 client := ArmClient{ 46 environment: *env, 47 resourceGroupName: config.ResourceGroupName, 48 storageAccountName: config.StorageAccountName, 49 } 50 51 // if we have an Access Key - we don't need the other clients 52 if config.AccessKey != "" { 53 client.accessKey = config.AccessKey 54 return &client, nil 55 } 56 57 // likewise with a SAS token 58 if config.SasToken != "" { 59 client.sasToken = config.SasToken 60 return &client, nil 61 } 62 63 builder := authentication.Builder{ 64 ClientID: config.ClientID, 65 SubscriptionID: config.SubscriptionID, 66 TenantID: config.TenantID, 67 CustomResourceManagerEndpoint: config.CustomResourceManagerEndpoint, 68 MetadataHost: config.MetadataHost, 69 Environment: config.Environment, 70 ClientSecretDocsLink: "https://www.terraform.io/docs/providers/azurerm/guides/service_principal_client_secret.html", 71 72 // Service Principal (Client Certificate) 73 ClientCertPassword: config.ClientCertificatePassword, 74 ClientCertPath: config.ClientCertificatePath, 75 76 // Service Principal (Client Secret) 77 ClientSecret: config.ClientSecret, 78 79 // Managed Service Identity 80 MsiEndpoint: config.MsiEndpoint, 81 82 // Feature Toggles 83 SupportsAzureCliToken: true, 84 SupportsClientCertAuth: true, 85 SupportsClientSecretAuth: true, 86 SupportsManagedServiceIdentity: config.UseMsi, 87 } 88 armConfig, err := builder.Build() 89 if err != nil { 90 return nil, fmt.Errorf("Error building ARM Config: %+v", err) 91 } 92 93 oauthConfig, err := armConfig.BuildOAuthConfig(env.ActiveDirectoryEndpoint) 94 if err != nil { 95 return nil, err 96 } 97 98 sender := sender.BuildSender("backend/remote-state/azure") 99 auth, err := armConfig.GetAuthorizationToken(sender, oauthConfig, env.TokenAudience) 100 if err != nil { 101 return nil, err 102 } 103 104 if config.UseAzureADAuthentication { 105 storageAuth, err := armConfig.GetAuthorizationToken(sender, oauthConfig, env.ResourceIdentifiers.Storage) 106 if err != nil { 107 return nil, err 108 } 109 client.azureAdStorageAuth = &storageAuth 110 } 111 112 accountsClient := armStorage.NewAccountsClientWithBaseURI(env.ResourceManagerEndpoint, armConfig.SubscriptionID) 113 client.configureClient(&accountsClient.Client, auth) 114 client.storageAccountsClient = &accountsClient 115 116 groupsClient := resources.NewGroupsClientWithBaseURI(env.ResourceManagerEndpoint, armConfig.SubscriptionID) 117 client.configureClient(&groupsClient.Client, auth) 118 client.groupsClient = &groupsClient 119 120 return &client, nil 121 } 122 123 func buildArmEnvironment(config BackendConfig) (*azure.Environment, error) { 124 // TODO: can we remove this? 125 // https://github.com/hashicorp/terraform/issues/27156 126 if config.CustomResourceManagerEndpoint != "" { 127 log.Printf("[DEBUG] Loading Environment from Endpoint %q", config.CustomResourceManagerEndpoint) 128 return authentication.LoadEnvironmentFromUrl(config.CustomResourceManagerEndpoint) 129 } 130 131 log.Printf("[DEBUG] Loading Environment %q", config.Environment) 132 return authentication.DetermineEnvironment(config.Environment) 133 } 134 135 func (c ArmClient) getBlobClient(ctx context.Context) (*blobs.Client, error) { 136 if c.sasToken != "" { 137 log.Printf("[DEBUG] Building the Blob Client from a SAS Token") 138 storageAuth, err := autorest.NewSASTokenAuthorizer(c.sasToken) 139 if err != nil { 140 return nil, fmt.Errorf("Error building Authorizer: %+v", err) 141 } 142 143 blobsClient := blobs.NewWithEnvironment(c.environment) 144 c.configureClient(&blobsClient.Client, storageAuth) 145 return &blobsClient, nil 146 } 147 148 if c.azureAdStorageAuth != nil { 149 blobsClient := blobs.NewWithEnvironment(c.environment) 150 c.configureClient(&blobsClient.Client, *c.azureAdStorageAuth) 151 return &blobsClient, nil 152 } 153 154 accessKey := c.accessKey 155 if accessKey == "" { 156 log.Printf("[DEBUG] Building the Blob Client from an Access Token (using user credentials)") 157 keys, err := c.storageAccountsClient.ListKeys(ctx, c.resourceGroupName, c.storageAccountName, "") 158 if err != nil { 159 return nil, fmt.Errorf("Error retrieving keys for Storage Account %q: %s", c.storageAccountName, err) 160 } 161 162 if keys.Keys == nil { 163 return nil, fmt.Errorf("Nil key returned for storage account %q", c.storageAccountName) 164 } 165 166 accessKeys := *keys.Keys 167 accessKey = *accessKeys[0].Value 168 } 169 170 storageAuth, err := autorest.NewSharedKeyAuthorizer(c.storageAccountName, accessKey, autorest.SharedKey) 171 if err != nil { 172 return nil, fmt.Errorf("Error building Authorizer: %+v", err) 173 } 174 175 blobsClient := blobs.NewWithEnvironment(c.environment) 176 c.configureClient(&blobsClient.Client, storageAuth) 177 return &blobsClient, nil 178 } 179 180 func (c ArmClient) getContainersClient(ctx context.Context) (*containers.Client, error) { 181 if c.sasToken != "" { 182 log.Printf("[DEBUG] Building the Container Client from a SAS Token") 183 storageAuth, err := autorest.NewSASTokenAuthorizer(c.sasToken) 184 if err != nil { 185 return nil, fmt.Errorf("Error building Authorizer: %+v", err) 186 } 187 188 containersClient := containers.NewWithEnvironment(c.environment) 189 c.configureClient(&containersClient.Client, storageAuth) 190 return &containersClient, nil 191 } 192 193 if c.azureAdStorageAuth != nil { 194 containersClient := containers.NewWithEnvironment(c.environment) 195 c.configureClient(&containersClient.Client, *c.azureAdStorageAuth) 196 return &containersClient, nil 197 } 198 199 accessKey := c.accessKey 200 if accessKey == "" { 201 log.Printf("[DEBUG] Building the Container Client from an Access Token (using user credentials)") 202 keys, err := c.storageAccountsClient.ListKeys(ctx, c.resourceGroupName, c.storageAccountName, "") 203 if err != nil { 204 return nil, fmt.Errorf("Error retrieving keys for Storage Account %q: %s", c.storageAccountName, err) 205 } 206 207 if keys.Keys == nil { 208 return nil, fmt.Errorf("Nil key returned for storage account %q", c.storageAccountName) 209 } 210 211 accessKeys := *keys.Keys 212 accessKey = *accessKeys[0].Value 213 } 214 215 storageAuth, err := autorest.NewSharedKeyAuthorizer(c.storageAccountName, accessKey, autorest.SharedKey) 216 if err != nil { 217 return nil, fmt.Errorf("Error building Authorizer: %+v", err) 218 } 219 220 containersClient := containers.NewWithEnvironment(c.environment) 221 c.configureClient(&containersClient.Client, storageAuth) 222 return &containersClient, nil 223 } 224 225 func (c *ArmClient) configureClient(client *autorest.Client, auth autorest.Authorizer) { 226 client.UserAgent = buildUserAgent() 227 client.Authorizer = auth 228 client.Sender = buildSender() 229 client.SkipResourceProviderRegistration = false 230 client.PollingDuration = 60 * time.Minute 231 } 232 233 func buildUserAgent() string { 234 userAgent := httpclient.UserAgentString() 235 236 // append the CloudShell version to the user agent if it exists 237 if azureAgent := os.Getenv("AZURE_HTTP_USER_AGENT"); azureAgent != "" { 238 userAgent = fmt.Sprintf("%s %s", userAgent, azureAgent) 239 } 240 241 return userAgent 242 }