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