Merge pull request #25770 from hashicorp/f/azure-backend-spcert
backend/azurerm: support for authenticating using a Client Certificate
This commit is contained in:
commit
d46e9a4198
|
@ -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 {
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue