opentofu/internal/backend/remote-state/azure/arm_client.go
Martin Atkins 4c254cc2be Move httpclient/ to internal/httpclient/
This is part of a general effort to move all of Terraform's non-library
package surface under internal in order to reinforce that these are for
internal use within Terraform only.

If you were previously importing packages under this prefix into an
external codebase, you could pin to an earlier release tag as an interim
solution until you've make a plan to achieve the same functionality some
other way.
2021-05-17 14:09:07 -07:00

243 lines
8.0 KiB
Go

package azure
import (
"context"
"fmt"
"log"
"os"
"time"
"github.com/tombuildsstuff/giovanni/storage/2018-11-09/blob/blobs"
"github.com/tombuildsstuff/giovanni/storage/2018-11-09/blob/containers"
"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"
)
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://www.terraform.io/docs/providers/azurerm/guides/service_principal_client_secret.html",
// Service Principal (Client Certificate)
ClientCertPassword: config.ClientCertificatePassword,
ClientCertPath: config.ClientCertificatePath,
// Service Principal (Client Secret)
ClientSecret: config.ClientSecret,
// Managed Service Identity
MsiEndpoint: config.MsiEndpoint,
// Feature Toggles
SupportsAzureCliToken: true,
SupportsClientCertAuth: true,
SupportsClientSecretAuth: true,
SupportsManagedServiceIdentity: config.UseMsi,
}
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
}
sender := sender.BuildSender("backend/remote-state/azure")
auth, err := armConfig.GetAuthorizationToken(sender, oauthConfig, env.TokenAudience)
if err != nil {
return nil, err
}
if config.UseAzureADAuthentication {
storageAuth, err := armConfig.GetAuthorizationToken(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 buildArmEnvironment(config BackendConfig) (*azure.Environment, error) {
// TODO: can we remove this?
// https://github.com/hashicorp/terraform/issues/27156
if config.CustomResourceManagerEndpoint != "" {
log.Printf("[DEBUG] Loading Environment from Endpoint %q", config.CustomResourceManagerEndpoint)
return authentication.LoadEnvironmentFromUrl(config.CustomResourceManagerEndpoint)
}
log.Printf("[DEBUG] Loading Environment %q", config.Environment)
return authentication.DetermineEnvironment(config.Environment)
}
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.UserAgentString()
// 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
}