2018-11-21 22:06:03 +01:00
|
|
|
package azure
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
2018-11-22 18:02:33 +01:00
|
|
|
"log"
|
2018-11-21 22:06:03 +01:00
|
|
|
"os"
|
|
|
|
"time"
|
|
|
|
|
2021-11-17 18:40:21 +01:00
|
|
|
"github.com/manicminer/hamilton/environments"
|
|
|
|
|
2020-05-20 17:29:02 +02:00
|
|
|
"github.com/tombuildsstuff/giovanni/storage/2018-11-09/blob/blobs"
|
|
|
|
"github.com/tombuildsstuff/giovanni/storage/2018-11-09/blob/containers"
|
|
|
|
|
2018-11-21 22:06:03 +01:00
|
|
|
"github.com/Azure/azure-sdk-for-go/profiles/2017-03-09/resources/mgmt/resources"
|
2021-03-22 18:49:34 +01:00
|
|
|
armStorage "github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2021-01-01/storage"
|
2018-11-21 22:06:03 +01:00
|
|
|
"github.com/Azure/go-autorest/autorest"
|
|
|
|
"github.com/Azure/go-autorest/autorest/azure"
|
|
|
|
"github.com/hashicorp/go-azure-helpers/authentication"
|
2019-11-26 01:03:57 +01:00
|
|
|
"github.com/hashicorp/go-azure-helpers/sender"
|
2021-05-17 18:54:53 +02:00
|
|
|
"github.com/hashicorp/terraform/internal/httpclient"
|
2018-11-21 22:06:03 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
type ArmClient struct {
|
|
|
|
// These Clients are only initialized if an Access Key isn't provided
|
|
|
|
groupsClient *resources.GroupsClient
|
|
|
|
storageAccountsClient *armStorage.AccountsClient
|
2020-05-20 17:29:02 +02:00
|
|
|
containersClient *containers.Client
|
|
|
|
blobsClient *blobs.Client
|
2018-11-21 22:06:03 +01:00
|
|
|
|
2021-03-22 18:15:41 +01:00
|
|
|
// azureAdStorageAuth is only here if we're using AzureAD Authentication but is an Authorizer for Storage
|
|
|
|
azureAdStorageAuth *autorest.Authorizer
|
|
|
|
|
2018-11-21 22:06:03 +01:00
|
|
|
accessKey string
|
|
|
|
environment azure.Environment
|
|
|
|
resourceGroupName string
|
|
|
|
storageAccountName string
|
2018-11-22 18:02:33 +01:00
|
|
|
sasToken string
|
2018-11-21 22:06:03 +01:00
|
|
|
}
|
|
|
|
|
2020-10-02 01:34:51 +02:00
|
|
|
func buildArmClient(ctx context.Context, config BackendConfig) (*ArmClient, error) {
|
|
|
|
env, err := authentication.AzureEnvironmentByNameFromEndpoint(ctx, config.MetadataHost, config.Environment)
|
2018-11-21 22:06:03 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-11-26 14:42:16 +01:00
|
|
|
|
2018-11-21 22:06:03 +01:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2018-11-22 18:02:33 +01:00
|
|
|
// likewise with a SAS token
|
|
|
|
if config.SasToken != "" {
|
|
|
|
client.sasToken = config.SasToken
|
|
|
|
return &client, nil
|
|
|
|
}
|
|
|
|
|
2018-11-21 22:06:03 +01:00
|
|
|
builder := authentication.Builder{
|
2018-11-26 14:42:16 +01:00
|
|
|
ClientID: config.ClientID,
|
|
|
|
SubscriptionID: config.SubscriptionID,
|
|
|
|
TenantID: config.TenantID,
|
|
|
|
CustomResourceManagerEndpoint: config.CustomResourceManagerEndpoint,
|
2020-10-27 19:43:22 +01:00
|
|
|
MetadataHost: config.MetadataHost,
|
2018-11-26 14:42:16 +01:00
|
|
|
Environment: config.Environment,
|
2021-11-17 18:28:47 +01:00
|
|
|
ClientSecretDocsLink: "https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/guides/service_principal_client_secret",
|
2020-08-07 11:58:33 +02:00
|
|
|
|
|
|
|
// Service Principal (Client Certificate)
|
|
|
|
ClientCertPassword: config.ClientCertificatePassword,
|
|
|
|
ClientCertPath: config.ClientCertificatePath,
|
|
|
|
|
|
|
|
// Service Principal (Client Secret)
|
|
|
|
ClientSecret: config.ClientSecret,
|
|
|
|
|
|
|
|
// Managed Service Identity
|
|
|
|
MsiEndpoint: config.MsiEndpoint,
|
2018-11-21 22:06:03 +01:00
|
|
|
|
|
|
|
// Feature Toggles
|
2018-12-10 22:23:30 +01:00
|
|
|
SupportsAzureCliToken: true,
|
2020-08-07 11:58:33 +02:00
|
|
|
SupportsClientCertAuth: true,
|
2018-11-22 16:52:27 +01:00
|
|
|
SupportsClientSecretAuth: true,
|
|
|
|
SupportsManagedServiceIdentity: config.UseMsi,
|
2021-11-17 18:40:21 +01:00
|
|
|
UseMicrosoftGraph: config.UseMicrosoftGraph,
|
2018-11-21 22:06:03 +01:00
|
|
|
}
|
|
|
|
armConfig, err := builder.Build()
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("Error building ARM Config: %+v", err)
|
|
|
|
}
|
|
|
|
|
2019-11-26 01:03:57 +01:00
|
|
|
oauthConfig, err := armConfig.BuildOAuthConfig(env.ActiveDirectoryEndpoint)
|
2018-11-21 22:06:03 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2021-11-17 18:40:21 +01:00
|
|
|
hamiltonEnv, err := environments.EnvironmentFromString(config.Environment)
|
2018-11-21 22:06:03 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2021-11-17 18:40:21 +01:00
|
|
|
sender := sender.BuildSender("backend/remote-state/azure")
|
|
|
|
var auth autorest.Authorizer
|
|
|
|
if builder.UseMicrosoftGraph {
|
|
|
|
log.Printf("[DEBUG] Obtaining a MSAL / Microsoft Graph token for Resource Manager..")
|
|
|
|
auth, err = armConfig.GetMSALToken(ctx, hamiltonEnv.ResourceManager, sender, oauthConfig, env.TokenAudience)
|
2021-03-22 18:15:41 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2021-11-17 18:40:21 +01:00
|
|
|
} else {
|
|
|
|
log.Printf("[DEBUG] Obtaining a ADAL / Azure Active Directory Graph token for Resource Manager..")
|
|
|
|
auth, err = armConfig.GetADALToken(ctx, sender, oauthConfig, env.TokenAudience)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if config.UseAzureADAuthentication {
|
|
|
|
if builder.UseMicrosoftGraph {
|
|
|
|
log.Printf("[DEBUG] Obtaining a MSAL / Microsoft Graph token for Storage..")
|
|
|
|
storageAuth, err := armConfig.GetMSALToken(ctx, hamiltonEnv.Storage, sender, oauthConfig, env.ResourceIdentifiers.Storage)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
client.azureAdStorageAuth = &storageAuth
|
|
|
|
} else {
|
|
|
|
log.Printf("[DEBUG] Obtaining a ADAL / Azure Active Directory Graph token for Storage..")
|
|
|
|
storageAuth, err := armConfig.GetADALToken(ctx, sender, oauthConfig, env.ResourceIdentifiers.Storage)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
client.azureAdStorageAuth = &storageAuth
|
|
|
|
}
|
2021-03-22 18:15:41 +01:00
|
|
|
}
|
|
|
|
|
2018-11-21 22:06:03 +01:00
|
|
|
accountsClient := armStorage.NewAccountsClientWithBaseURI(env.ResourceManagerEndpoint, armConfig.SubscriptionID)
|
2019-08-20 16:50:20 +02:00
|
|
|
client.configureClient(&accountsClient.Client, auth)
|
2018-11-21 22:06:03 +01:00
|
|
|
client.storageAccountsClient = &accountsClient
|
|
|
|
|
|
|
|
groupsClient := resources.NewGroupsClientWithBaseURI(env.ResourceManagerEndpoint, armConfig.SubscriptionID)
|
2019-08-20 16:50:20 +02:00
|
|
|
client.configureClient(&groupsClient.Client, auth)
|
2018-11-21 22:06:03 +01:00
|
|
|
client.groupsClient = &groupsClient
|
|
|
|
|
|
|
|
return &client, nil
|
|
|
|
}
|
|
|
|
|
2020-05-20 17:29:02 +02:00
|
|
|
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)
|
2018-11-21 22:06:03 +01:00
|
|
|
if err != nil {
|
2020-05-20 17:29:02 +02:00
|
|
|
return nil, fmt.Errorf("Error building Authorizer: %+v", err)
|
2018-11-21 22:06:03 +01:00
|
|
|
}
|
2020-05-20 17:29:02 +02:00
|
|
|
|
|
|
|
blobsClient := blobs.NewWithEnvironment(c.environment)
|
|
|
|
c.configureClient(&blobsClient.Client, storageAuth)
|
|
|
|
return &blobsClient, nil
|
2018-11-21 22:06:03 +01:00
|
|
|
}
|
|
|
|
|
2021-03-22 18:15:41 +01:00
|
|
|
if c.azureAdStorageAuth != nil {
|
|
|
|
blobsClient := blobs.NewWithEnvironment(c.environment)
|
|
|
|
c.configureClient(&blobsClient.Client, *c.azureAdStorageAuth)
|
|
|
|
return &blobsClient, nil
|
|
|
|
}
|
|
|
|
|
2020-05-20 17:29:02 +02:00
|
|
|
accessKey := c.accessKey
|
|
|
|
if accessKey == "" {
|
|
|
|
log.Printf("[DEBUG] Building the Blob Client from an Access Token (using user credentials)")
|
2021-03-22 18:49:34 +01:00
|
|
|
keys, err := c.storageAccountsClient.ListKeys(ctx, c.resourceGroupName, c.storageAccountName, "")
|
2018-11-22 18:02:33 +01:00
|
|
|
if err != nil {
|
2020-05-20 17:29:02 +02:00
|
|
|
return nil, fmt.Errorf("Error retrieving keys for Storage Account %q: %s", c.storageAccountName, err)
|
2018-11-22 18:02:33 +01:00
|
|
|
}
|
|
|
|
|
2020-05-20 17:29:02 +02:00
|
|
|
if keys.Keys == nil {
|
|
|
|
return nil, fmt.Errorf("Nil key returned for storage account %q", c.storageAccountName)
|
|
|
|
}
|
|
|
|
|
|
|
|
accessKeys := *keys.Keys
|
|
|
|
accessKey = *accessKeys[0].Value
|
2018-11-22 18:02:33 +01:00
|
|
|
}
|
|
|
|
|
2020-05-20 17:29:02 +02:00
|
|
|
storageAuth, err := autorest.NewSharedKeyAuthorizer(c.storageAccountName, accessKey, autorest.SharedKey)
|
2018-11-21 22:06:03 +01:00
|
|
|
if err != nil {
|
2020-05-20 17:29:02 +02:00
|
|
|
return nil, fmt.Errorf("Error building Authorizer: %+v", err)
|
2018-11-21 22:06:03 +01:00
|
|
|
}
|
|
|
|
|
2020-05-20 17:29:02 +02:00
|
|
|
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
|
2018-11-21 22:06:03 +01:00
|
|
|
}
|
2021-03-22 18:15:41 +01:00
|
|
|
|
|
|
|
if c.azureAdStorageAuth != nil {
|
|
|
|
containersClient := containers.NewWithEnvironment(c.environment)
|
|
|
|
c.configureClient(&containersClient.Client, *c.azureAdStorageAuth)
|
|
|
|
return &containersClient, nil
|
|
|
|
}
|
|
|
|
|
2020-05-20 17:29:02 +02:00
|
|
|
accessKey := c.accessKey
|
|
|
|
if accessKey == "" {
|
|
|
|
log.Printf("[DEBUG] Building the Container Client from an Access Token (using user credentials)")
|
2021-03-22 18:49:34 +01:00
|
|
|
keys, err := c.storageAccountsClient.ListKeys(ctx, c.resourceGroupName, c.storageAccountName, "")
|
2020-05-20 17:29:02 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("Error retrieving keys for Storage Account %q: %s", c.storageAccountName, err)
|
|
|
|
}
|
2018-11-21 22:06:03 +01:00
|
|
|
|
2020-05-20 17:29:02 +02:00
|
|
|
if keys.Keys == nil {
|
|
|
|
return nil, fmt.Errorf("Nil key returned for storage account %q", c.storageAccountName)
|
|
|
|
}
|
|
|
|
|
|
|
|
accessKeys := *keys.Keys
|
|
|
|
accessKey = *accessKeys[0].Value
|
|
|
|
}
|
2018-11-21 22:06:03 +01:00
|
|
|
|
2020-05-20 17:29:02 +02:00
|
|
|
storageAuth, err := autorest.NewSharedKeyAuthorizer(c.storageAccountName, accessKey, autorest.SharedKey)
|
2018-11-21 22:06:03 +01:00
|
|
|
if err != nil {
|
2020-05-20 17:29:02 +02:00
|
|
|
return nil, fmt.Errorf("Error building Authorizer: %+v", err)
|
2018-11-21 22:06:03 +01:00
|
|
|
}
|
2020-05-20 17:29:02 +02:00
|
|
|
|
|
|
|
containersClient := containers.NewWithEnvironment(c.environment)
|
|
|
|
c.configureClient(&containersClient.Client, storageAuth)
|
|
|
|
return &containersClient, nil
|
2018-11-21 22:06:03 +01:00
|
|
|
}
|
|
|
|
|
2019-08-20 16:50:20 +02:00
|
|
|
func (c *ArmClient) configureClient(client *autorest.Client, auth autorest.Authorizer) {
|
2018-11-21 22:06:03 +01:00
|
|
|
client.UserAgent = buildUserAgent()
|
|
|
|
client.Authorizer = auth
|
2019-08-20 16:50:20 +02:00
|
|
|
client.Sender = buildSender()
|
2018-11-21 22:06:03 +01:00
|
|
|
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
|
|
|
|
}
|