backend/azurerm: support for authenticating via msi (#19433)

* backend/azurerm: support for authenticating via msi

* adding acceptance tests for msi auth

* including the resource group name in the tests

* support for using the test client via msi
This commit is contained in:
Tom Harvey 2018-11-22 16:52:27 +01:00 committed by GitHub
parent 9f15381fec
commit c928962f44
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 156 additions and 3 deletions

View File

@ -50,10 +50,12 @@ func buildArmClient(config BackendConfig) (*ArmClient, error) {
SubscriptionID: config.SubscriptionID, SubscriptionID: config.SubscriptionID,
TenantID: config.TenantID, TenantID: config.TenantID,
Environment: config.Environment, Environment: config.Environment,
MsiEndpoint: config.MsiEndpoint,
// Feature Toggles // Feature Toggles
SupportsClientSecretAuth: true, SupportsClientSecretAuth: true,
// TODO: support for Azure CLI / Client Certificate / MSI SupportsManagedServiceIdentity: config.UseMsi,
// TODO: support for Azure CLI / Client Certificate auth
} }
armConfig, err := builder.Build() armConfig, err := builder.Build()
if err != nil { if err != nil {

View File

@ -78,6 +78,20 @@ func New() backend.Backend {
DefaultFunc: schema.EnvDefaultFunc("ARM_TENANT_ID", ""), DefaultFunc: schema.EnvDefaultFunc("ARM_TENANT_ID", ""),
}, },
"use_msi": {
Type: schema.TypeBool,
Optional: true,
Description: "Should Managed Service Identity be used?.",
DefaultFunc: schema.EnvDefaultFunc("ARM_USE_MSI", false),
},
"msi_endpoint": {
Type: schema.TypeString,
Optional: true,
Description: "The Managed Service Identity Endpoint.",
DefaultFunc: schema.EnvDefaultFunc("ARM_MSI_ENDPOINT", ""),
},
// TODO: rename these fields // TODO: rename these fields
// TODO: support for custom resource manager endpoints // TODO: support for custom resource manager endpoints
}, },
@ -106,9 +120,11 @@ type BackendConfig struct {
ClientID string ClientID string
ClientSecret string ClientSecret string
Environment string Environment string
MsiEndpoint string
ResourceGroupName string ResourceGroupName string
SubscriptionID string SubscriptionID string
TenantID string TenantID string
UseMsi bool
} }
func (b *Backend) configure(ctx context.Context) error { func (b *Backend) configure(ctx context.Context) error {
@ -127,10 +143,12 @@ func (b *Backend) configure(ctx context.Context) error {
ClientID: data.Get("arm_client_id").(string), ClientID: data.Get("arm_client_id").(string),
ClientSecret: data.Get("arm_client_secret").(string), ClientSecret: data.Get("arm_client_secret").(string),
Environment: data.Get("environment").(string), Environment: data.Get("environment").(string),
MsiEndpoint: data.Get("msi_endpoint").(string),
ResourceGroupName: data.Get("resource_group_name").(string), ResourceGroupName: data.Get("resource_group_name").(string),
StorageAccountName: data.Get("storage_account_name").(string), StorageAccountName: data.Get("storage_account_name").(string),
SubscriptionID: data.Get("arm_subscription_id").(string), SubscriptionID: data.Get("arm_subscription_id").(string),
TenantID: data.Get("arm_tenant_id").(string), TenantID: data.Get("arm_tenant_id").(string),
UseMsi: data.Get("use_msi").(bool),
} }
armClient, err := buildArmClient(config) armClient, err := buildArmClient(config)

View File

@ -59,6 +59,34 @@ func TestBackendAccessKeyBasic(t *testing.T) {
backend.TestBackendStates(t, b) backend.TestBackendStates(t, b)
} }
func TestBackendManagedServiceIdentityBasic(t *testing.T) {
testAccAzureBackendRunningInAzure(t)
rs := acctest.RandString(4)
res := testResourceNames(rs, "testState")
armClient := buildTestClient(t, res)
ctx := context.TODO()
err := armClient.buildTestResources(ctx, &res)
if err != nil {
armClient.destroyTestResources(ctx, res)
t.Fatalf("Error creating Test Resources: %q", err)
}
defer armClient.destroyTestResources(ctx, res)
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,
"use_msi": true,
"arm_subscription_id": os.Getenv("ARM_SUBSCRIPTION_ID"),
"arm_tenant_id": os.Getenv("ARM_TENANT_ID"),
"environment": os.Getenv("ARM_ENVIRONMENT"),
})).(*Backend)
backend.TestBackendStates(t, b)
}
func TestBackendServicePrincipalBasic(t *testing.T) { func TestBackendServicePrincipalBasic(t *testing.T) {
testAccAzureBackend(t) testAccAzureBackend(t)
rs := acctest.RandString(4) rs := acctest.RandString(4)

View File

@ -45,6 +45,39 @@ func TestRemoteClientAccessKeyBasic(t *testing.T) {
remote.TestClient(t, state.(*remote.State).Client) remote.TestClient(t, state.(*remote.State).Client)
} }
func TestRemoteClientManagedServiceIdentityBasic(t *testing.T) {
testAccAzureBackendRunningInAzure(t)
rs := acctest.RandString(4)
res := testResourceNames(rs, "testState")
armClient := buildTestClient(t, res)
ctx := context.TODO()
err := armClient.buildTestResources(ctx, &res)
if err != nil {
armClient.destroyTestResources(ctx, res)
t.Fatalf("Error creating Test Resources: %q", err)
}
defer armClient.destroyTestResources(ctx, res)
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,
"use_msi": true,
"arm_subscription_id": os.Getenv("ARM_SUBSCRIPTION_ID"),
"arm_tenant_id": os.Getenv("ARM_TENANT_ID"),
"environment": os.Getenv("ARM_ENVIRONMENT"),
})).(*Backend)
state, err := b.StateMgr(backend.DefaultStateName)
if err != nil {
t.Fatal(err)
}
remote.TestClient(t, state.(*remote.State).Client)
}
func TestRemoteClientServicePrincipalBasic(t *testing.T) { func TestRemoteClientServicePrincipalBasic(t *testing.T) {
testAccAzureBackend(t) testAccAzureBackend(t)
rs := acctest.RandString(4) rs := acctest.RandString(4)

View File

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"log" "log"
"os" "os"
"strings"
"testing" "testing"
"github.com/Azure/azure-sdk-for-go/profiles/2017-03-09/resources/mgmt/resources" "github.com/Azure/azure-sdk-for-go/profiles/2017-03-09/resources/mgmt/resources"
@ -21,20 +22,47 @@ func testAccAzureBackend(t *testing.T) {
} }
} }
// these kind of tests can only run when within Azure (e.g. MSI)
func testAccAzureBackendRunningInAzure(t *testing.T) {
testAccAzureBackend(t)
if os.Getenv("TF_RUNNING_IN_AZURE") == "" {
t.Skip("Skipping test since not running in Azure")
}
}
func buildTestClient(t *testing.T, res resourceNames) *ArmClient { func buildTestClient(t *testing.T, res resourceNames) *ArmClient {
subscriptionID := os.Getenv("ARM_SUBSCRIPTION_ID") subscriptionID := os.Getenv("ARM_SUBSCRIPTION_ID")
tenantID := os.Getenv("ARM_TENANT_ID") tenantID := os.Getenv("ARM_TENANT_ID")
clientID := os.Getenv("ARM_CLIENT_ID") clientID := os.Getenv("ARM_CLIENT_ID")
clientSecret := os.Getenv("ARM_CLIENT_SECRET") clientSecret := os.Getenv("ARM_CLIENT_SECRET")
msiEnabled := strings.EqualFold(os.Getenv("ARM_USE_MSI"), "true")
environment := os.Getenv("ARM_ENVIRONMENT") environment := os.Getenv("ARM_ENVIRONMENT")
// location isn't used in this method, but is in the other test methods // location isn't used in this method, but is in the other test methods
location := os.Getenv("ARM_LOCATION") location := os.Getenv("ARM_LOCATION")
if subscriptionID == "" || tenantID == "" || clientID == "" || clientSecret == "" || environment == "" || location == "" { hasCredentials := (clientID != "" && clientSecret != "") || msiEnabled
if !hasCredentials {
t.Fatal("Azure credentials missing or incomplete") t.Fatal("Azure credentials missing or incomplete")
} }
if subscriptionID == "" {
t.Fatalf("Missing ARM_SUBSCRIPTION_ID")
}
if tenantID == "" {
t.Fatalf("Missing ARM_TENANT_ID")
}
if environment == "" {
t.Fatalf("Missing ARM_ENVIRONMENT")
}
if location == "" {
t.Fatalf("Missing ARM_LOCATION")
}
armClient, err := buildArmClient(BackendConfig{ armClient, err := buildArmClient(BackendConfig{
SubscriptionID: subscriptionID, SubscriptionID: subscriptionID,
TenantID: tenantID, TenantID: tenantID,
@ -43,6 +71,7 @@ func buildTestClient(t *testing.T, res resourceNames) *ArmClient {
Environment: environment, Environment: environment,
ResourceGroupName: res.resourceGroup, ResourceGroupName: res.resourceGroup,
StorageAccountName: res.storageAccountName, StorageAccountName: res.storageAccountName,
UseMsi: msiEnabled,
}) })
if err != nil { if err != nil {
t.Fatalf("Failed to build ArmClient: %+v", err) t.Fatalf("Failed to build ArmClient: %+v", err)

View File

@ -27,6 +27,21 @@ terraform {
} }
``` ```
When authenticating using Managed Service Identity (MSI):
```hcl
terraform {
backend "azurerm" {
storage_account_name = "abcd1234"
container_name = "tfstate"
key = "prod.terraform.tfstate"
use_msi = true
arm_subscription_id = "00000000-0000-0000-0000-000000000000"
arm_tenant_id = "00000000-0000-0000-0000-000000000000"
}
}
```
When authenticating using the Access Key associated with the Storage Account: When authenticating using the Access Key associated with the Storage Account:
```hcl ```hcl
@ -60,6 +75,22 @@ data "terraform_remote_state" "foo" {
} }
``` ```
When authenticating using Managed Service Identity (MSI):
```hcl
data "terraform_remote_state" "foo" {
backend = "azurerm"
config = {
storage_account_name = "terraform123abc"
container_name = "terraform-state"
key = "prod.terraform.tfstate"
use_msi = true
arm_subscription_id = "00000000-0000-0000-0000-000000000000"
arm_tenant_id = "00000000-0000-0000-0000-000000000000"
}
}
```
When authenticating using the Access Key associated with the Storage Account: When authenticating using the Access Key associated with the Storage Account:
```hcl ```hcl
@ -91,6 +122,18 @@ The following configuration options are supported:
--- ---
When authenticating using the Managed Service Identity (MSI) - the following fields are also supported:
* `arm_subscription_id` - (Optional) The Subscription ID in which the Storage Account exists. This can also be sourced from the `ARM_SUBSCRIPTION_ID` environment variable.
* `arm_tenant_id` - (Optional) The Tenant ID in which the Subscription exists. This can also be sourced from the `ARM_TENANT_ID` environment variable.
* `msi_endpoint` - (Optional) The path to a custom Managed Service Identity endpoint which is automatically determined if not specified. This can also be sourced from the `ARM_MSI_ENDPOINT` environment variable.
* `use_msi` - (Optional) Should Managed Service Identity authentication be used? This can also be sourced from the `ARM_USE_MSI` environment variable.
---
When authenticating using the Storage Account's Access Key - the following fields are also supported: When authenticating using the Storage Account's Access Key - the following fields are also supported:
* `access_key` - (Optional) The Access Key used to access the Blob Storage Account. This can also be sourced from the `ARM_ACCESS_KEY` environment variable. * `access_key` - (Optional) The Access Key used to access the Blob Storage Account. This can also be sourced from the `ARM_ACCESS_KEY` environment variable.