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,
|
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 {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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.
|
||||||
|
|
Loading…
Reference in New Issue