diff --git a/CHANGELOG.md b/CHANGELOG.md index fbf3fae61..5e1f0c3a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ FEATURES: * Add support for new WIF fields in `vault_azure_secret_backend`. Requires Vault 1.17+. *Available only for Vault Enterprise* ([#2250](https://github.com/hashicorp/terraform-provider-vault/pull/2250)) * Add support for new WIF fields in `vault_aws_auth_backend_client`. Requires Vault 1.17+. *Available only for Vault Enterprise* ([#2243](https://github.com/hashicorp/terraform-provider-vault/pull/2243)). * Add support for new WIF fields in `vault_gcp_auth_backend` ([#2256](https://github.com/hashicorp/terraform-provider-vault/pull/2256)) +* Add support for new WIF fields in `vault_azure_auth_backend_config`. Requires Vault 1.17+. *Available only for Vault Enterprise* ([#2254](https://github.com/hashicorp/terraform-provider-vault/pull/2254)). * Add new data source and resource `vault_pki_secret_backend_config_est`. Requires Vault 1.16+. *Available only for Vault Enterprise* ([#2246](https://github.com/hashicorp/terraform-provider-vault/pull/2246)) IMPROVEMENTS: diff --git a/vault/resource_auth_backend.go b/vault/resource_auth_backend.go index 7dabc045d..c789a4c53 100644 --- a/vault/resource_auth_backend.go +++ b/vault/resource_auth_backend.go @@ -6,34 +6,34 @@ package vault import ( "context" "errors" - "fmt" + "log" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/vault/api" - "github.com/hashicorp/terraform-provider-vault/internal/consts" "github.com/hashicorp/terraform-provider-vault/internal/provider" "github.com/hashicorp/terraform-provider-vault/util" "github.com/hashicorp/terraform-provider-vault/util/mountutil" + "github.com/hashicorp/vault/api" ) func AuthBackendResource() *schema.Resource { return provider.MustAddMountMigrationSchema(&schema.Resource{ SchemaVersion: 1, - Create: authBackendWrite, - Delete: authBackendDelete, - Read: provider.ReadWrapper(authBackendRead), - Update: authBackendUpdate, + CreateContext: authBackendWrite, + DeleteContext: authBackendDelete, + ReadContext: provider.ReadContextWrapper(authBackendRead), + UpdateContext: authBackendUpdate, Importer: &schema.ResourceImporter{ - State: schema.ImportStatePassthrough, + StateContext: schema.ImportStatePassthroughContext, }, MigrateState: resourceAuthBackendMigrateState, CustomizeDiff: getMountCustomizeDiffFunc(consts.FieldPath), Schema: map[string]*schema.Schema{ - "type": { + consts.FieldType: { Type: schema.TypeString, Required: true, ForceNew: true, @@ -51,85 +51,100 @@ func AuthBackendResource() *schema.Resource { }, }, - "description": { + consts.FieldDescription: { Type: schema.TypeString, Optional: true, Description: "The description of the auth backend", }, - "local": { + consts.FieldLocal: { Type: schema.TypeBool, ForceNew: true, Optional: true, Description: "Specifies if the auth method is local only", }, - "accessor": { + consts.FieldAccessor: { Type: schema.TypeString, Computed: true, Description: "The accessor of the auth backend", }, - "tune": authMountTuneSchema(), + consts.FieldIdentityTokenKey: { + Type: schema.TypeString, + Optional: true, + Description: "The key to use for signing identity tokens.", + }, + + consts.FieldTune: authMountTuneSchema(), }, }, false) } -func authBackendWrite(d *schema.ResourceData, meta interface{}) error { +func authBackendWrite(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { client, e := provider.GetClient(d, meta) if e != nil { - return e + return diag.FromErr(e) } - mountType := d.Get("type").(string) + mountType := d.Get(consts.FieldType).(string) path := d.Get(consts.FieldPath).(string) if path == "" { path = mountType } + config := &api.MountConfigInput{} + useAPIver117Ent := provider.IsAPISupported(meta, provider.VaultVersion117) && provider.IsEnterpriseSupported(meta) + if useAPIver117Ent { + if v, ok := d.GetOk(consts.FieldIdentityTokenKey); ok { + config.IdentityTokenKey = v.(string) + } + } + options := &api.EnableAuthOptions{ Type: mountType, - Description: d.Get("description").(string), - Local: d.Get("local").(bool), + Description: d.Get(consts.FieldDescription).(string), + Local: d.Get(consts.FieldLocal).(bool), + Config: *config, } log.Printf("[DEBUG] Writing auth %q to Vault", path) - if err := client.Sys().EnableAuthWithOptions(path, options); err != nil { - return fmt.Errorf("error writing to Vault: %s", err) + if err := client.Sys().EnableAuthWithOptionsWithContext(ctx, path, options); err != nil { + return diag.Errorf("error writing to Vault: %s", err) } d.SetId(path) - return authBackendUpdate(d, meta) + return authBackendUpdate(ctx, d, meta) } -func authBackendDelete(d *schema.ResourceData, meta interface{}) error { +func authBackendDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { client, e := provider.GetClient(d, meta) if e != nil { - return e + return diag.FromErr(e) } path := d.Id() log.Printf("[DEBUG] Deleting auth %s from Vault", path) - if err := client.Sys().DisableAuth(path); err != nil { - return fmt.Errorf("error disabling auth from Vault: %s", err) + if err := client.Sys().DisableAuthWithContext(ctx, path); err != nil { + return diag.Errorf("error disabling auth from Vault: %s", err) } return nil } -func authBackendRead(d *schema.ResourceData, meta interface{}) error { +func authBackendRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { client, e := provider.GetClient(d, meta) if e != nil { - return e + return diag.FromErr(e) } path := d.Id() - mount, err := mountutil.GetAuthMount(context.Background(), client, path) + mount, err := mountutil.GetAuthMount(ctx, client, path) if errors.Is(err, mountutil.ErrMountNotFound) { log.Printf("[WARN] Mount %q not found, removing from state.", path) d.SetId("") @@ -137,32 +152,36 @@ func authBackendRead(d *schema.ResourceData, meta interface{}) error { } if err != nil { - return err + return diag.FromErr(err) } - if err := d.Set("type", mount.Type); err != nil { - return err + if err := d.Set(consts.FieldType, mount.Type); err != nil { + return diag.FromErr(err) } if err := d.Set(consts.FieldPath, path); err != nil { - return err + return diag.FromErr(err) } - if err := d.Set("description", mount.Description); err != nil { - return err + if err := d.Set(consts.FieldDescription, mount.Description); err != nil { + return diag.FromErr(err) } - if err := d.Set("local", mount.Local); err != nil { - return err + if err := d.Set(consts.FieldLocal, mount.Local); err != nil { + return diag.FromErr(err) } - if err := d.Set("accessor", mount.Accessor); err != nil { - return err + if err := d.Set(consts.FieldAccessor, mount.Accessor); err != nil { + return diag.FromErr(err) } + // TODO: uncomment when identity token key is being returned on the read mount endpoint + //if err := d.Set(consts.FieldIdentityTokenKey, mount.Config.IdentityTokenKey); err != nil { + // return diag.FromErr(err) + //} return nil } -func authBackendUpdate(d *schema.ResourceData, meta interface{}) error { +func authBackendUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { client, e := provider.GetClient(d, meta) if e != nil { - return e + return diag.FromErr(e) } path := d.Id() @@ -171,38 +190,44 @@ func authBackendUpdate(d *schema.ResourceData, meta interface{}) error { if !d.IsNewResource() { path, e = util.Remount(d, client, consts.FieldPath, true) if e != nil { - return e + return diag.FromErr(e) } } - backendType := d.Get("type").(string) - var input api.MountConfigInput + backendType := d.Get(consts.FieldType).(string) + var config api.MountConfigInput var callTune bool - if d.HasChange("tune") { + if d.HasChange(consts.FieldTune) { log.Printf("[INFO] Auth '%q' tune configuration changed", path) - if raw, ok := d.GetOk("tune"); ok { + if raw, ok := d.GetOk(consts.FieldTune); ok { log.Printf("[DEBUG] Writing %s auth tune to '%q'", backendType, path) - input = expandAuthMethodTune(raw.(*schema.Set).List()) + config = expandAuthMethodTune(raw.(*schema.Set).List()) } callTune = true } - if d.HasChange("description") && !d.IsNewResource() { - desc := d.Get("description").(string) - input.Description = &desc + if d.HasChanges(consts.FieldIdentityTokenKey, consts.FieldDescription) && !d.IsNewResource() { + desc := d.Get(consts.FieldDescription).(string) + config.Description = &desc + + useAPIVer117Ent := provider.IsAPISupported(meta, provider.VaultVersion117) && provider.IsEnterpriseSupported(meta) + if useAPIVer117Ent { + config.IdentityTokenKey = d.Get(consts.FieldIdentityTokenKey).(string) + } + callTune = true } if callTune { - if err := tuneMount(client, "auth/"+path, input); err != nil { - return err + if err := tuneMount(client, "auth/"+path, config); err != nil { + return diag.FromErr(e) } log.Printf("[INFO] Written %s auth tune to '%q'", backendType, path) } - return authBackendRead(d, meta) + return authBackendRead(ctx, d, meta) } diff --git a/vault/resource_aws_secret_backend.go b/vault/resource_aws_secret_backend.go index 540aa54d9..1c317d541 100644 --- a/vault/resource_aws_secret_backend.go +++ b/vault/resource_aws_secret_backend.go @@ -184,14 +184,14 @@ func awsSecretBackendCreate(ctx context.Context, d *schema.ResourceData, meta in DefaultLeaseTTL: fmt.Sprintf("%ds", defaultTTL), MaxLeaseTTL: fmt.Sprintf("%ds", maxTTL), } - useAPIVer116 := provider.IsAPISupported(meta, provider.VaultVersion116) + useAPIVer116 := provider.IsAPISupported(meta, provider.VaultVersion116) && provider.IsEnterpriseSupported(meta) if useAPIVer116 { identityTokenKey := d.Get(consts.FieldIdentityTokenKey).(string) if identityTokenKey != "" { mountConfig.IdentityTokenKey = identityTokenKey } } - err := client.Sys().Mount(path, &api.MountInput{ + err := client.Sys().MountWithContext(ctx, path, &api.MountInput{ Type: consts.MountTypeAWS, Description: description, Local: local, @@ -230,7 +230,7 @@ func awsSecretBackendCreate(ctx context.Context, d *schema.ResourceData, meta in data[consts.FieldRegion] = region } - _, err = client.Logical().Write(path+"/config/root", data) + _, err = client.Logical().WriteWithContext(ctx, path+"/config/root", data) if err != nil { return diag.Errorf("error configuring root credentials for %q: %s", path, err) } @@ -244,7 +244,7 @@ func awsSecretBackendCreate(ctx context.Context, d *schema.ResourceData, meta in } func awsSecretBackendRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - useAPIVer116 := provider.IsAPISupported(meta, provider.VaultVersion116) + useAPIVer116 := provider.IsAPISupported(meta, provider.VaultVersion116) && provider.IsEnterpriseSupported(meta) client, e := provider.GetClient(d, meta) if e != nil { @@ -269,7 +269,7 @@ func awsSecretBackendRead(ctx context.Context, d *schema.ResourceData, meta inte log.Printf("[DEBUG] Read AWS backend mount %q from Vault", path) log.Printf("[DEBUG] Read AWS secret backend config/root %s", path) - resp, err := client.Logical().Read(path + "/config/root") + resp, err := client.Logical().ReadWithContext(ctx, path+"/config/root") if err != nil { // This is here to support backwards compatibility with Vault. Read operations on the config/root // endpoint were just added and haven't been released yet, and so in currently released versions @@ -319,19 +319,33 @@ func awsSecretBackendRead(ctx context.Context, d *schema.ResourceData, meta inte } } - d.Set(consts.FieldPath, path) - d.Set(consts.FieldDescription, mount.Description) - d.Set(consts.FieldDefaultLeaseTTL, mount.Config.DefaultLeaseTTL) - d.Set(consts.FieldMaxLeaseTTL, mount.Config.MaxLeaseTTL) - d.Set(consts.FieldLocal, mount.Local) + if err := d.Set(consts.FieldPath, path); err != nil { + return diag.FromErr(err) + } + if err := d.Set(consts.FieldDescription, mount.Description); err != nil { + return diag.FromErr(err) + } + if err := d.Set(consts.FieldDefaultLeaseTTL, mount.Config.DefaultLeaseTTL); err != nil { + return diag.FromErr(err) + } + if err := d.Set(consts.FieldMaxLeaseTTL, mount.Config.MaxLeaseTTL); err != nil { + return diag.FromErr(err) + } + if err := d.Set(consts.FieldLocal, mount.Local); err != nil { + return diag.FromErr(err) + } if useAPIVer116 { - d.Set(consts.FieldIdentityTokenKey, mount.Config.IdentityTokenKey) + if err := d.Set(consts.FieldIdentityTokenKey, mount.Config.IdentityTokenKey); err != nil { + return diag.FromErr(err) + } } return nil } func awsSecretBackendUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + useAPIVer116 := provider.IsAPISupported(meta, provider.VaultVersion116) && provider.IsEnterpriseSupported(meta) + client, e := provider.GetClient(d, meta) if e != nil { return diag.FromErr(e) @@ -352,7 +366,6 @@ func awsSecretBackendUpdate(ctx context.Context, d *schema.ResourceData, meta in MaxLeaseTTL: fmt.Sprintf("%ds", d.Get(consts.FieldMaxLeaseTTL)), } - useAPIVer116 := provider.IsAPISupported(meta, provider.VaultVersion116) if useAPIVer116 { identityTokenKey := d.Get(consts.FieldIdentityTokenKey).(string) if identityTokenKey != "" { @@ -360,7 +373,7 @@ func awsSecretBackendUpdate(ctx context.Context, d *schema.ResourceData, meta in } } log.Printf("[DEBUG] Updating mount config input for %q", path) - err := client.Sys().TuneMount(path, config) + err := client.Sys().TuneMountWithContext(ctx, path, config) if err != nil { return diag.Errorf("error updating mount config input for %q: %s", path, err) } @@ -379,7 +392,6 @@ func awsSecretBackendUpdate(ctx context.Context, d *schema.ResourceData, meta in } } - useAPIVer116 := provider.IsAPISupported(meta, provider.VaultVersion116) if useAPIVer116 { identityTokenAudience := d.Get(consts.FieldIdentityTokenAudience).(string) if identityTokenAudience != "" { @@ -400,7 +412,7 @@ func awsSecretBackendUpdate(ctx context.Context, d *schema.ResourceData, meta in data[consts.FieldRegion] = region } - _, err := client.Logical().Write(path+"/config/root", data) + _, err := client.Logical().WriteWithContext(ctx, path+"/config/root", data) if err != nil { return diag.Errorf("error configuring root credentials for %q: %s", path, err) } @@ -413,7 +425,7 @@ func awsSecretBackendUpdate(ctx context.Context, d *schema.ResourceData, meta in return awsSecretBackendRead(ctx, d, meta) } -func awsSecretBackendDelete(_ context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +func awsSecretBackendDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { client, e := provider.GetClient(d, meta) if e != nil { return diag.FromErr(e) @@ -422,7 +434,7 @@ func awsSecretBackendDelete(_ context.Context, d *schema.ResourceData, meta inte path := d.Id() log.Printf("[DEBUG] Unmounting AWS backend %q", path) - err := client.Sys().Unmount(path) + err := client.Sys().UnmountWithContext(ctx, path) if err != nil { return diag.Errorf("error unmounting AWS backend from %q: %s", path, err) } diff --git a/vault/resource_azure_auth_backend_config.go b/vault/resource_azure_auth_backend_config.go index c00ef9a85..dc5e85ffe 100644 --- a/vault/resource_azure_auth_backend_config.go +++ b/vault/resource_azure_auth_backend_config.go @@ -4,13 +4,16 @@ package vault import ( + "context" "fmt" + "log" "regexp" "strings" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - + "github.com/hashicorp/terraform-provider-vault/internal/consts" "github.com/hashicorp/terraform-provider-vault/internal/provider" ) @@ -18,17 +21,16 @@ var azureAuthBackendConfigFromPathRegex = regexp.MustCompile("^auth/(.+)/config$ func azureAuthBackendConfigResource() *schema.Resource { return &schema.Resource{ - Create: azureAuthBackendWrite, - Read: provider.ReadWrapper(azureAuthBackendRead), - Update: azureAuthBackendWrite, - Delete: azureAuthBackendDelete, - Exists: azureAuthBackendExists, + CreateContext: azureAuthBackendWrite, + ReadContext: provider.ReadContextWrapper(azureAuthBackendRead), + UpdateContext: azureAuthBackendWrite, + DeleteContext: azureAuthBackendDelete, Importer: &schema.ResourceImporter{ - State: schema.ImportStatePassthrough, + StateContext: schema.ImportStatePassthroughContext, }, Schema: map[string]*schema.Schema{ - "backend": { + consts.FieldBackend: { Type: schema.TypeString, Optional: true, Description: "Unique name of the auth backend to configure.", @@ -39,73 +41,92 @@ func azureAuthBackendConfigResource() *schema.Resource { return strings.Trim(v.(string), "/") }, }, - "tenant_id": { + consts.FieldTenantID: { Type: schema.TypeString, Required: true, Description: "The tenant id for the Azure Active Directory organization.", Sensitive: true, }, - "client_id": { + consts.FieldClientID: { Type: schema.TypeString, Optional: true, Description: "The client id for credentials to query the Azure APIs. Currently read permissions to query compute resources are required.", Sensitive: true, }, - "client_secret": { + consts.FieldClientSecret: { Type: schema.TypeString, Optional: true, Description: "The client secret for credentials to query the Azure APIs", Sensitive: true, }, - "resource": { + consts.FieldResource: { Type: schema.TypeString, Required: true, Description: "The configured URL for the application registered in Azure Active Directory.", }, - "environment": { + consts.FieldEnvironment: { Type: schema.TypeString, Optional: true, Description: "The Azure cloud environment. Valid values: AzurePublicCloud, AzureUSGovernmentCloud, AzureChinaCloud, AzureGermanCloud.", }, + consts.FieldIdentityTokenAudience: { + Type: schema.TypeString, + Optional: true, + Description: "The audience claim value.", + }, + consts.FieldIdentityTokenTTL: { + Type: schema.TypeInt, + Optional: true, + Computed: true, + Description: "The TTL of generated identity tokens in seconds.", + }, }, } } -func azureAuthBackendWrite(d *schema.ResourceData, meta interface{}) error { +func azureAuthBackendWrite(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { config, e := provider.GetClient(d, meta) if e != nil { - return e + return diag.FromErr(e) } // if backend comes from the config, it won't have the StateFunc // applied yet, so we need to apply it again. - backend := d.Get("backend").(string) - tenantId := d.Get("tenant_id").(string) - clientId := d.Get("client_id").(string) - clientSecret := d.Get("client_secret").(string) - resource := d.Get("resource").(string) - environment := d.Get("environment").(string) + backend := d.Get(consts.FieldBackend).(string) + tenantId := d.Get(consts.FieldTenantID).(string) + clientId := d.Get(consts.FieldClientID).(string) + clientSecret := d.Get(consts.FieldClientSecret).(string) + resource := d.Get(consts.FieldResource).(string) + environment := d.Get(consts.FieldEnvironment).(string) + identityTokenAud := d.Get(consts.FieldIdentityTokenAudience).(string) + identityTokenTTL := d.Get(consts.FieldIdentityTokenTTL).(int) path := azureAuthBackendConfigPath(backend) data := map[string]interface{}{ - "tenant_id": tenantId, - "client_id": clientId, - "client_secret": clientSecret, - "resource": resource, - "environment": environment, + consts.FieldTenantID: tenantId, + consts.FieldClientID: clientId, + consts.FieldClientSecret: clientSecret, + consts.FieldResource: resource, + consts.FieldEnvironment: environment, + } + + useAPIVer117Ent := provider.IsAPISupported(meta, provider.VaultVersion117) && provider.IsEnterpriseSupported(meta) + if useAPIVer117Ent { + data[consts.FieldIdentityTokenAudience] = identityTokenAud + data[consts.FieldIdentityTokenTTL] = identityTokenTTL } log.Printf("[DEBUG] Writing Azure auth backend config to %q", path) - _, err := config.Logical().Write(path, data) + _, err := config.Logical().WriteWithContext(ctx, path, data) if err != nil { - return fmt.Errorf("error writing to %q: %s", path, err) + return diag.Errorf("error writing to %q: %s", path, err) } log.Printf("[DEBUG] Wrote Azure auth backend config to %q", path) d.SetId(path) - return azureAuthBackendRead(d, meta) + return azureAuthBackendRead(ctx, d, meta) } func azureAuthBackendConfigBackendFromPath(path string) (string, error) { @@ -119,16 +140,16 @@ func azureAuthBackendConfigBackendFromPath(path string) (string, error) { return res[1], nil } -func azureAuthBackendRead(d *schema.ResourceData, meta interface{}) error { +func azureAuthBackendRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { config, e := provider.GetClient(d, meta) if e != nil { - return e + return diag.FromErr(e) } path := d.Id() log.Printf("[DEBUG] Reading Azure auth backend config") - secret, err := config.Logical().Read(path) + secret, err := config.Logical().ReadWithContext(ctx, path) if err != nil { - return fmt.Errorf("error reading Azure auth backend config from %q: %s", path, err) + return diag.Errorf("error reading Azure auth backend config from %q: %s", path, err) } log.Printf("[DEBUG] Read Azure auth backend config") @@ -139,47 +160,58 @@ func azureAuthBackendRead(d *schema.ResourceData, meta interface{}) error { } backend, err := azureAuthBackendConfigBackendFromPath(path) if err != nil { - return fmt.Errorf("invalid path %q for azure auth backend config: %s", path, err) + return diag.Errorf("invalid path %q for azure auth backend config: %s", path, err) } - d.Set("backend", backend) - d.Set("tenant_id", secret.Data["tenant_id"]) - d.Set("client_id", secret.Data["client_id"]) - if v, ok := secret.Data["client_secret"]; ok { - d.Set("client_secret", v) + if err := d.Set(consts.FieldBackend, backend); err != nil { + return diag.FromErr(err) } - d.Set("resource", secret.Data["resource"]) - d.Set("environment", secret.Data["environment"]) - return nil -} -func azureAuthBackendDelete(d *schema.ResourceData, meta interface{}) error { - config, e := provider.GetClient(d, meta) - if e != nil { - return e + fields := []string{ + consts.FieldTenantID, + consts.FieldClientID, + consts.FieldClientSecret, + consts.FieldResource, + consts.FieldEnvironment, } - log.Printf("[DEBUG] Deleting Azure auth backend config from %q", d.Id()) - _, err := config.Logical().Delete(d.Id()) - if err != nil { - return fmt.Errorf("error deleting Azure auth backend config from %q: %s", d.Id(), err) + for _, k := range fields { + if v, ok := secret.Data[k]; ok { + if err := d.Set(k, v); err != nil { + return diag.FromErr(err) + } + } + } + + useAPIVer117Ent := provider.IsAPISupported(meta, provider.VaultVersion117) && provider.IsEnterpriseSupported(meta) + if useAPIVer117Ent { + if v, ok := secret.Data[consts.FieldIdentityTokenAudience]; ok { + if err := d.Set(consts.FieldIdentityTokenAudience, v); err != nil { + return diag.FromErr(err) + } + } + if v, ok := secret.Data[consts.FieldIdentityTokenTTL]; ok { + if err := d.Set(consts.FieldIdentityTokenTTL, v); err != nil { + return diag.FromErr(err) + } + } } - log.Printf("[DEBUG] Deleted Azure auth backend config from %q", d.Id()) return nil } -func azureAuthBackendExists(d *schema.ResourceData, meta interface{}) (bool, error) { +func azureAuthBackendDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { config, e := provider.GetClient(d, meta) if e != nil { - return false, e + return diag.FromErr(e) } - log.Printf("[DEBUG] Checking if Azure auth backend is configured at %q", d.Id()) - secret, err := config.Logical().Read(d.Id()) + log.Printf("[DEBUG] Deleting Azure auth backend config from %q", d.Id()) + _, err := config.Logical().DeleteWithContext(ctx, d.Id()) if err != nil { - return true, fmt.Errorf("error checking if Azure auth backend is configured at %q: %s", d.Id(), err) + return diag.Errorf("error deleting Azure auth backend config from %q: %s", d.Id(), err) } - log.Printf("[DEBUG] Checked if Azure auth backend is configured at %q", d.Id()) - return secret != nil, nil + log.Printf("[DEBUG] Deleted Azure auth backend config from %q", d.Id()) + + return nil } func azureAuthBackendConfigPath(path string) string { diff --git a/vault/resource_azure_auth_backend_config_test.go b/vault/resource_azure_auth_backend_config_test.go index f6177763d..4f9fe69b2 100644 --- a/vault/resource_azure_auth_backend_config_test.go +++ b/vault/resource_azure_auth_backend_config_test.go @@ -11,6 +11,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-vault/internal/consts" "github.com/hashicorp/terraform-provider-vault/internal/provider" "github.com/hashicorp/terraform-provider-vault/testutil" ) @@ -30,7 +31,7 @@ func TestAccAzureAuthBackendConfig_import(t *testing.T) { ResourceName: "vault_azure_auth_backend_config.config", ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"client_secret"}, + ImportStateVerifyIgnore: []string{consts.FieldClientSecret}, }, }, }) @@ -55,6 +56,46 @@ func TestAccAzureAuthBackendConfig_basic(t *testing.T) { }) } +func TestAccAzureAuthBackend_wif(t *testing.T) { + backend := acctest.RandomWithPrefix("tf-test-azure") + updatedBackend := acctest.RandomWithPrefix("tf-test-azure-updated") + + resourceType := "vault_azure_auth_backend_config" + resourceName := resourceType + ".config" + resource.Test(t, resource.TestCase{ + ProviderFactories: providerFactories, + PreCheck: func() { + testutil.TestEntPreCheck(t) + SkipIfAPIVersionLT(t, testProvider.Meta(), provider.VaultVersion117) + }, + CheckDestroy: testCheckMountDestroyed(resourceType, consts.MountTypeAzure, consts.FieldBackend), + Steps: []resource.TestStep{ + { + Config: testAccAzureAuthBackendConfig_wifBasic(backend), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, consts.FieldBackend, backend), + resource.TestCheckResourceAttr(resourceName, consts.FieldTenantID, "11111111-2222-3333-4444-222222222222"), + resource.TestCheckResourceAttr(resourceName, consts.FieldClientID, "11111111-2222-3333-4444-333333333333"), + resource.TestCheckResourceAttr(resourceName, consts.FieldResource, "http://vault.hashicorp.com"), + resource.TestCheckResourceAttr(resourceName, consts.FieldIdentityTokenAudience, "wif-audience"), + resource.TestCheckResourceAttr(resourceName, consts.FieldIdentityTokenTTL, "600"), + ), + }, + { + Config: testAccAzureAuthBackendConfig_wifUpdated(updatedBackend), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, consts.FieldBackend, updatedBackend), + resource.TestCheckResourceAttr(resourceName, consts.FieldTenantID, "22222222-3333-4444-5555-333333333333"), + resource.TestCheckResourceAttr(resourceName, consts.FieldClientID, "22222222-3333-4444-5555-444444444444"), + resource.TestCheckResourceAttr(resourceName, consts.FieldResource, "http://vault.hashicorp.com"), + resource.TestCheckResourceAttr(resourceName, consts.FieldIdentityTokenAudience, "wif-audience-updated"), + resource.TestCheckResourceAttr(resourceName, consts.FieldIdentityTokenTTL, "1800"), + ), + }, + }, + }) +} + func testAccCheckAzureAuthBackendConfigDestroy(s *terraform.State) error { config := testProvider.Meta().(*provider.ProviderMeta).MustGetClient() @@ -118,11 +159,10 @@ func testAccAzureAuthBackendConfigCheck_attrs(backend string) resource.TestCheck return fmt.Errorf("Azure auth not configured at %q", endpoint) } attrs := map[string]string{ - "tenant_id": "tenant_id", - "client_id": "client_id", - //"client_secret": "client_secret", - "resource": "resource", - "environment": "environment", + consts.FieldTenantID: consts.FieldTenantID, + consts.FieldClientID: consts.FieldClientID, + consts.FieldResource: consts.FieldResource, + consts.FieldEnvironment: consts.FieldEnvironment, } for stateAttr, apiAttr := range attrs { if resp.Data[apiAttr] == nil && instanceState.Attributes[stateAttr] == "" { @@ -152,3 +192,37 @@ resource "vault_azure_auth_backend_config" "config" { resource = "http://vault.hashicorp.com" }`, backend) } + +func testAccAzureAuthBackendConfig_wifBasic(backend string) string { + return fmt.Sprintf(` +resource "vault_auth_backend" "azure" { + path = "%s" + type = "azure" +} + +resource "vault_azure_auth_backend_config" "config" { + backend = vault_auth_backend.azure.path + tenant_id = "11111111-2222-3333-4444-222222222222" + client_id = "11111111-2222-3333-4444-333333333333" + resource = "http://vault.hashicorp.com" + identity_token_audience = "wif-audience" + identity_token_ttl = 600 +}`, backend) +} + +func testAccAzureAuthBackendConfig_wifUpdated(backend string) string { + return fmt.Sprintf(` +resource "vault_auth_backend" "azure" { + path = "%s" + type = "azure" +} + +resource "vault_azure_auth_backend_config" "config" { + backend = vault_auth_backend.azure.path + tenant_id = "22222222-3333-4444-5555-333333333333" + client_id = "22222222-3333-4444-5555-444444444444" + resource = "http://vault.hashicorp.com" + identity_token_audience = "wif-audience-updated" + identity_token_ttl = 1800 +}`, backend) +} diff --git a/vault/resource_azure_secret_backend.go b/vault/resource_azure_secret_backend.go index dbb46b0cf..cf4727e94 100644 --- a/vault/resource_azure_secret_backend.go +++ b/vault/resource_azure_secret_backend.go @@ -137,7 +137,7 @@ func azureSecretBackendCreate(ctx context.Context, d *schema.ResourceData, meta Description: description, Config: mountConfig, } - if err := client.Sys().Mount(path, input); err != nil { + if err := client.Sys().MountWithContext(ctx, path, input); err != nil { return diag.Errorf("error mounting to %q: %s", path, err) } @@ -248,6 +248,20 @@ func azureSecretBackendUpdate(ctx context.Context, d *schema.ResourceData, meta return diag.FromErr(err) } + if d.HasChanges(consts.FieldIdentityTokenKey, consts.FieldDescription) { + desc := d.Get(consts.FieldDescription).(string) + config := api.MountConfigInput{ + Description: &desc, + } + useAPIVer117Ent := provider.IsAPISupported(meta, provider.VaultVersion117) && provider.IsEnterpriseSupported(meta) + if useAPIVer117Ent { + config.IdentityTokenKey = d.Get(consts.FieldIdentityTokenKey).(string) + } + if err := client.Sys().TuneMountWithContext(ctx, path, config); err != nil { + return diag.FromErr(err) + } + } + data := azureSecretBackendRequestData(d, meta) if len(data) > 0 { _, err := client.Logical().WriteWithContext(ctx, azureSecretBackendPath(path), data) diff --git a/vault/resource_azure_secret_backend_test.go b/vault/resource_azure_secret_backend_test.go index e1d596c67..531dae66c 100644 --- a/vault/resource_azure_secret_backend_test.go +++ b/vault/resource_azure_secret_backend_test.go @@ -140,7 +140,7 @@ func TestAccAzureSecretBackend_wif(t *testing.T) { resource.TestCheckResourceAttr(resourceName, consts.FieldIdentityTokenTTL, "1800"), ), }, - testutil.GetImportTestStep(resourceName, false, nil), + testutil.GetImportTestStep(resourceName, false, nil, consts.FieldDisableRemount), }, }) } diff --git a/website/docs/r/azure_auth_backend_config.html.md b/website/docs/r/azure_auth_backend_config.html.md index 64de0670b..f051dfdfc 100644 --- a/website/docs/r/azure_auth_backend_config.html.md +++ b/website/docs/r/azure_auth_backend_config.html.md @@ -26,6 +26,22 @@ for more details. ## Example Usage +You can setup the Azure auth engine with Workload Identity Federation (WIF) for a secret-less configuration: +```hcl +resource "vault_auth_backend" "example" { + type = "azure" + identity_token_key = "example-key" +} + +resource "vault_azure_auth_backend_config" "example" { + backend = vault_auth_backend.example.path + tenant_id = "11111111-2222-3333-4444-555555555555" + client_id = "11111111-2222-3333-4444-555555555555" + identity_token_audience = "" + identity_token_ttl = "" +} +``` + ```hcl resource "vault_auth_backend" "example" { type = "azure" @@ -68,6 +84,12 @@ The following arguments are supported: AzurePublicCloud, AzureUSGovernmentCloud, AzureChinaCloud, AzureGermanCloud. Defaults to `AzurePublicCloud`. +* `identity_token_audience` - (Optional) The audience claim value for plugin identity tokens. Requires Vault 1.17+. + *Available only for Vault Enterprise* + +* `identity_token_ttl` - (Optional) The TTL of generated identity tokens in seconds. + Defaults to 1 hour. Uses [duration format strings](https://developer.hashicorp.com/vault/docs/concepts/duration-format). + Requires Vault 1.17+. *Available only for Vault Enterprise* ## Attributes Reference