Merge pull request #25770 from hashicorp/f/azure-backend-spcert

backend/azurerm: support for authenticating using a Client Certificate
This commit is contained in:
James Bardin 2020-08-19 13:56:30 -04:00 committed by GitHub
commit d46e9a4198
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 104 additions and 23 deletions

View File

@ -59,18 +59,27 @@ func buildArmClient(config BackendConfig) (*ArmClient, error) {
builder := authentication.Builder{ builder := authentication.Builder{
ClientID: config.ClientID, ClientID: config.ClientID,
ClientSecret: config.ClientSecret,
SubscriptionID: config.SubscriptionID, SubscriptionID: config.SubscriptionID,
TenantID: config.TenantID, TenantID: config.TenantID,
CustomResourceManagerEndpoint: config.CustomResourceManagerEndpoint, CustomResourceManagerEndpoint: config.CustomResourceManagerEndpoint,
Environment: config.Environment, Environment: config.Environment,
MsiEndpoint: config.MsiEndpoint, ClientSecretDocsLink: "https://www.terraform.io/docs/providers/azurerm/guides/service_principal_client_secret.html",
// Service Principal (Client Certificate)
ClientCertPassword: config.ClientCertificatePassword,
ClientCertPath: config.ClientCertificatePath,
// Service Principal (Client Secret)
ClientSecret: config.ClientSecret,
// Managed Service Identity
MsiEndpoint: config.MsiEndpoint,
// Feature Toggles // Feature Toggles
SupportsAzureCliToken: true, SupportsAzureCliToken: true,
SupportsClientCertAuth: true,
SupportsClientSecretAuth: true, SupportsClientSecretAuth: true,
SupportsManagedServiceIdentity: config.UseMsi, SupportsManagedServiceIdentity: config.UseMsi,
// TODO: support for Client Certificate auth
} }
armConfig, err := builder.Build() armConfig, err := builder.Build()
if err != nil { if err != nil {

View File

@ -71,11 +71,11 @@ func New() backend.Backend {
DefaultFunc: schema.EnvDefaultFunc("ARM_CLIENT_ID", ""), DefaultFunc: schema.EnvDefaultFunc("ARM_CLIENT_ID", ""),
}, },
"client_secret": { "endpoint": {
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
Description: "The Client Secret.", Description: "A custom Endpoint used to access the Azure Resource Manager API's.",
DefaultFunc: schema.EnvDefaultFunc("ARM_CLIENT_SECRET", ""), DefaultFunc: schema.EnvDefaultFunc("ARM_ENDPOINT", ""),
}, },
"subscription_id": { "subscription_id": {
@ -92,13 +92,35 @@ func New() backend.Backend {
DefaultFunc: schema.EnvDefaultFunc("ARM_TENANT_ID", ""), DefaultFunc: schema.EnvDefaultFunc("ARM_TENANT_ID", ""),
}, },
// Service Principal (Client Certificate) specific
"client_certificate_password": {
Type: schema.TypeString,
Optional: true,
Description: "The password associated with the Client Certificate specified in `client_certificate_path`",
DefaultFunc: schema.EnvDefaultFunc("ARM_CLIENT_CERTIFICATE_PASSWORD", ""),
},
"client_certificate_path": {
Type: schema.TypeString,
Optional: true,
Description: "The path to the PFX file used as the Client Certificate when authenticating as a Service Principal",
DefaultFunc: schema.EnvDefaultFunc("ARM_CLIENT_CERTIFICATE_PATH", ""),
},
// Service Principal (Client Secret) specific
"client_secret": {
Type: schema.TypeString,
Optional: true,
Description: "The Client Secret.",
DefaultFunc: schema.EnvDefaultFunc("ARM_CLIENT_SECRET", ""),
},
// Managed Service Identity specific
"use_msi": { "use_msi": {
Type: schema.TypeBool, Type: schema.TypeBool,
Optional: true, Optional: true,
Description: "Should Managed Service Identity be used?.", Description: "Should Managed Service Identity be used?.",
DefaultFunc: schema.EnvDefaultFunc("ARM_USE_MSI", false), DefaultFunc: schema.EnvDefaultFunc("ARM_USE_MSI", false),
}, },
"msi_endpoint": { "msi_endpoint": {
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
@ -106,13 +128,6 @@ func New() backend.Backend {
DefaultFunc: schema.EnvDefaultFunc("ARM_MSI_ENDPOINT", ""), 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 // Deprecated fields
"arm_client_id": { "arm_client_id": {
Type: schema.TypeString, Type: schema.TypeString,
@ -167,6 +182,8 @@ type BackendConfig struct {
// Optional // Optional
AccessKey string AccessKey string
ClientID string ClientID string
ClientCertificatePassword string
ClientCertificatePath string
ClientSecret string ClientSecret string
CustomResourceManagerEndpoint string CustomResourceManagerEndpoint string
Environment string Environment string
@ -199,6 +216,8 @@ func (b *Backend) configure(ctx context.Context) error {
config := BackendConfig{ config := BackendConfig{
AccessKey: data.Get("access_key").(string), AccessKey: data.Get("access_key").(string),
ClientID: clientId, ClientID: clientId,
ClientCertificatePassword: data.Get("client_certificate_password").(string),
ClientCertificatePath: data.Get("client_certificate_path").(string),
ClientSecret: clientSecret, ClientSecret: clientSecret,
CustomResourceManagerEndpoint: data.Get("endpoint").(string), CustomResourceManagerEndpoint: data.Get("endpoint").(string),
Environment: data.Get("environment").(string), Environment: data.Get("environment").(string),

View File

@ -123,7 +123,44 @@ func TestBackendSASTokenBasic(t *testing.T) {
backend.TestBackendStates(t, b) backend.TestBackendStates(t, b)
} }
func TestBackendServicePrincipalBasic(t *testing.T) { func TestBackendServicePrincipalClientCertificateBasic(t *testing.T) {
testAccAzureBackend(t)
clientCertPassword := os.Getenv("ARM_CLIENT_CERTIFICATE_PASSWORD")
clientCertPath := os.Getenv("ARM_CLIENT_CERTIFICATE_PATH")
if clientCertPath == "" {
t.Skip("Skipping since `ARM_CLIENT_CERTIFICATE_PATH` is not specified!")
}
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_certificate_password": clientCertPassword,
"client_certificate_path": clientCertPath,
"environment": os.Getenv("ARM_ENVIRONMENT"),
"endpoint": os.Getenv("ARM_ENDPOINT"),
})).(*Backend)
backend.TestBackendStates(t, b)
}
func TestBackendServicePrincipalClientSecretBasic(t *testing.T) {
testAccAzureBackend(t) testAccAzureBackend(t)
rs := acctest.RandString(4) rs := acctest.RandString(4)
res := testResourceNames(rs, "testState") res := testResourceNames(rs, "testState")
@ -152,7 +189,7 @@ func TestBackendServicePrincipalBasic(t *testing.T) {
backend.TestBackendStates(t, b) backend.TestBackendStates(t, b)
} }
func TestBackendServicePrincipalCustomEndpoint(t *testing.T) { func TestBackendServicePrincipalClientSecretCustomEndpoint(t *testing.T) {
testAccAzureBackend(t) testAccAzureBackend(t)
// this is only applicable for Azure Stack. // this is only applicable for Azure Stack.

View File

@ -15,7 +15,7 @@ Stores the state as a Blob with the given Key within the Blob Container within [
## Example Configuration ## Example Configuration
When authenticating using the Azure CLI or a Service Principal: When authenticating using the Azure CLI or a Service Principal (either with a Client Certificate or a Client Secret):
```hcl ```hcl
terraform { terraform {
@ -37,8 +37,8 @@ terraform {
container_name = "tfstate" container_name = "tfstate"
key = "prod.terraform.tfstate" key = "prod.terraform.tfstate"
use_msi = true use_msi = true
subscription_id = "00000000-0000-0000-0000-000000000000" subscription_id = "00000000-0000-0000-0000-000000000000"
tenant_id = "00000000-0000-0000-0000-000000000000" tenant_id = "00000000-0000-0000-0000-000000000000"
} }
} }
``` ```
@ -79,7 +79,7 @@ terraform {
## Data Source Configuration ## Data Source Configuration
When authenticating using a Service Principal: When authenticating using a Service Principall (either with a Client Certificate or a Client Secret):
```hcl ```hcl
data "terraform_remote_state" "foo" { data "terraform_remote_state" "foo" {
@ -154,12 +154,12 @@ 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`. * `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`.
* `snapshot` - (Optional) Should the Blob used to store the Terraform Statefile be snapshotted before use? Defaults to `false`. This value can also be sourced from the `ARM_SNAPSHOT` environment variable.
* `endpoint` - (Optional) The Custom Endpoint for Azure Resource Manager. This can also be sourced from the `ARM_ENDPOINT` environment variable. * `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. ~> **NOTE:** An `endpoint` should only be configured when using Azure Stack.
* `snapshot` - (Optional) Should the Blob used to store the Terraform Statefile be snapshotted before use? Defaults to `false`. This value can also be sourced from the `ARM_SNAPSHOT` environment variable.
--- ---
When authenticating using the Managed Service Identity (MSI) - the following fields are also supported: When authenticating using the Managed Service Identity (MSI) - the following fields are also supported:
@ -186,7 +186,23 @@ When authenticating using the Storage Account's Access Key - the following field
--- ---
When authenticating using a Service Principal - the following fields are also supported: When authenticating using a Service Principal with a Client Certificate - the following fields are also supported:
* `resource_group_name` - (Required) The Name of the Resource Group in which the Storage Account exists.
* `client_id` - (Optional) The Client ID of the Service Principal. This can also be sourced from the `ARM_CLIENT_ID` environment variable.
* `client_certificate_password` - (Optional) The password associated with the Client Certificate specified in `client_certificate_path`. This can also be sourced from the `ARM_CLIENT_CERTIFICATE_PASSWORD` environment variable.
* `client_certificate_path` - (Optional) The path to the PFX file used as the Client Certificate when authenticating as a Service Principal. This can also be sourced from the `ARM_CLIENT_CERTIFICATE_PATH` environment variable.
* `subscription_id` - (Optional) The Subscription ID in which the Storage Account exists. This can also be sourced from the `ARM_SUBSCRIPTION_ID` environment variable.
* `tenant_id` - (Optional) The Tenant ID in which the Subscription exists. This can also be sourced from the `ARM_TENANT_ID` environment variable.
---
When authenticating using a Service Principal with a Client Secret - the following fields are also supported:
* `resource_group_name` - (Required) The Name of the Resource Group in which the Storage Account exists. * `resource_group_name` - (Required) The Name of the Resource Group in which the Storage Account exists.