From 6d4f70246729463d14c4b438233d11f579eb635f Mon Sep 17 00:00:00 2001 From: Tom Harvey Date: Mon, 26 Nov 2018 14:42:16 +0100 Subject: [PATCH] backend/azurerm: support for custom resource manager endpoints (#19460) * backend/azurerm: removing the `arm_` prefix from keys * removing the deprecated fields test because the deprecation makes it fail * authentication: support for custom resource manager endpoints * Adding debug prefixes to the log statements --- backend/remote-state/azure/arm_client.go | 34 ++++++++----- backend/remote-state/azure/backend.go | 53 ++++++++++++--------- backend/remote-state/azure/backend_test.go | 44 +++++++++++++++++ backend/remote-state/azure/client_test.go | 8 ++++ backend/remote-state/azure/helpers_test.go | 32 ++++++++----- website/docs/backends/types/azurerm.html.md | 4 ++ 6 files changed, 128 insertions(+), 47 deletions(-) diff --git a/backend/remote-state/azure/arm_client.go b/backend/remote-state/azure/arm_client.go index 4393a66e3..20814a0ee 100644 --- a/backend/remote-state/azure/arm_client.go +++ b/backend/remote-state/azure/arm_client.go @@ -32,10 +32,11 @@ type ArmClient struct { } func buildArmClient(config BackendConfig) (*ArmClient, error) { - env, err := authentication.DetermineEnvironment(config.Environment) + env, err := buildArmEnvironment(config) if err != nil { return nil, err } + client := ArmClient{ environment: *env, resourceGroupName: config.ResourceGroupName, @@ -55,12 +56,13 @@ func buildArmClient(config BackendConfig) (*ArmClient, error) { } builder := authentication.Builder{ - ClientID: config.ClientID, - ClientSecret: config.ClientSecret, - SubscriptionID: config.SubscriptionID, - TenantID: config.TenantID, - Environment: config.Environment, - MsiEndpoint: config.MsiEndpoint, + ClientID: config.ClientID, + ClientSecret: config.ClientSecret, + SubscriptionID: config.SubscriptionID, + TenantID: config.TenantID, + CustomResourceManagerEndpoint: config.CustomResourceManagerEndpoint, + Environment: config.Environment, + MsiEndpoint: config.MsiEndpoint, // Feature Toggles SupportsClientSecretAuth: true, @@ -77,7 +79,7 @@ func buildArmClient(config BackendConfig) (*ArmClient, error) { return nil, err } - auth, err := armConfig.GetAuthorizationToken(oauthConfig, env.ResourceManagerEndpoint) + auth, err := armConfig.GetAuthorizationToken(oauthConfig, env.TokenAudience) if err != nil { return nil, err } @@ -93,9 +95,19 @@ func buildArmClient(config BackendConfig) (*ArmClient, error) { return &client, nil } +func buildArmEnvironment(config BackendConfig) (*azure.Environment, error) { + if config.CustomResourceManagerEndpoint != "" { + log.Printf("[DEBUG] Loading Environment from Endpoint %q", config.CustomResourceManagerEndpoint) + return authentication.LoadEnvironmentFromUrl(config.CustomResourceManagerEndpoint) + } + + log.Printf("[DEBUG] Loading Environment %q", config.Environment) + return authentication.DetermineEnvironment(config.Environment) +} + func (c ArmClient) getBlobClient(ctx context.Context) (*storage.BlobStorageClient, error) { if c.accessKey != "" { - log.Printf("Building the Blob Client from an Access Token") + log.Printf("[DEBUG] Building the Blob Client from an Access Token") storageClient, err := storage.NewBasicClientOnSovereignCloud(c.storageAccountName, c.accessKey, c.environment) if err != nil { return nil, fmt.Errorf("Error creating storage client for storage account %q: %s", c.storageAccountName, err) @@ -105,7 +117,7 @@ func (c ArmClient) getBlobClient(ctx context.Context) (*storage.BlobStorageClien } if c.sasToken != "" { - log.Printf("Building the Blob Client from a SAS Token") + log.Printf("[DEBUG] Building the Blob Client from a SAS Token") token := strings.TrimPrefix(c.sasToken, "?") uri, err := url.ParseQuery(token) if err != nil { @@ -117,7 +129,7 @@ func (c ArmClient) getBlobClient(ctx context.Context) (*storage.BlobStorageClien return &client, nil } - log.Printf("Building the Blob Client from an Access Token (using user credentials)") + log.Printf("[DEBUG] Building the Blob Client from an Access Token (using user credentials)") keys, err := c.storageAccountsClient.ListKeys(ctx, c.resourceGroupName, c.storageAccountName) if err != nil { return nil, fmt.Errorf("Error retrieving keys for Storage Account %q: %s", c.storageAccountName, err) diff --git a/backend/remote-state/azure/backend.go b/backend/remote-state/azure/backend.go index 0599db420..a2135c441 100644 --- a/backend/remote-state/azure/backend.go +++ b/backend/remote-state/azure/backend.go @@ -99,6 +99,13 @@ func New() backend.Backend { DefaultFunc: schema.EnvDefaultFunc("ARM_MSI_ENDPOINT", ""), }, + "endpoint": { + Type: schema.TypeString, + Optional: true, + Description: "A custom Endpoint used to access the Azure Resource Manager API's.", + DefaultFunc: schema.EnvDefaultFunc("ARM_ENDPOINT", ""), + }, + // Deprecated fields "arm_client_id": { Type: schema.TypeString, @@ -127,8 +134,6 @@ func New() backend.Backend { Description: "The Tenant ID.", Deprecated: "`arm_tenant_id` has been replaced by `tenant_id`", }, - - // TODO: support for custom resource manager endpoints }, } @@ -151,16 +156,17 @@ type BackendConfig struct { StorageAccountName string // Optional - AccessKey string - ClientID string - ClientSecret string - Environment string - MsiEndpoint string - ResourceGroupName string - SasToken string - SubscriptionID string - TenantID string - UseMsi bool + AccessKey string + ClientID string + ClientSecret string + CustomResourceManagerEndpoint string + Environment string + MsiEndpoint string + ResourceGroupName string + SasToken string + SubscriptionID string + TenantID string + UseMsi bool } func (b *Backend) configure(ctx context.Context) error { @@ -180,17 +186,18 @@ func (b *Backend) configure(ctx context.Context) error { tenantId := valueFromDeprecatedField(data, "tenant_id", "arm_tenant_id") config := BackendConfig{ - AccessKey: data.Get("access_key").(string), - ClientID: clientId, - ClientSecret: clientSecret, - Environment: data.Get("environment").(string), - MsiEndpoint: data.Get("msi_endpoint").(string), - ResourceGroupName: data.Get("resource_group_name").(string), - SasToken: data.Get("sas_token").(string), - StorageAccountName: data.Get("storage_account_name").(string), - SubscriptionID: subscriptionId, - TenantID: tenantId, - UseMsi: data.Get("use_msi").(bool), + AccessKey: data.Get("access_key").(string), + ClientID: clientId, + ClientSecret: clientSecret, + CustomResourceManagerEndpoint: data.Get("endpoint").(string), + Environment: data.Get("environment").(string), + MsiEndpoint: data.Get("msi_endpoint").(string), + ResourceGroupName: data.Get("resource_group_name").(string), + SasToken: data.Get("sas_token").(string), + StorageAccountName: data.Get("storage_account_name").(string), + SubscriptionID: subscriptionId, + TenantID: tenantId, + UseMsi: data.Get("use_msi").(bool), } armClient, err := buildArmClient(config) diff --git a/backend/remote-state/azure/backend_test.go b/backend/remote-state/azure/backend_test.go index 619b0d78d..09ce9e8e2 100644 --- a/backend/remote-state/azure/backend_test.go +++ b/backend/remote-state/azure/backend_test.go @@ -55,6 +55,7 @@ func TestBackendAccessKeyBasic(t *testing.T) { "key": res.storageKeyName, "access_key": res.storageAccountAccessKey, "environment": os.Getenv("ARM_ENVIRONMENT"), + "endpoint": os.Getenv("ARM_ENDPOINT"), })).(*Backend) backend.TestBackendStates(t, b) @@ -82,6 +83,7 @@ func TestBackendManagedServiceIdentityBasic(t *testing.T) { "subscription_id": os.Getenv("ARM_SUBSCRIPTION_ID"), "tenant_id": os.Getenv("ARM_TENANT_ID"), "environment": os.Getenv("ARM_ENVIRONMENT"), + "endpoint": os.Getenv("ARM_ENDPOINT"), })).(*Backend) backend.TestBackendStates(t, b) @@ -111,6 +113,7 @@ func TestBackendSASTokenBasic(t *testing.T) { "key": res.storageKeyName, "sas_token": *sasToken, "environment": os.Getenv("ARM_ENVIRONMENT"), + "endpoint": os.Getenv("ARM_ENDPOINT"), })).(*Backend) backend.TestBackendStates(t, b) @@ -139,6 +142,43 @@ func TestBackendServicePrincipalBasic(t *testing.T) { "client_id": os.Getenv("ARM_CLIENT_ID"), "client_secret": os.Getenv("ARM_CLIENT_SECRET"), "environment": os.Getenv("ARM_ENVIRONMENT"), + "endpoint": os.Getenv("ARM_ENDPOINT"), + })).(*Backend) + + backend.TestBackendStates(t, b) +} + +func TestBackendServicePrincipalCustomEndpoint(t *testing.T) { + testAccAzureBackend(t) + + // this is only applicable for Azure Stack. + endpoint := os.Getenv("ARM_ENDPOINT") + if endpoint == "" { + t.Skip("Skipping as ARM_ENDPOINT isn't configured") + } + + rs := acctest.RandString(4) + res := testResourceNames(rs, "testState") + armClient := buildTestClient(t, res) + + ctx := context.TODO() + err := armClient.buildTestResources(ctx, &res) + defer armClient.destroyTestResources(ctx, res) + if err != nil { + t.Fatalf("Error creating Test Resources: %q", err) + } + + b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{ + "storage_account_name": res.storageAccountName, + "container_name": res.storageContainerName, + "key": res.storageKeyName, + "resource_group_name": res.resourceGroup, + "subscription_id": os.Getenv("ARM_SUBSCRIPTION_ID"), + "tenant_id": os.Getenv("ARM_TENANT_ID"), + "client_id": os.Getenv("ARM_CLIENT_ID"), + "client_secret": os.Getenv("ARM_CLIENT_SECRET"), + "environment": os.Getenv("ARM_ENVIRONMENT"), + "endpoint": endpoint, })).(*Backend) backend.TestBackendStates(t, b) @@ -163,6 +203,7 @@ func TestBackendAccessKeyLocked(t *testing.T) { "key": res.storageKeyName, "access_key": res.storageAccountAccessKey, "environment": os.Getenv("ARM_ENVIRONMENT"), + "endpoint": os.Getenv("ARM_ENDPOINT"), })).(*Backend) b2 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{ @@ -171,6 +212,7 @@ func TestBackendAccessKeyLocked(t *testing.T) { "key": res.storageKeyName, "access_key": res.storageAccountAccessKey, "environment": os.Getenv("ARM_ENVIRONMENT"), + "endpoint": os.Getenv("ARM_ENDPOINT"), })).(*Backend) backend.TestBackendStateLocks(t, b1, b2) @@ -200,6 +242,7 @@ func TestBackendServicePrincipalLocked(t *testing.T) { "client_id": os.Getenv("ARM_CLIENT_ID"), "client_secret": os.Getenv("ARM_CLIENT_SECRET"), "environment": os.Getenv("ARM_ENVIRONMENT"), + "endpoint": os.Getenv("ARM_ENDPOINT"), })).(*Backend) b2 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{ @@ -212,6 +255,7 @@ func TestBackendServicePrincipalLocked(t *testing.T) { "client_id": os.Getenv("ARM_CLIENT_ID"), "client_secret": os.Getenv("ARM_CLIENT_SECRET"), "environment": os.Getenv("ARM_ENVIRONMENT"), + "endpoint": os.Getenv("ARM_ENDPOINT"), })).(*Backend) backend.TestBackendStateLocks(t, b1, b2) diff --git a/backend/remote-state/azure/client_test.go b/backend/remote-state/azure/client_test.go index c6d0c0eef..caee66dea 100644 --- a/backend/remote-state/azure/client_test.go +++ b/backend/remote-state/azure/client_test.go @@ -35,6 +35,7 @@ func TestRemoteClientAccessKeyBasic(t *testing.T) { "key": res.storageKeyName, "access_key": res.storageAccountAccessKey, "environment": os.Getenv("ARM_ENVIRONMENT"), + "endpoint": os.Getenv("ARM_ENDPOINT"), })).(*Backend) state, err := b.StateMgr(backend.DefaultStateName) @@ -67,6 +68,7 @@ func TestRemoteClientManagedServiceIdentityBasic(t *testing.T) { "subscription_id": os.Getenv("ARM_SUBSCRIPTION_ID"), "tenant_id": os.Getenv("ARM_TENANT_ID"), "environment": os.Getenv("ARM_ENVIRONMENT"), + "endpoint": os.Getenv("ARM_ENDPOINT"), })).(*Backend) state, err := b.StateMgr(backend.DefaultStateName) @@ -101,6 +103,7 @@ func TestRemoteClientSasTokenBasic(t *testing.T) { "key": res.storageKeyName, "sas_token": *sasToken, "environment": os.Getenv("ARM_ENVIRONMENT"), + "endpoint": os.Getenv("ARM_ENDPOINT"), })).(*Backend) state, err := b.StateMgr(backend.DefaultStateName) @@ -134,6 +137,7 @@ func TestRemoteClientServicePrincipalBasic(t *testing.T) { "client_id": os.Getenv("ARM_CLIENT_ID"), "client_secret": os.Getenv("ARM_CLIENT_SECRET"), "environment": os.Getenv("ARM_ENVIRONMENT"), + "endpoint": os.Getenv("ARM_ENDPOINT"), })).(*Backend) state, err := b.StateMgr(backend.DefaultStateName) @@ -163,6 +167,7 @@ func TestRemoteClientAccessKeyLocks(t *testing.T) { "key": res.storageKeyName, "access_key": res.storageAccountAccessKey, "environment": os.Getenv("ARM_ENVIRONMENT"), + "endpoint": os.Getenv("ARM_ENDPOINT"), })).(*Backend) b2 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{ @@ -171,6 +176,7 @@ func TestRemoteClientAccessKeyLocks(t *testing.T) { "key": res.storageKeyName, "access_key": res.storageAccountAccessKey, "environment": os.Getenv("ARM_ENVIRONMENT"), + "endpoint": os.Getenv("ARM_ENDPOINT"), })).(*Backend) s1, err := b1.StateMgr(backend.DefaultStateName) @@ -209,6 +215,7 @@ func TestRemoteClientServicePrincipalLocks(t *testing.T) { "client_id": os.Getenv("ARM_CLIENT_ID"), "client_secret": os.Getenv("ARM_CLIENT_SECRET"), "environment": os.Getenv("ARM_ENVIRONMENT"), + "endpoint": os.Getenv("ARM_ENDPOINT"), })).(*Backend) b2 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{ @@ -221,6 +228,7 @@ func TestRemoteClientServicePrincipalLocks(t *testing.T) { "client_id": os.Getenv("ARM_CLIENT_ID"), "client_secret": os.Getenv("ARM_CLIENT_SECRET"), "environment": os.Getenv("ARM_ENVIRONMENT"), + "endpoint": os.Getenv("ARM_ENDPOINT"), })).(*Backend) s1, err := b1.StateMgr(backend.DefaultStateName) diff --git a/backend/remote-state/azure/helpers_test.go b/backend/remote-state/azure/helpers_test.go index ce35c32d9..932b63849 100644 --- a/backend/remote-state/azure/helpers_test.go +++ b/backend/remote-state/azure/helpers_test.go @@ -16,7 +16,8 @@ import ( ) const ( - sasSignedVersion = "2017-07-29" + // required for Azure Stack + sasSignedVersion = "2015-04-05" ) // verify that we are doing ACC tests or the Azure tests specifically @@ -45,9 +46,6 @@ func buildTestClient(t *testing.T, res resourceNames) *ArmClient { msiEnabled := strings.EqualFold(os.Getenv("ARM_USE_MSI"), "true") environment := os.Getenv("ARM_ENVIRONMENT") - // location isn't used in this method, but is in the other test methods - location := os.Getenv("ARM_LOCATION") - hasCredentials := (clientID != "" && clientSecret != "") || msiEnabled if !hasCredentials { t.Fatal("Azure credentials missing or incomplete") @@ -65,19 +63,25 @@ func buildTestClient(t *testing.T, res resourceNames) *ArmClient { t.Fatalf("Missing ARM_ENVIRONMENT") } + // location isn't used in this method, but is in the other test methods + location := os.Getenv("ARM_LOCATION") if location == "" { t.Fatalf("Missing ARM_LOCATION") } + // Endpoint is optional (only for Stack) + endpoint := os.Getenv("ARM_ENDPOINT") + armClient, err := buildArmClient(BackendConfig{ - SubscriptionID: subscriptionID, - TenantID: tenantID, - ClientID: clientID, - ClientSecret: clientSecret, - Environment: environment, - ResourceGroupName: res.resourceGroup, - StorageAccountName: res.storageAccountName, - UseMsi: msiEnabled, + SubscriptionID: subscriptionID, + TenantID: tenantID, + ClientID: clientID, + ClientSecret: clientSecret, + CustomResourceManagerEndpoint: endpoint, + Environment: environment, + ResourceGroupName: res.resourceGroup, + StorageAccountName: res.storageAccountName, + UseMsi: msiEnabled, }) if err != nil { t.Fatalf("Failed to build ArmClient: %+v", err) @@ -99,7 +103,9 @@ func buildSasToken(accountName, accessKey string) (*string, error) { signedVersion := sasSignedVersion utcNow := time.Now().UTC() - startDate := utcNow.Format(time.RFC3339) + + // account for servers being up to 5 minutes out + startDate := utcNow.Add(time.Minute * -5).Format(time.RFC3339) endDate := utcNow.Add(time.Hour * 24).Format(time.RFC3339) sasToken, err := sasStorage.ComputeSASToken(accountName, accessKey, permissions, services, resourceTypes, diff --git a/website/docs/backends/types/azurerm.html.md b/website/docs/backends/types/azurerm.html.md index 04fb2d2eb..e852d29fa 100644 --- a/website/docs/backends/types/azurerm.html.md +++ b/website/docs/backends/types/azurerm.html.md @@ -152,6 +152,10 @@ The following configuration options are supported: * `environment` - (Optional) The Azure Environment which should be used. This can also be sourced from the `ARM_ENVIRONMENT` environment variable. Possible values are `public`, `china`, `german`, `stack` and `usgovernment`. Defaults to `public`. +* `endpoint` - (Optional) The Custom Endpoint for Azure Resource Manager. This can also be sourced from the `ARM_ENDPOINT` environment variable. + +~> **NOTE:** An `endpoint` should only be configured when using Azure Stack. + --- When authenticating using the Managed Service Identity (MSI) - the following fields are also supported: