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:
parent
9f15381fec
commit
c928962f44
|
@ -50,10 +50,12 @@ func buildArmClient(config BackendConfig) (*ArmClient, error) {
|
|||
SubscriptionID: config.SubscriptionID,
|
||||
TenantID: config.TenantID,
|
||||
Environment: config.Environment,
|
||||
MsiEndpoint: config.MsiEndpoint,
|
||||
|
||||
// Feature Toggles
|
||||
SupportsClientSecretAuth: true,
|
||||
// TODO: support for Azure CLI / Client Certificate / MSI
|
||||
SupportsClientSecretAuth: true,
|
||||
SupportsManagedServiceIdentity: config.UseMsi,
|
||||
// TODO: support for Azure CLI / Client Certificate auth
|
||||
}
|
||||
armConfig, err := builder.Build()
|
||||
if err != nil {
|
||||
|
|
|
@ -78,6 +78,20 @@ func New() backend.Backend {
|
|||
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: support for custom resource manager endpoints
|
||||
},
|
||||
|
@ -106,9 +120,11 @@ type BackendConfig struct {
|
|||
ClientID string
|
||||
ClientSecret string
|
||||
Environment string
|
||||
MsiEndpoint string
|
||||
ResourceGroupName string
|
||||
SubscriptionID string
|
||||
TenantID string
|
||||
UseMsi bool
|
||||
}
|
||||
|
||||
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),
|
||||
ClientSecret: data.Get("arm_client_secret").(string),
|
||||
Environment: data.Get("environment").(string),
|
||||
MsiEndpoint: data.Get("msi_endpoint").(string),
|
||||
ResourceGroupName: data.Get("resource_group_name").(string),
|
||||
StorageAccountName: data.Get("storage_account_name").(string),
|
||||
SubscriptionID: data.Get("arm_subscription_id").(string),
|
||||
TenantID: data.Get("arm_tenant_id").(string),
|
||||
UseMsi: data.Get("use_msi").(bool),
|
||||
}
|
||||
|
||||
armClient, err := buildArmClient(config)
|
||||
|
|
|
@ -59,6 +59,34 @@ func TestBackendAccessKeyBasic(t *testing.T) {
|
|||
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) {
|
||||
testAccAzureBackend(t)
|
||||
rs := acctest.RandString(4)
|
||||
|
|
|
@ -45,6 +45,39 @@ func TestRemoteClientAccessKeyBasic(t *testing.T) {
|
|||
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) {
|
||||
testAccAzureBackend(t)
|
||||
rs := acctest.RandString(4)
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"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 {
|
||||
subscriptionID := os.Getenv("ARM_SUBSCRIPTION_ID")
|
||||
tenantID := os.Getenv("ARM_TENANT_ID")
|
||||
clientID := os.Getenv("ARM_CLIENT_ID")
|
||||
clientSecret := os.Getenv("ARM_CLIENT_SECRET")
|
||||
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")
|
||||
|
||||
if subscriptionID == "" || tenantID == "" || clientID == "" || clientSecret == "" || environment == "" || location == "" {
|
||||
hasCredentials := (clientID != "" && clientSecret != "") || msiEnabled
|
||||
if !hasCredentials {
|
||||
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{
|
||||
SubscriptionID: subscriptionID,
|
||||
TenantID: tenantID,
|
||||
|
@ -43,6 +71,7 @@ func buildTestClient(t *testing.T, res resourceNames) *ArmClient {
|
|||
Environment: environment,
|
||||
ResourceGroupName: res.resourceGroup,
|
||||
StorageAccountName: res.storageAccountName,
|
||||
UseMsi: msiEnabled,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to build ArmClient: %+v", err)
|
||||
|
|
|
@ -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:
|
||||
|
||||
```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:
|
||||
|
||||
```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:
|
||||
|
||||
* `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.
|
||||
|
|
Loading…
Reference in New Issue