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