added vault_azure_access_credentials ephemeral resource (#2654)

* added azure_access_credentials ephemeral resource

* minor refactor

* added doc

* added changelog entry

* minor fix

* fixed test

* added retry with backoff and removed changelog

* rename var and use consts in schema

* checking status code as well for retry

* minor refactor

* reduced defaultNumSequentialSuccesses to 4

* fix doc
This commit is contained in:
aahel 2025-11-21 22:56:47 +05:30 committed by GitHub
parent 148e86b088
commit 1f4beca5bd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 725 additions and 0 deletions

View file

@ -98,6 +98,10 @@ const (
FieldClientID = "client_id"
FieldClientSecret = "client_secret"
FieldEnvironment = "environment"
FieldValidateCreds = "validate_creds"
FieldNumSequentialSuccesses = "num_sequential_successes"
FieldNumSecondsBetweenTests = "num_seconds_between_tests"
FieldMaxCredValidationSeconds = "max_cred_validation_seconds"
FieldVaultName = "vault_name"
FieldKeyName = "key_name"
FieldResource = "resource"
@ -218,6 +222,7 @@ const (
FieldRenewMinLease = "renew_min_lease"
FieldRenewIncrement = "renew_increment"
FieldLeaseStarted = "lease_started"
FieldLeaseStartTime = "lease_start_time"
FieldClientToken = "client_token"
FieldWrappedToken = "wrapped_token"
FieldOrphan = "orphan"

View file

@ -238,6 +238,7 @@ func (p *fwprovider) EphemeralResources(_ context.Context) []func() ephemeral.Ep
ephemeralsecrets.NewKVV2EphemeralSecretResource,
ephemeralsecrets.NewDBEphemeralSecretResource,
ephemeralsecrets.NewAzureStaticCredsEphemeralSecretResource,
ephemeralsecrets.NewAzureAccessCredentialsEphemeralResource,
}
}

View file

@ -0,0 +1,499 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package ephemeralsecrets
import (
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"strings"
"time"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/arm"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime"
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources"
"github.com/cenkalti/backoff/v4"
"github.com/hashicorp/terraform-plugin-framework/ephemeral"
"github.com/hashicorp/terraform-plugin-framework/ephemeral/schema"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-provider-vault/internal/consts"
"github.com/hashicorp/terraform-provider-vault/internal/framework/base"
"github.com/hashicorp/terraform-provider-vault/internal/framework/client"
"github.com/hashicorp/terraform-provider-vault/internal/framework/errutil"
"github.com/hashicorp/terraform-provider-vault/internal/framework/model"
"github.com/hashicorp/vault/api"
"github.com/hashicorp/vault/sdk/helper/pointerutil"
)
// https://learn.microsoft.com/en-us/graph/sdks/national-clouds
const (
azurePublicCloudEnvName = "AZUREPUBLICCLOUD"
azureChinaCloudEnvName = "AZURECHINACLOUD"
azureUSGovCloudEnvName = "AZUREUSGOVERNMENTCLOUD"
// Default values for credential validation
defaultNumSecondsBetweenTests = 1
defaultMaxCredValidationSeconds = 300
defaultNumSequentialSuccesses = 4
)
var azureCloudConfigMap = map[string]cloud.Configuration{
azureChinaCloudEnvName: cloud.AzureChina,
azurePublicCloudEnvName: cloud.AzurePublic,
azureUSGovCloudEnvName: cloud.AzureGovernment,
}
// Ensure the implementation satisfies the ephemeral.EphemeralResource interface
var _ ephemeral.EphemeralResource = &AzureAccessCredentialsEphemeralResource{}
var _ ephemeral.EphemeralResourceWithClose = &AzureAccessCredentialsEphemeralResource{}
// NewAzureAccessCredentialsEphemeralResource returns the implementation for this resource to be
// imported by the Terraform Plugin Framework provider
var NewAzureAccessCredentialsEphemeralResource = func() ephemeral.EphemeralResource {
return &AzureAccessCredentialsEphemeralResource{}
}
// AzureAccessCredentialsEphemeralResource implements the methods that define this resource
type AzureAccessCredentialsEphemeralResource struct {
base.EphemeralResourceWithConfigure
}
// AzureAccessCredentialsPrivateData stores data needed for cleanup in Close
type AzureAccessCredentialsPrivateData struct {
LeaseID string `json:"lease_id"`
Namespace string `json:"namespace"`
}
// AzureAccessCredentialsAPIModel describes the Vault API data model.
type AzureAccessCredentialsAPIModel struct {
ClientID string `json:"client_id" mapstructure:"client_id"`
ClientSecret string `json:"client_secret" mapstructure:"client_secret"`
}
// AzureAccessCredentialsModel describes the Terraform resource data model to match the
// resource schema.
type AzureAccessCredentialsModel struct {
// common fields to all ephemeral resources
base.BaseModelEphemeral
// fields specific to this resource
Backend types.String `tfsdk:"backend"`
Role types.String `tfsdk:"role"`
ValidateCreds types.Bool `tfsdk:"validate_creds"`
NumSequentialSuccesses types.Int64 `tfsdk:"num_sequential_successes"`
NumSecondsBetweenTests types.Int64 `tfsdk:"num_seconds_between_tests"`
MaxCredValidationSeconds types.Int64 `tfsdk:"max_cred_validation_seconds"`
SubscriptionID types.String `tfsdk:"subscription_id"`
TenantID types.String `tfsdk:"tenant_id"`
Environment types.String `tfsdk:"environment"`
// computed fields
ClientID types.String `tfsdk:"client_id"`
ClientSecret types.String `tfsdk:"client_secret"`
LeaseID types.String `tfsdk:"lease_id"`
LeaseDuration types.Int64 `tfsdk:"lease_duration"`
LeaseStartTime types.String `tfsdk:"lease_start_time"`
LeaseRenewable types.Bool `tfsdk:"lease_renewable"`
}
// Schema defines this resource's schema which is the data that is available in
// the resource's configuration, plan, and state
func (r *AzureAccessCredentialsEphemeralResource) Schema(_ context.Context, _ ephemeral.SchemaRequest, resp *ephemeral.SchemaResponse) {
resp.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
consts.FieldBackend: schema.StringAttribute{
MarkdownDescription: "Azure Secret Backend to read credentials from.",
Required: true,
},
consts.FieldRole: schema.StringAttribute{
MarkdownDescription: "Azure Secret Role to read credentials from.",
Required: true,
},
consts.FieldValidateCreds: schema.BoolAttribute{
MarkdownDescription: "Whether generated credentials should be validated before being returned.",
Optional: true,
},
consts.FieldNumSequentialSuccesses: schema.Int64Attribute{
MarkdownDescription: "If 'validate_creds' is true, the number of sequential successes required to validate generated credentials.",
Optional: true,
},
consts.FieldNumSecondsBetweenTests: schema.Int64Attribute{
MarkdownDescription: "If 'validate_creds' is true, the number of seconds to wait between each test of generated credentials.",
Optional: true,
},
consts.FieldMaxCredValidationSeconds: schema.Int64Attribute{
MarkdownDescription: "If 'validate_creds' is true, the number of seconds after which to give up validating credentials.",
Optional: true,
},
consts.FieldSubscriptionID: schema.StringAttribute{
MarkdownDescription: "The subscription ID to use during credential validation. Defaults to the subscription ID configured in the Vault backend.",
Optional: true,
},
consts.FieldTenantID: schema.StringAttribute{
MarkdownDescription: "The tenant ID to use during credential validation. Defaults to the tenant ID configured in the Vault backend.",
Optional: true,
},
consts.FieldEnvironment: schema.StringAttribute{
MarkdownDescription: "The Azure environment to use during credential validation. Defaults to the Azure Public Cloud. Some possible values: AzurePublicCloud, AzureUSGovernmentCloud.",
Optional: true,
},
consts.FieldClientID: schema.StringAttribute{
MarkdownDescription: "The client id for credentials to query the Azure APIs.",
Computed: true,
},
consts.FieldClientSecret: schema.StringAttribute{
MarkdownDescription: "The client secret for credentials to query the Azure APIs.",
Computed: true,
Sensitive: true,
},
consts.FieldLeaseID: schema.StringAttribute{
MarkdownDescription: "Lease identifier assigned by vault.",
Computed: true,
},
consts.FieldLeaseDuration: schema.Int64Attribute{
MarkdownDescription: "Lease duration in seconds relative to the time in lease_start_time.",
Computed: true,
},
consts.FieldLeaseStartTime: schema.StringAttribute{
MarkdownDescription: "Time at which the lease was read, using the clock of the system where Terraform was running.",
Computed: true,
},
consts.FieldLeaseRenewable: schema.BoolAttribute{
MarkdownDescription: "True if the duration of this lease can be extended through renewal.",
Computed: true,
},
},
MarkdownDescription: "Provides an ephemeral resource to read Azure access credentials from Vault.",
}
base.MustAddBaseEphemeralSchema(&resp.Schema)
}
// Metadata sets the full name for this resource
func (r *AzureAccessCredentialsEphemeralResource) Metadata(ctx context.Context, req ephemeral.MetadataRequest, resp *ephemeral.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_azure_access_credentials"
}
func (r *AzureAccessCredentialsEphemeralResource) Open(ctx context.Context, req ephemeral.OpenRequest, resp *ephemeral.OpenResponse) {
var data AzureAccessCredentialsModel
// Read Terraform prior state data into the model
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
c, err := client.GetClient(ctx, r.Meta(), data.Namespace.ValueString())
if err != nil {
resp.Diagnostics.AddError(errutil.ClientConfigureErr(err))
return
}
backend := data.Backend.ValueString()
role := data.Role.ValueString()
credsPath := backend + "/creds/" + role
// Retry logic for reading credentials from Vault with exponential backoff
// Azure can return rate limit errors generating credentials when multiple
// requests are made during plan,apply,refresh in quick succession
var secret *api.Secret
bo := backoff.NewExponentialBackOff()
bo.InitialInterval = 2 * time.Second
bo.MaxInterval = 30 * time.Second
bo.MaxElapsedTime = 5 * time.Minute
err = backoff.RetryNotify(
func() error {
var readErr error
secret, readErr = c.Logical().ReadWithContext(ctx, credsPath)
if readErr != nil {
if respErr, ok := readErr.(*api.ResponseError); ok {
if respErr.StatusCode == 500 && strings.Contains(respErr.Error(), "concurrent requests being made") {
// Retryable error
return respErr
}
// Non-retryable error
return backoff.Permanent(respErr)
}
// Non-retryable error
return backoff.Permanent(readErr)
}
return nil
},
bo,
func(err error, duration time.Duration) {
log.Printf("[WARN] Azure rate limit error reading credentials, retrying in %s: %s", duration, err)
},
)
if err != nil {
resp.Diagnostics.AddError(
"Error reading from Vault",
fmt.Sprintf("Error reading Azure credentials from path %q: %s", credsPath, err),
)
return
}
if secret == nil {
resp.Diagnostics.AddError(
"No role found",
fmt.Sprintf("No role found at path %q", credsPath),
)
return
}
log.Printf("[DEBUG] Read %q from Vault", credsPath)
var apiResp AzureAccessCredentialsAPIModel
if err := model.ToAPIModel(secret.Data, &apiResp); err != nil {
resp.Diagnostics.AddError("Unable to translate Vault response data", err.Error())
return
}
// Set the basic credential fields
data.ClientID = types.StringValue(apiResp.ClientID)
data.ClientSecret = types.StringValue(apiResp.ClientSecret)
data.LeaseID = types.StringValue(secret.LeaseID)
data.LeaseDuration = types.Int64Value(int64(secret.LeaseDuration))
data.LeaseStartTime = types.StringValue(time.Now().Format(time.RFC3339))
data.LeaseRenewable = types.BoolValue(secret.Renewable)
// Store lease information in private data for cleanup in Close
if secret.LeaseID != "" {
privateData, err := json.Marshal(AzureAccessCredentialsPrivateData{
LeaseID: secret.LeaseID,
Namespace: data.Namespace.ValueString(),
})
if err != nil {
log.Printf("[WARN] Failed to marshal private data: %s", err)
} else {
resp.Private.SetKey(ctx, "lease_data", privateData)
}
}
// If we're not supposed to validate creds, we're done
if !data.ValidateCreds.ValueBool() {
resp.Diagnostics.Append(resp.Result.Set(ctx, &data)...)
return
}
// Credential validation logic
configPath := backend + "/config"
var config *api.Secret
getConfigData := func() (map[string]interface{}, error) {
if config == nil {
configSecret, err := c.Logical().ReadWithContext(ctx, configPath)
if err != nil {
return nil, fmt.Errorf("error reading from Vault: %w", err)
}
if configSecret == nil {
return nil, fmt.Errorf("config not found at %q", configPath)
}
config = configSecret
}
return config.Data, nil
}
subscriptionID := data.SubscriptionID.ValueString()
if subscriptionID == "" {
configData, err := getConfigData()
if err != nil {
resp.Diagnostics.AddError("Error reading backend config", err.Error())
return
}
if v, ok := configData["subscription_id"]; ok {
subscriptionID = v.(string)
}
}
if subscriptionID == "" {
resp.Diagnostics.AddError(
"Missing subscription_id",
"subscription_id cannot be empty when validate_creds is true",
)
return
}
tenantID := data.TenantID.ValueString()
if tenantID == "" {
configData, err := getConfigData()
if err != nil {
resp.Diagnostics.AddError("Error reading backend config", err.Error())
return
}
if v, ok := configData["tenant_id"]; ok {
tenantID = v.(string)
}
}
if tenantID == "" {
resp.Diagnostics.AddError(
"Missing tenant_id",
"tenant_id cannot be empty when validate_creds is true",
)
return
}
environment := data.Environment.ValueString()
if environment == "" {
configData, err := getConfigData()
if err != nil {
resp.Diagnostics.AddError("Error reading backend config", err.Error())
return
}
if v, ok := configData["environment"]; ok && v.(string) != "" {
environment = v.(string)
}
}
cloudConfig, err := getAzureCloudConfigFromName(environment)
if err != nil {
resp.Diagnostics.AddError("Invalid Azure environment", err.Error())
return
}
// Default validation parameters
delay := time.Duration(defaultNumSecondsBetweenTests) * time.Second
if !data.NumSecondsBetweenTests.IsNull() {
delay = time.Duration(data.NumSecondsBetweenTests.ValueInt64()) * time.Second
}
maxValidationSeconds := int64(defaultMaxCredValidationSeconds)
if !data.MaxCredValidationSeconds.IsNull() {
maxValidationSeconds = data.MaxCredValidationSeconds.ValueInt64()
}
wantSuccessCount := int64(defaultNumSequentialSuccesses)
if !data.NumSequentialSuccesses.IsNull() {
wantSuccessCount = data.NumSequentialSuccesses.ValueInt64()
}
endTime := time.Now().Add(time.Duration(maxValidationSeconds) * time.Second)
var successCount int64
// Credential validation retry loop
for {
creds, err := azidentity.NewClientSecretCredential(
tenantID, apiResp.ClientID, apiResp.ClientSecret, &azidentity.ClientSecretCredentialOptions{})
if err != nil {
resp.Diagnostics.AddError(
"Failed to create credentials",
fmt.Sprintf("Failed to create new credential during validation: %s", err),
)
return
}
providerClient, err := armresources.NewProvidersClient(subscriptionID, creds, &arm.ClientOptions{
ClientOptions: policy.ClientOptions{
Cloud: cloudConfig,
},
})
if err != nil {
resp.Diagnostics.AddError(
"Failed to create Azure client",
fmt.Sprintf("Failed to create new provider client during validation: %s", err),
)
return
}
pager := providerClient.NewListPager(&armresources.ProvidersClientListOptions{
Expand: pointerutil.StringPtr("metadata"),
})
hasError := false
for pager.More() {
var rawResponse *http.Response
ctxWithResp := runtime.WithCaptureResponse(ctx, &rawResponse)
_, err := pager.NextPage(ctxWithResp)
if err != nil {
hasError = true
log.Printf("[WARN] Provider Client List request failed err=%s", err)
break
}
log.Printf("[DEBUG] Provider Client List response %+v", rawResponse)
}
if !hasError {
successCount++
log.Printf("[DEBUG] Credential validation succeeded on try %d/%d", successCount, wantSuccessCount)
if successCount >= wantSuccessCount {
break
}
} else {
log.Printf("[WARN] Credential validation failed, retrying in %s", delay)
successCount = 0
}
if time.Now().After(endTime) {
resp.Diagnostics.AddError(
"Credential validation timeout",
fmt.Sprintf("validation failed after max_cred_validation_seconds of %d, giving up; now=%s, endTime=%s",
maxValidationSeconds, time.Now().String(), endTime.String()),
)
return
}
time.Sleep(delay)
}
resp.Diagnostics.Append(resp.Result.Set(ctx, &data)...)
}
// Close revokes the credentials lease when the ephemeral resource is no longer needed
func (r *AzureAccessCredentialsEphemeralResource) Close(ctx context.Context, req ephemeral.CloseRequest, resp *ephemeral.CloseResponse) {
privateBytes, diags := req.Private.GetKey(ctx, "lease_data")
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
// If no private data, nothing to clean up
if len(privateBytes) == 0 {
return
}
var privateData AzureAccessCredentialsPrivateData
if err := json.Unmarshal(privateBytes, &privateData); err != nil {
log.Printf("[WARN] Failed to unmarshal private data: %s", err)
return
}
if privateData.LeaseID == "" {
// No lease to revoke
return
}
c, err := client.GetClient(ctx, r.Meta(), privateData.Namespace)
if err != nil {
resp.Diagnostics.AddError("Error configuring Vault client for revoke", err.Error())
return
}
// Attempt to revoke the lease
err = c.Sys().Revoke(privateData.LeaseID)
if err != nil {
// Log but do not fail resource close
log.Printf("[WARN] Failed to revoke lease %q: %s", privateData.LeaseID, err)
} else {
log.Printf("[DEBUG] Successfully revoked lease %q", privateData.LeaseID)
}
}
func getAzureCloudConfigFromName(name string) (cloud.Configuration, error) {
if name == "" {
return cloud.AzurePublic, nil
}
if c, ok := azureCloudConfigMap[strings.ToUpper(name)]; !ok {
return c, fmt.Errorf("unsupported Azure cloud name %q", name)
} else {
return c, nil
}
}

View file

@ -0,0 +1,130 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package ephemeralsecrets_test
import (
"fmt"
"regexp"
"testing"
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
"github.com/hashicorp/terraform-plugin-testing/echoprovider"
"github.com/hashicorp/terraform-plugin-testing/helper/acctest"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/hashicorp/terraform-plugin-testing/knownvalue"
"github.com/hashicorp/terraform-plugin-testing/statecheck"
"github.com/hashicorp/terraform-plugin-testing/tfjsonpath"
"github.com/hashicorp/terraform-provider-vault/acctestutil"
"github.com/hashicorp/terraform-provider-vault/internal/providertest"
"github.com/hashicorp/terraform-provider-vault/testutil"
)
// TestAccAzureAccessCredentialsEphemeralResource_basic tests the creation of dynamic
// Azure service principal credentials using ephemeral resource.
func TestAccAzureAccessCredentialsEphemeralResource_basic(t *testing.T) {
tests := []struct {
name string
validateCreds bool
}{
{
name: "without validation",
validateCreds: false,
},
{
name: "with validation",
validateCreds: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.validateCreds && testing.Short() {
t.Skip("skipping test with credential validation overhead in short mode")
}
conf := testutil.GetTestAzureConfExistingSP(t)
backend := acctest.RandomWithPrefix("tf-test-azure")
role := acctest.RandomWithPrefix("tf-role")
nonEmpty := regexp.MustCompile(`^.+$`)
resource.Test(t, resource.TestCase{
PreCheck: func() {
acctestutil.TestEntPreCheck(t)
},
ProtoV5ProviderFactories: providertest.ProtoV5ProviderFactories,
ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){
"echo": echoprovider.NewProviderServer(),
},
Steps: []resource.TestStep{
{
Config: testAccAzureAccessCredentialsEphemeralResourceConfig_basic(backend, role, conf, tt.validateCreds),
ConfigStateChecks: []statecheck.StateCheck{
statecheck.ExpectKnownValue("echo.test_azure",
tfjsonpath.New("data").AtMapKey("client_id"),
knownvalue.StringExact(conf.ClientID)),
statecheck.ExpectKnownValue("echo.test_azure",
tfjsonpath.New("data").AtMapKey("client_secret"),
knownvalue.StringRegexp(nonEmpty)),
statecheck.ExpectKnownValue("echo.test_azure",
tfjsonpath.New("data").AtMapKey("lease_id"),
knownvalue.StringRegexp(nonEmpty)),
statecheck.ExpectKnownValue("echo.test_azure",
tfjsonpath.New("data").AtMapKey("lease_duration"),
knownvalue.NotNull()),
statecheck.ExpectKnownValue("echo.test_azure",
tfjsonpath.New("data").AtMapKey("lease_start_time"),
knownvalue.StringRegexp(nonEmpty)),
statecheck.ExpectKnownValue("echo.test_azure",
tfjsonpath.New("data").AtMapKey("lease_renewable"),
knownvalue.NotNull()),
},
},
},
})
})
}
}
func testAccAzureAccessCredentialsEphemeralResourceConfig_basic(backend, role string, conf *testutil.AzureTestConf, validateCreds bool) string {
return fmt.Sprintf(`
resource "vault_azure_secret_backend" "azure" {
subscription_id = "%s"
tenant_id = "%s"
client_id = "%s"
client_secret = "%s"
path = "%s"
}
resource "vault_azure_secret_backend_role" "role" {
backend = vault_azure_secret_backend.azure.path
role = "%s"
ttl = 3600
max_ttl = 7200
application_object_id = "%s"
}
ephemeral "vault_azure_access_credentials" "cred" {
backend = vault_azure_secret_backend.azure.path
role = vault_azure_secret_backend_role.role.role
mount_id = vault_azure_secret_backend_role.role.id
validate_creds = %t
num_sequential_successes = 2
}
provider "echo" {
data = ephemeral.vault_azure_access_credentials.cred
}
resource "echo" "test_azure" {}
`,
conf.SubscriptionID,
conf.TenantID,
conf.ClientID,
conf.ClientSecret,
backend,
role,
conf.AppObjectID,
validateCreds,
)
}

