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  }