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  }