View file

@ -0,0 +1,90 @@
---
layout: "vault"
page_title: "Vault: ephemeral vault_azure_access_credentials resource"
sidebar_current: "docs-vault-ephemeral-azure-access-credentials"
description: |-
Read an ephemeral dynamic secret from the Vault Azure Secrets engine
---
# vault_azure_access_credentials (Ephemeral)
Reads ephemeral dynamic Azure credentials for a role managed by the Azure Secrets Engine.
These credentials are not stored in Terraform state.
For more information, refer to
the [Vault Azure Secrets Engine documentation](https://developer.hashicorp.com/vault/docs/secrets/azure).
## Example Usage
```hcl
resource "vault_azure_secret_backend" "azure" {
subscription_id = var.subscription_id
tenant_id = var.tenant_id
client_id = var.client_id
client_secret = var.client_secret
path = "azure"
}
resource "vault_azure_secret_backend_role" "azurerole" {
backend = vault_azure_secret_backend.azure.path
role = "azurerole"
ttl = 3600
max_ttl = 7200
application_object_id = "00000000-0000-0000-0000-000000000000"
}
ephemeral "vault_azure_access_credentials" "creds" {
mount_id = vault_azure_secret_backend.azure.id
backend = vault_azure_secret_backend.azure.path
role = vault_azure_secret_backend_role.azurerole.role
}
```
## Argument Reference
The following arguments are supported:
* `mount_id` - (Optional) If value is set, will defer provisioning the ephemeral resource until
`terraform apply`. For more details, please refer to the official documentation around
[using ephemeral resources in the Vault Provider](https://registry.terraform.io/providers/hashicorp/vault/latest/docs/guides/using_ephemeral_resources).
* `namespace` - (Optional) The namespace of the target resource.
The value should not contain leading or trailing forward slashes.
The `namespace` is always relative to the provider's
configured [namespace](/docs/providers/vault/index.html#namespace).
*Available only for Vault Enterprise*.
* `backend` - (Required) Path to the mounted Azure Secrets Engine where the role resides.
* `role` - (Required) The name of the Azure role to generate credentials for.
* `validate_creds` - (Optional) Whether generated credentials should be validated before being returned.
* `num_sequential_successes` - (Optional) If 'validate_creds' is true, the number of sequential successes required to validate generated credentials. Defaults to 4.
* `num_seconds_between_tests` - (Optional) If 'validate_creds' is true, the number of seconds to wait between each test of generated credentials. Defaults to 1.
* `max_cred_validation_seconds` - (Optional) If 'validate_creds' is true, the number of seconds after which to give up validating credentials. Defaults to 300.
* `subscription_id` - (Optional) The subscription ID to use during credential validation. Defaults to the subscription ID configured in the Vault backend.
* `tenant_id` - (Optional) The tenant ID to use during credential validation. Defaults to the tenant ID configured in the Vault backend.
* `environment` - (Optional) The Azure environment to use during credential validation. Defaults to the Azure Public Cloud. Some possible values: AzurePublicCloud, AzureUSGovernmentCloud.
## Attributes Reference
In addition to the arguments above, the following attributes are exported:
* `client_id` - The Azure AD Application's client ID.
* `client_secret` - The client secret for the Azure AD Application.
* `lease_id` - The lease identifier assigned by Vault.
* `lease_duration` - The duration of the secret lease in seconds.
* `lease_start_time` - The time when the lease was read, in RFC3339 format.
* `lease_renewable` - True if the lease can be renewed.