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