From 63c5a309b78816c36f40d2c028b139e1361ba92a Mon Sep 17 00:00:00 2001 From: Leland Ursu Date: Tue, 19 Nov 2024 13:46:58 -0500 Subject: [PATCH 1/5] Update CODEOWNERS (#2368) --- CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index a765f7ea9..7476e1d8a 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1 +1 @@ -* @hashicorp/vault-ecosystem +* @hashicorp/vault From 4473d751536080664a2580b1f432800038c608ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Andrieux?= Date: Fri, 22 Nov 2024 22:56:59 +0100 Subject: [PATCH 2/5] vault_database_secret_backend_connection: Add support for password_authentication on PostgreSQL (#2371) Co-authored-by: vinay-gopalan <86625824+vinay-gopalan@users.noreply.github.com> --- CHANGELOG.md | 4 +++ ...urce_database_secret_backend_connection.go | 18 +++++++++++ ...database_secret_backend_connection_test.go | 30 ++++++++++++++++++- .../r/database_secret_backend_connection.md | 10 +++++-- 4 files changed, 58 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb789b06f..0c944ebfd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## Unreleased +FEATURES: + +* Update `vault_database_secret_backend_connection`to support `password_authentication` for PostgreSQL, allowing to encrypt password before being passed to PostgreSQL ([#2371](https://github.com/hashicorp/terraform-provider-vault/pull/2371)) + ## 4.5.0 (Nov 19, 2024) FEATURES: diff --git a/vault/resource_database_secret_backend_connection.go b/vault/resource_database_secret_backend_connection.go index 50bf82028..d394d5d54 100644 --- a/vault/resource_database_secret_backend_connection.go +++ b/vault/resource_database_secret_backend_connection.go @@ -836,6 +836,12 @@ func postgresConnectionStringResource() *schema.Resource { Optional: true, Description: "If set, allows onboarding static roles with a rootless connection configuration.", } + r.Schema["password_authentication"] = &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Default: "password", + Description: "When set to `scram-sha-256`, passwords will be hashed by Vault before being sent to PostgreSQL.", + } return r } @@ -1150,6 +1156,12 @@ func getPostgresConnectionDetailsFromResponse(d *schema.ResourceData, prefix str } } + if provider.IsAPISupported(meta, provider.VaultVersion114) { + if v, ok := data["password_authentication"]; ok { + result["password_authentication"] = v.(string) + } + } + if provider.IsAPISupported(meta, provider.VaultVersion118) { if v, ok := data["tls_ca"]; ok { result["tls_ca"] = v.(string) @@ -1571,6 +1583,12 @@ func setPostgresDatabaseConnectionData(d *schema.ResourceData, prefix string, da } } + if provider.IsAPISupported(meta, provider.VaultVersion114) { + if v, ok := d.GetOk(prefix + "password_authentication"); ok { + data["password_authentication"] = v.(string) + } + } + if provider.IsAPISupported(meta, provider.VaultVersion118) && provider.IsEnterpriseSupported(meta) { if v, ok := d.GetOk(prefix + "self_managed"); ok { data["self_managed"] = v.(bool) diff --git a/vault/resource_database_secret_backend_connection_test.go b/vault/resource_database_secret_backend_connection_test.go index 69e85fe17..104aec513 100644 --- a/vault/resource_database_secret_backend_connection_test.go +++ b/vault/resource_database_secret_backend_connection_test.go @@ -812,6 +812,7 @@ func TestAccDatabaseSecretBackendConnection_postgresql(t *testing.T) { resource.TestCheckResourceAttr(testDefaultDatabaseSecretBackendResource, "root_rotation_statements.0", "FOOBAR"), resource.TestCheckResourceAttr(testDefaultDatabaseSecretBackendResource, "verify_connection", "true"), resource.TestCheckResourceAttr(testDefaultDatabaseSecretBackendResource, "postgresql.0.connection_url", connURL), + resource.TestCheckResourceAttr(testDefaultDatabaseSecretBackendResource, "postgresql.0.password_authentication", "password"), resource.TestCheckResourceAttr(testDefaultDatabaseSecretBackendResource, "postgresql.0.max_open_connections", maxOpenConnections), resource.TestCheckResourceAttr(testDefaultDatabaseSecretBackendResource, "postgresql.0.max_idle_connections", maxIdleConnections), resource.TestCheckResourceAttr(testDefaultDatabaseSecretBackendResource, "postgresql.0.max_connection_lifetime", maxConnLifetime), @@ -833,6 +834,12 @@ func TestAccDatabaseSecretBackendConnection_postgresql(t *testing.T) { resource.TestCheckResourceAttr(testDefaultDatabaseSecretBackendResource, "postgresql.0.username_template", ""), ), }, + { + Config: testAccDatabaseSecretBackendConnectionConfig_postgresql_password_authentication(name, backend, parsedURL), + Check: testComposeCheckFuncCommonDatabaseSecretBackend(name, backend, pluginName, + resource.TestCheckResourceAttr(testDefaultDatabaseSecretBackendResource, "postgresql.0.password_authentication", "scram-sha-256"), + ), + }, }, }) } @@ -1771,6 +1778,27 @@ resource "vault_database_secret_backend_connection" "test" { `, path, name, parsedURL.String()) } +func testAccDatabaseSecretBackendConnectionConfig_postgresql_password_authentication(name, path string, parsedURL *url.URL) string { + return fmt.Sprintf(` +resource "vault_mount" "db" { + path = "%s" + type = "database" +} + +resource "vault_database_secret_backend_connection" "test" { + backend = vault_mount.db.path + name = "%s" + allowed_roles = ["dev", "prod"] + root_rotation_statements = ["FOOBAR"] + + postgresql { + connection_url = "%s" + password_authentication = "scram-sha-256" + } +} +`, path, name, parsedURL.String()) +} + func testAccDatabaseSecretBackendConnectionConfig_postgresql_tls(name, path, tlsCA, tlsCert, privateKey string) string { return fmt.Sprintf(` resource "vault_mount" "db" { @@ -1849,7 +1877,7 @@ resource "vault_database_secret_backend_connection" "test" { allowed_roles = ["dev", "prod"] root_rotation_statements = ["FOOBAR"] - snowflake { + snowflake { connection_url = "%s" username = "%s" password = "%s" diff --git a/website/docs/r/database_secret_backend_connection.md b/website/docs/r/database_secret_backend_connection.md index fab521f1a..eaf6bccbd 100644 --- a/website/docs/r/database_secret_backend_connection.md +++ b/website/docs/r/database_secret_backend_connection.md @@ -124,7 +124,7 @@ Exactly one of the nested blocks of configuration options must be supplied. * `connect_timeout` - (Optional) The number of seconds to use as a connection timeout. -* `skip_verification` - (Optional) Skip permissions checks when a connection to Cassandra is first created. +* `skip_verification` - (Optional) Skip permissions checks when a connection to Cassandra is first created. These checks ensure that Vault is able to create roles, but can be resource intensive in clusters with many roles. ### Couchbase Configuration Options @@ -328,8 +328,8 @@ See the [Vault * `password` - (Optional) The root credential password used in the connection URL. -* `self_managed` - (Optional) If set, allows onboarding static roles with a rootless - connection configuration. Mutually exclusive with `username` and `password`. +* `self_managed` - (Optional) If set, allows onboarding static roles with a rootless + connection configuration. Mutually exclusive with `username` and `password`. If set, will force `verify_connection` to be false. Requires Vault 1.18+ Enterprise. * `tls_ca` - (Optional) The x509 CA file for validating the certificate @@ -338,6 +338,10 @@ See the [Vault * `tls_certificate` - (Optional) The x509 client certificate for connecting to the database. Must be PEM encoded. +* `password_authentication` - (Optional) When set to `scram-sha-256`, passwords will be + hashed by Vault before being sent to PostgreSQL. See the [Vault docs](https://www.vaultproject.io/api-docs/secret/databases/postgresql.html#sample-payload) + for an example. Requires Vault 1.14+. + * `private_key` - (Optional) The secret key used for the x509 client certificate. Must be PEM encoded. From 0cbe249861bb5779a7dbe892e41c1a3f9a253f03 Mon Sep 17 00:00:00 2001 From: helenfufu <25168806+helenfufu@users.noreply.github.com> Date: Fri, 22 Nov 2024 14:20:54 -0800 Subject: [PATCH 3/5] Vault 32530 add external id to aws auth backend sts role (#2370) * add external_id to aws_auth_backend_sts_role resource * update docs for aws_auth_backend_sts_role resource * update acceptance tests * only support external_id on vault versions >= 1.17 external_id support for aws auth sts configuration added in 1.17.0: https://github.com/hashicorp/vault/pull/26628 * separate import test for external_id case * update changelog * fix changelog: move change under unreleased * add import test steps and remove separate import tests, add separate withExternalID test * handle error on d.Set * rename testAccAWSAuthBackendSTSRoleConfig_basic to more general testAccAWSAuthBackendSTSRoleConfig * note 1.17 version requirement in doc Co-authored-by: vinay-gopalan <86625824+vinay-gopalan@users.noreply.github.com> --------- Co-authored-by: vinay-gopalan <86625824+vinay-gopalan@users.noreply.github.com> --- CHANGELOG.md | 3 +- vault/resource_aws_auth_backend_sts_role.go | 42 +++++++++-- ...resource_aws_auth_backend_sts_role_test.go | 73 +++++++++++++++---- .../docs/r/aws_auth_backend_sts_role.html.md | 2 + 4 files changed, 97 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c944ebfd..f758c0f52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,9 @@ ## Unreleased -FEATURES: +FEATURES: * Update `vault_database_secret_backend_connection`to support `password_authentication` for PostgreSQL, allowing to encrypt password before being passed to PostgreSQL ([#2371](https://github.com/hashicorp/terraform-provider-vault/pull/2371)) +* Add support for `external_id` field for the `vault_aws_auth_backend_sts_role` resource ([#2370](https://github.com/hashicorp/terraform-provider-vault/pull/2370)) ## 4.5.0 (Nov 19, 2024) diff --git a/vault/resource_aws_auth_backend_sts_role.go b/vault/resource_aws_auth_backend_sts_role.go index b79bf35f8..96e481eb5 100644 --- a/vault/resource_aws_auth_backend_sts_role.go +++ b/vault/resource_aws_auth_backend_sts_role.go @@ -11,6 +11,7 @@ import ( "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" ) @@ -52,6 +53,11 @@ func awsAuthBackendSTSRoleResource() *schema.Resource { return strings.Trim(v.(string), "/") }, }, + consts.FieldExternalID: { + Type: schema.TypeString, + Optional: true, + Description: "External ID expected by the STS role.", + }, }, } } @@ -65,13 +71,20 @@ func awsAuthBackendSTSRoleCreate(d *schema.ResourceData, meta interface{}) error backend := d.Get("backend").(string) accountID := d.Get("account_id").(string) stsRole := d.Get("sts_role").(string) + externalID := d.Get(consts.FieldExternalID).(string) path := awsAuthBackendSTSRolePath(backend, accountID) - log.Printf("[DEBUG] Writing STS role %q to AWS auth backend", path) - _, err := client.Logical().Write(path, map[string]interface{}{ + data := map[string]interface{}{ "sts_role": stsRole, - }) + } + + if provider.IsAPISupported(meta, provider.VaultVersion117) { + data[consts.FieldExternalID] = externalID + } + + log.Printf("[DEBUG] Writing STS role %q to AWS auth backend", path) + _, err := client.Logical().Write(path, data) d.SetId(path) @@ -117,6 +130,15 @@ func awsAuthBackendSTSRoleRead(d *schema.ResourceData, meta interface{}) error { d.Set("backend", backend) d.Set("account_id", accountID) d.Set("sts_role", resp.Data["sts_role"]) + + if provider.IsAPISupported(meta, provider.VaultVersion117) { + if v, ok := resp.Data[consts.FieldExternalID]; ok { + if err := d.Set(consts.FieldExternalID, v); err != nil { + return err + } + } + } + return nil } @@ -127,12 +149,20 @@ func awsAuthBackendSTSRoleUpdate(d *schema.ResourceData, meta interface{}) error } stsRole := d.Get("sts_role").(string) + externalID := d.Get(consts.FieldExternalID).(string) + path := d.Id() - log.Printf("[DEBUG] Updating STS role %q in AWS auth backend", path) - _, err := client.Logical().Write(path, map[string]interface{}{ + data := map[string]interface{}{ "sts_role": stsRole, - }) + } + + if provider.IsAPISupported(meta, provider.VaultVersion117) { + data[consts.FieldExternalID] = externalID + } + + log.Printf("[DEBUG] Updating STS role %q in AWS auth backend", path) + _, err := client.Logical().Write(path, data) if err != nil { return fmt.Errorf("error updating STS role %q in AWS auth backend", path) } diff --git a/vault/resource_aws_auth_backend_sts_role_test.go b/vault/resource_aws_auth_backend_sts_role_test.go index b331594b6..80d241426 100644 --- a/vault/resource_aws_auth_backend_sts_role_test.go +++ b/vault/resource_aws_auth_backend_sts_role_test.go @@ -6,31 +6,55 @@ package vault import ( "fmt" "strconv" + "strings" "testing" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "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" ) -func TestAccAWSAuthBackendSTSRole_import(t *testing.T) { +func TestAccAWSAuthBackendSTSRole_withExternalID(t *testing.T) { backend := acctest.RandomWithPrefix("aws") accountID := strconv.Itoa(acctest.RandInt()) arn := acctest.RandomWithPrefix("arn:aws:iam::" + accountID + ":role/test-role") + externalID := "external-id" + updatedExternalID := "external-id-updated" + resourceName := "vault_aws_auth_backend_sts_role.role" + resource.Test(t, resource.TestCase{ - PreCheck: func() { testutil.TestAccPreCheck(t) }, + PreCheck: func() { + testutil.TestAccPreCheck(t) + SkipIfAPIVersionLT(t, testProvider.Meta(), provider.VaultVersion117) + }, ProviderFactories: providerFactories, CheckDestroy: testAccCheckAWSAuthBackendSTSRoleDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSAuthBackendSTSRoleConfig_basic(backend, accountID, arn), - Check: testAccAWSAuthBackendSTSRoleCheck_attrs(backend, accountID, arn), + Config: testAccAWSAuthBackendSTSRoleConfig(backend, accountID, arn, externalID), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "backend", backend), + resource.TestCheckResourceAttr(resourceName, "account_id", accountID), + resource.TestCheckResourceAttr(resourceName, "sts_role", arn), + resource.TestCheckResourceAttr(resourceName, consts.FieldExternalID, externalID), + ), }, { - ResourceName: "vault_aws_auth_backend_sts_role.role", + // Update external ID. + Config: testAccAWSAuthBackendSTSRoleConfig(backend, accountID, arn, updatedExternalID), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "backend", backend), + resource.TestCheckResourceAttr(resourceName, "account_id", accountID), + resource.TestCheckResourceAttr(resourceName, "sts_role", arn), + resource.TestCheckResourceAttr(resourceName, consts.FieldExternalID, updatedExternalID), + ), + }, + { + ResourceName: resourceName, ImportState: true, ImportStateVerify: true, }, @@ -49,13 +73,19 @@ func TestAccAWSAuthBackendSTSRole_basic(t *testing.T) { CheckDestroy: testAccCheckAWSAuthBackendSTSRoleDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSAuthBackendSTSRoleConfig_basic(backend, accountID, arn), + Config: testAccAWSAuthBackendSTSRoleConfig(backend, accountID, arn, ""), Check: testAccAWSAuthBackendSTSRoleCheck_attrs(backend, accountID, arn), }, { - Config: testAccAWSAuthBackendSTSRoleConfig_basic(backend, accountID, updatedArn), + // Update ARN. + Config: testAccAWSAuthBackendSTSRoleConfig(backend, accountID, updatedArn, ""), Check: testAccAWSAuthBackendSTSRoleCheck_attrs(backend, accountID, updatedArn), }, + { + ResourceName: "vault_aws_auth_backend_sts_role.role", + ImportState: true, + ImportStateVerify: true, + }, }, }) } @@ -129,17 +159,28 @@ func testAccAWSAuthBackendSTSRoleCheck_attrs(backend, accountID, stsRole string) } } -func testAccAWSAuthBackendSTSRoleConfig_basic(backend, accountID, stsRole string) string { - return fmt.Sprintf(` +func testAccAWSAuthBackendSTSRoleConfig(backend, accountID, stsRole, externalID string) string { + backendResource := fmt.Sprintf(` resource "vault_auth_backend" "aws" { - type = "aws" - path = "%s" -} + type = "aws" + path = "%s" +}`, backend) + + roleResourceOptionalFields := "" + if externalID != "" { + roleResourceOptionalFields += fmt.Sprintf(` + external_id = "%s"`, externalID) + } + roleResource := fmt.Sprintf(` resource "vault_aws_auth_backend_sts_role" "role" { - backend = vault_auth_backend.aws.path - account_id = "%s" - sts_role = "%s" + backend = vault_auth_backend.aws.path + account_id = "%s" + sts_role = "%s"%s } -`, backend, accountID, stsRole) +`, accountID, stsRole, roleResourceOptionalFields) + + resources := []string{backendResource, roleResource} + + return strings.Join(resources, "\n") } diff --git a/website/docs/r/aws_auth_backend_sts_role.html.md b/website/docs/r/aws_auth_backend_sts_role.html.md index 20535a616..98ac51c09 100644 --- a/website/docs/r/aws_auth_backend_sts_role.html.md +++ b/website/docs/r/aws_auth_backend_sts_role.html.md @@ -51,6 +51,8 @@ The following arguments are supported: * `backend` - (Optional) The path the AWS auth backend being configured was mounted at. Defaults to `aws`. +* `external_id` - (Optional) External ID expected by the STS role. The associated STS role must be configured to require the external ID. Requires Vault 1.17+. + ## Attributes Reference No additional attributes are exported by this resource. From 879bb54b1bcffc84d43778ea23fd92abe75850df Mon Sep 17 00:00:00 2001 From: Laurent Date: Mon, 25 Nov 2024 23:17:32 +0100 Subject: [PATCH 4/5] feat(pki): Add support for ACME configuration (#2157) * feat(pki): Add support for ACME configuration * imprv(pki): Remove ValidateFunc, check for 1.14.1. Simplify StateContext. Return error message if response is nil, nil --- CHANGELOG.md | 1 + internal/consts/consts.go | 6 + vault/provider.go | 4 + ...resource_pki_secret_backend_config_acme.go | 209 ++++++++++++++++++ ...rce_pki_secret_backend_config_acme_test.go | 113 ++++++++++ .../r/pki_secret_backend_config_acme.html.md | 81 +++++++ website/vault.erb | 4 + 7 files changed, 418 insertions(+) create mode 100644 vault/resource_pki_secret_backend_config_acme.go create mode 100644 vault/resource_pki_secret_backend_config_acme_test.go create mode 100644 website/docs/r/pki_secret_backend_config_acme.html.md diff --git a/CHANGELOG.md b/CHANGELOG.md index f758c0f52..04937f440 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ FEATURES: * Update `vault_database_secret_backend_connection`to support `password_authentication` for PostgreSQL, allowing to encrypt password before being passed to PostgreSQL ([#2371](https://github.com/hashicorp/terraform-provider-vault/pull/2371)) * Add support for `external_id` field for the `vault_aws_auth_backend_sts_role` resource ([#2370](https://github.com/hashicorp/terraform-provider-vault/pull/2370)) +* Add support for ACME configuration with the `vault_pki_secret_backend_config_acme` resource. Requires Vault 1.14+ ([#2157](https://github.com/hashicorp/terraform-provider-vault/pull/2157)). ## 4.5.0 (Nov 19, 2024) diff --git a/internal/consts/consts.go b/internal/consts/consts.go index 1fab9e141..86a56b734 100644 --- a/internal/consts/consts.go +++ b/internal/consts/consts.go @@ -438,6 +438,12 @@ const ( FieldMaxRetries = "max_retries" FieldSessionTags = "session_tags" FieldSelfManagedPassword = "self_managed_password" + FieldAllowedIssuers = "allowed_issuers" + FieldAllowedRoles = "allowed_roles" + FieldAllowRoleExtKeyUsage = "allow_role_ext_key_usage" + FieldDefaultDirectoryPolicy = "default_directory_policy" + FieldDnsResolver = "dns_resolver" + FieldEabPolicy = "eab_policy" /* common environment variables diff --git a/vault/provider.go b/vault/provider.go index f64faf555..9c3658da1 100644 --- a/vault/provider.go +++ b/vault/provider.go @@ -579,6 +579,10 @@ var ( Resource: UpdateSchemaResource(pkiSecretBackendCrlConfigResource()), PathInventory: []string{"/pki/config/crl"}, }, + "vault_pki_secret_backend_config_acme": { + Resource: UpdateSchemaResource(pkiSecretBackendConfigACMEResource()), + PathInventory: []string{"/pki/config/acme"}, + }, "vault_pki_secret_backend_config_ca": { Resource: UpdateSchemaResource(pkiSecretBackendConfigCAResource()), PathInventory: []string{"/pki/config/ca"}, diff --git a/vault/resource_pki_secret_backend_config_acme.go b/vault/resource_pki_secret_backend_config_acme.go new file mode 100644 index 000000000..a72e11467 --- /dev/null +++ b/vault/resource_pki_secret_backend_config_acme.go @@ -0,0 +1,209 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +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" +) + +var ( + pkiSecretBackendFromConfigACMERegex = regexp.MustCompile("^(.+)/config/acme$") + pkiAcmeFields = []string{ + consts.FieldEnabled, + consts.FieldDefaultDirectoryPolicy, + consts.FieldAllowedRoles, + consts.FieldAllowRoleExtKeyUsage, + consts.FieldAllowedIssuers, + consts.FieldEabPolicy, + consts.FieldDnsResolver, + } +) + +func pkiSecretBackendConfigACMEResource() *schema.Resource { + return &schema.Resource{ + CreateContext: provider.MountCreateContextWrapper(pkiSecretBackendConfigACMECreate, provider.VaultVersion113), + ReadContext: provider.ReadContextWrapper(pkiSecretBackendConfigACMERead), + UpdateContext: pkiSecretBackendConfigACMEUpdate, + DeleteContext: pkiSecretBackendConfigACMEDelete, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: map[string]*schema.Schema{ + consts.FieldBackend: { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "Full path where PKI backend is mounted.", + // standardise on no beginning or trailing slashes + StateFunc: func(v interface{}) string { + return strings.Trim(v.(string), "/") + }, + }, + consts.FieldEnabled: { + Type: schema.TypeBool, + Required: true, + Description: "Specifies whether ACME is enabled.", + }, + consts.FieldDefaultDirectoryPolicy: { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "Specifies the policy to be used for non-role-qualified ACME requests.", + }, + consts.FieldAllowedRoles: { + Type: schema.TypeList, + Optional: true, + Computed: true, + Description: "Specifies which roles are allowed for use with ACME.", + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + consts.FieldAllowRoleExtKeyUsage: { + Type: schema.TypeBool, + Optional: true, + Description: "Specifies whether the ExtKeyUsage field from a role is used.", + }, + consts.FieldAllowedIssuers: { + Type: schema.TypeList, + Optional: true, + Computed: true, + Description: "Specifies which issuers are allowed for use with ACME.", + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + consts.FieldEabPolicy: { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "Specifies the policy to use for external account binding behaviour.", + }, + consts.FieldDnsResolver: { + Type: schema.TypeString, + Optional: true, + Description: "DNS resolver to use for domain resolution on this mount. " + + "Must be in the format :, with both parts mandatory.", + }, + }, + } +} + +func pkiSecretBackendConfigACMECreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client, e := provider.GetClient(d, meta) + if e != nil { + return diag.FromErr(e) + } + + backend := d.Get(consts.FieldBackend).(string) + path := fmt.Sprintf("%s/config/acme", backend) + + resp, err := client.Logical().ReadWithContext(ctx, path) + if err != nil { + return diag.Errorf("error reading acme config at %s, err=%s", path, err) + } + + if resp == nil { + return diag.Errorf("no acme config found at path %s", path) + } + + d.SetId(path) + + return pkiSecretBackendConfigACMEUpdate(ctx, d, meta) +} + +func pkiSecretBackendConfigACMEUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client, e := provider.GetClient(d, meta) + if e != nil { + return diag.FromErr(e) + } + + path := d.Id() + + var patchRequired bool + data := map[string]interface{}{} + for _, k := range pkiAcmeFields { + if d.HasChange(k) { + data[k] = d.Get(k) + patchRequired = true + } + } + + if patchRequired { + _, err := client.Logical().WriteWithContext(ctx, path, data) + if err != nil { + return diag.Errorf("error writing data to %q, err=%s", path, err) + } + } + + return pkiSecretBackendConfigACMERead(ctx, d, meta) +} + +func pkiSecretBackendConfigACMERead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client, e := provider.GetClient(d, meta) + if e != nil { + return diag.FromErr(e) + } + + path := d.Id() + if path == "" { + return diag.Errorf("no path set, id=%q", d.Id()) + } + + // get backend from full path + backend, err := pkiSecretBackendFromConfigACME(path) + if err != nil { + return diag.FromErr(err) + } + + log.Printf("[DEBUG] Reading %s from Vault", path) + resp, err := client.Logical().ReadWithContext(ctx, path) + if err != nil { + return diag.Errorf("error reading from Vault: %s", err) + } + + if resp == nil { + return diag.Errorf("got nil response from Vault from path: %q", path) + } + + // set backend and issuerRef + if err := d.Set(consts.FieldBackend, backend); err != nil { + return diag.FromErr(err) + } + + for _, k := range pkiAcmeFields { + if err := d.Set(k, resp.Data[k]); err != nil { + return diag.Errorf("error setting state key %q for acme config, err=%s", + k, err) + } + } + + return nil +} + +func pkiSecretBackendConfigACMEDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + return nil +} + +func pkiSecretBackendFromConfigACME(path string) (string, error) { + if !pkiSecretBackendFromConfigACMERegex.MatchString(path) { + return "", fmt.Errorf("no backend found") + } + res := pkiSecretBackendFromConfigACMERegex.FindStringSubmatch(path) + if len(res) != 2 { + return "", fmt.Errorf("unexpected number of matches (%d) for backend", len(res)) + } + return res[1], nil +} diff --git a/vault/resource_pki_secret_backend_config_acme_test.go b/vault/resource_pki_secret_backend_config_acme_test.go new file mode 100644 index 000000000..b646f4ed9 --- /dev/null +++ b/vault/resource_pki_secret_backend_config_acme_test.go @@ -0,0 +1,113 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package vault + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + + "github.com/hashicorp/terraform-provider-vault/internal/consts" + "github.com/hashicorp/terraform-provider-vault/internal/provider" + "github.com/hashicorp/terraform-provider-vault/testutil" +) + +func TestPkiSecretBackendConfigACME_basic(t *testing.T) { + backend := acctest.RandomWithPrefix("pki-root") + resourceType := "vault_pki_secret_backend_config_acme" + resourceName := resourceType + ".test" + + resource.Test(t, resource.TestCase{ + ProviderFactories: providerFactories, + PreCheck: func() { + testutil.TestAccPreCheck(t) + SkipIfAPIVersionLT(t, testProvider.Meta(), provider.VaultVersion114) + }, + CheckDestroy: testCheckMountDestroyed(resourceType, consts.MountTypePKI, consts.FieldBackend), + Steps: []resource.TestStep{ + { + Config: testPkiSecretBackendConfigACME(backend, "sign-verbatim", "*", "*", "not-required", "", + false, false), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, consts.FieldBackend, backend), + resource.TestCheckResourceAttr(resourceName, consts.FieldEnabled, "false"), + resource.TestCheckResourceAttr(resourceName, consts.FieldDefaultDirectoryPolicy, "sign-verbatim"), + resource.TestCheckResourceAttr(resourceName, consts.FieldAllowedRoles+".0", "*"), + resource.TestCheckResourceAttr(resourceName, consts.FieldAllowedIssuers+".0", "*"), + resource.TestCheckResourceAttr(resourceName, consts.FieldEabPolicy, "not-required"), + resource.TestCheckResourceAttr(resourceName, consts.FieldDnsResolver, ""), + ), + }, + { + Config: testPkiSecretBackendConfigACME(backend, "forbid", "test", "*", "new-account-required", + "1.1.1.1:8443", true, false), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, consts.FieldBackend, backend), + resource.TestCheckResourceAttr(resourceName, consts.FieldEnabled, "true"), + resource.TestCheckResourceAttr(resourceName, consts.FieldDefaultDirectoryPolicy, "forbid"), + resource.TestCheckResourceAttr(resourceName, consts.FieldAllowedRoles+".0", "test"), + resource.TestCheckResourceAttr(resourceName, consts.FieldAllowedIssuers+".0", "*"), + resource.TestCheckResourceAttr(resourceName, consts.FieldEabPolicy, "new-account-required"), + resource.TestCheckResourceAttr(resourceName, consts.FieldDnsResolver, "1.1.1.1:8443"), + ), + }, + { + Config: testPkiSecretBackendConfigACME(backend, "role:test", "*", "*", "always-required", "", + true, true), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, consts.FieldBackend, backend), + resource.TestCheckResourceAttr(resourceName, consts.FieldEnabled, "true"), + resource.TestCheckResourceAttr(resourceName, consts.FieldDefaultDirectoryPolicy, "role:test"), + resource.TestCheckResourceAttr(resourceName, consts.FieldAllowedRoles+".0", "*"), + resource.TestCheckResourceAttr(resourceName, consts.FieldAllowRoleExtKeyUsage, "true"), + resource.TestCheckResourceAttr(resourceName, consts.FieldAllowedIssuers+".0", "*"), + resource.TestCheckResourceAttr(resourceName, consts.FieldEabPolicy, "always-required"), + resource.TestCheckResourceAttr(resourceName, consts.FieldDnsResolver, ""), + ), + }, + testutil.GetImportTestStep(resourceName, false, nil), + }, + }) +} + +func testPkiSecretBackendConfigACME(path, default_directory_policy, allowed_roles, allowed_issuers, + eab_policy, dns_resolver string, enabled, allow_role_ext_key_usage bool) string { + return fmt.Sprintf(` +resource "vault_mount" "test" { + path = "%s" + type = "pki" +} + +resource "vault_pki_secret_backend_root_cert" "test" { + backend = vault_mount.test.path + type = "internal" + common_name = "test" + ttl = "86400" +} + +resource "vault_pki_secret_backend_config_cluster" "test" { + backend = vault_mount.test.path + path = "http://127.0.0.1:8200/v1/${vault_mount.test.path}" + aia_path = "http://127.0.0.1:8200/v1/${vault_mount.test.path}" +} + +resource "vault_pki_secret_backend_role" "test" { + backend = vault_pki_secret_backend_root_cert.test.backend + name = "test" +} + +resource "vault_pki_secret_backend_config_acme" "test" { + backend = vault_mount.test.path + enabled = "%t" + allowed_issuers = ["%s"] + allowed_roles = ["%s"] + allow_role_ext_key_usage = "%t" + default_directory_policy = "%s" + dns_resolver = "%s" + eab_policy = "%s" +}`, path, enabled, allowed_issuers, allowed_roles, allow_role_ext_key_usage, + default_directory_policy, dns_resolver, eab_policy) +} diff --git a/website/docs/r/pki_secret_backend_config_acme.html.md b/website/docs/r/pki_secret_backend_config_acme.html.md new file mode 100644 index 000000000..b52a9f386 --- /dev/null +++ b/website/docs/r/pki_secret_backend_config_acme.html.md @@ -0,0 +1,81 @@ +--- +layout: "vault" +page_title: "Vault: vault_pki_secret_backend_config_acme resource" +sidebar_current: "docs-vault-resource-pki-secret-backend-config-acme" +description: |- + Sets the ACME configuration on a PKI Secret Backend for Vault. +--- + +# vault\_pki\_secret\_backend\_config\_acme + +Allows setting the ACME server configuration used by specified mount. + +## Example Usage + +```hcl +resource "vault_mount" "pki" { + path = "pki" + type = "pki" + default_lease_ttl_seconds = 3600 + max_lease_ttl_seconds = 86400 +} + +resource "vault_pki_secret_backend_config_cluster" "pki_config_cluster" { + backend = vault_mount.pki.path + path = "http://127.0.0.1:8200/v1/pki" + aia_path = "http://127.0.0.1:8200/v1/pki" +} + +resource "vault_pki_secret_backend_config_acme" "example" { + backend = vault_mount.pki.path + enabled = true + allowed_issuers = ["*"] + allowed_roles = ["*"] + allow_role_ext_key_usage = false + default_directory_policy = "sign-verbatim" + dns_resolver = "" + eab_policy = "not-required" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `namespace` - (Optional) The namespace to provision the resource in. + The value should not contain leading or trailing forward slashes. + The `namespace` is always relative to the provider's configured [namespace](/docs/providers/vault#namespace). + *Available only for Vault Enterprise*. + +* `backend` - (Required) The path the PKI secret backend is mounted at, with no leading or trailing `/`s. + +* `enabled` - (Required) Specifies whether ACME is enabled. + +* `allowed_issuers` - (Optional) Specifies which issuers are allowed for use with ACME. + +* `allowed_roles` - (Optional) Specifies which roles are allowed for use with ACME. + +* `allow_role_ext_key_usage` - (Optional) Specifies whether the ExtKeyUsage field from a role is used. **Vault 1.14.1+** + +* `default_directory_policy` - (Optional) Specifies the policy to be used for non-role-qualified ACME requests. + Allowed values are `forbid`, `sign-verbatim`, `role:`, `external-policy` or `external-policy:`. + +* `dns_resolver` - (Optional) DNS resolver to use for domain resolution on this mount. + Must be in the format `:`, with both parts mandatory. + +* `eab_policy` - (Optional) Specifies the policy to use for external account binding behaviour. + Allowed values are `not-required`, `new-account-required` or `always-required`. + +## Attributes Reference + +No additional attributes are exported by this resource. + +## Import + +The ACME configuration can be imported using the resource's `id`. +In the case of the example above the `id` would be `pki/config/acme`, +where the `pki` component is the resource's `backend`, e.g. + +``` +$ terraform import vault_pki_secret_backend_config_acme.example pki/config/acme +``` diff --git a/website/vault.erb b/website/vault.erb index e7d1fdfce..0e900c0ab 100644 --- a/website/vault.erb +++ b/website/vault.erb @@ -549,6 +549,10 @@ vault_pki_secret_backend_cert + > + vault_pki_secret_backend_config_acme + + > vault_pki_secret_backend_config_ca From 435ce67e0a66d1b80499e8544227bcca405bfcc1 Mon Sep 17 00:00:00 2001 From: Ben Hughes Date: Tue, 26 Nov 2024 16:42:45 +0000 Subject: [PATCH 5/5] Add cn_validations property to pki_secret_backend_role (#1820) --- CHANGELOG.md | 1 + internal/consts/consts.go | 2 +- vault/resource_pki_secret_backend_role.go | 13 ++++++++++--- vault/resource_pki_secret_backend_role_test.go | 7 +++++++ website/docs/r/pki_secret_backend_role.html.md | 2 ++ 5 files changed, 21 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 04937f440..547d1ac76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ FEATURES: * Update `vault_database_secret_backend_connection`to support `password_authentication` for PostgreSQL, allowing to encrypt password before being passed to PostgreSQL ([#2371](https://github.com/hashicorp/terraform-provider-vault/pull/2371)) * Add support for `external_id` field for the `vault_aws_auth_backend_sts_role` resource ([#2370](https://github.com/hashicorp/terraform-provider-vault/pull/2370)) * Add support for ACME configuration with the `vault_pki_secret_backend_config_acme` resource. Requires Vault 1.14+ ([#2157](https://github.com/hashicorp/terraform-provider-vault/pull/2157)). +* Update `vault_pki_secret_backend_role` to support the `cn_validations` role field ([#1820](https://github.com/hashicorp/terraform-provider-vault/pull/1820)). ## 4.5.0 (Nov 19, 2024) diff --git a/internal/consts/consts.go b/internal/consts/consts.go index 86a56b734..7ec79457c 100644 --- a/internal/consts/consts.go +++ b/internal/consts/consts.go @@ -444,7 +444,7 @@ const ( FieldDefaultDirectoryPolicy = "default_directory_policy" FieldDnsResolver = "dns_resolver" FieldEabPolicy = "eab_policy" - + FieldCnValidations = "cn_validations" /* common environment variables */ diff --git a/vault/resource_pki_secret_backend_role.go b/vault/resource_pki_secret_backend_role.go index 644fe2a00..a2a599403 100644 --- a/vault/resource_pki_secret_backend_role.go +++ b/vault/resource_pki_secret_backend_role.go @@ -50,6 +50,7 @@ var pkiSecretListFields = []string{ consts.FieldAllowedSerialNumbers, consts.FieldExtKeyUsage, consts.FieldExtKeyUsageOIDs, + consts.FieldCnValidations, } var pkiSecretBooleanFields = []string{ @@ -423,9 +424,15 @@ func pkiSecretBackendRoleResource() *schema.Resource { Required: false, Optional: true, Description: "Defines allowed Subject serial numbers.", - Elem: &schema.Schema{ - Type: schema.TypeString, - }, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + consts.FieldCnValidations: { + Type: schema.TypeList, + Required: false, + Optional: true, + Computed: true, + Description: "Specify validations to run on the Common Name field of the certificate.", + Elem: &schema.Schema{Type: schema.TypeString}, }, consts.FieldAllowedUserIds: { Type: schema.TypeList, diff --git a/vault/resource_pki_secret_backend_role_test.go b/vault/resource_pki_secret_backend_role_test.go index 9bc15c9ba..6293a786c 100644 --- a/vault/resource_pki_secret_backend_role_test.go +++ b/vault/resource_pki_secret_backend_role_test.go @@ -180,6 +180,9 @@ func TestPkiSecretBackendRole_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "not_before_duration", "45m"), resource.TestCheckResourceAttr(resourceName, "policy_identifiers.#", "1"), resource.TestCheckResourceAttr(resourceName, "policy_identifiers.0", "1.2.3.4"), + resource.TestCheckResourceAttr(resourceName, "cn_validations.#", "2"), + resource.TestCheckTypeSetElemAttr(resourceName, "cn_validations.*", "email"), + resource.TestCheckTypeSetElemAttr(resourceName, "cn_validations.*", "hostname"), } resource.Test(t, resource.TestCase{ ProviderFactories: providerFactories, @@ -320,6 +323,8 @@ func TestPkiSecretBackendRole_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "policy_identifiers.0", "1.2.3.4"), resource.TestCheckResourceAttr(resourceName, "basic_constraints_valid_for_non_ca", "false"), resource.TestCheckResourceAttr(resourceName, "not_before_duration", "45m"), + resource.TestCheckResourceAttr(resourceName, "cn_validations.#", "1"), + resource.TestCheckTypeSetElemAttr(resourceName, "cn_validations.*", "disabled"), ), }, { @@ -391,6 +396,7 @@ resource "vault_pki_secret_backend_role" "test" { basic_constraints_valid_for_non_ca = false not_before_duration = "45m" allowed_serial_numbers = ["*"] + cn_validations = ["email", "hostname"] } `, path, name, roleTTL, maxTTL, extraConfig) } @@ -446,6 +452,7 @@ resource "vault_pki_secret_backend_role" "test" { basic_constraints_valid_for_non_ca = false not_before_duration = "45m" allowed_serial_numbers = ["*"] + cn_validations = ["disabled"] }`, path, name, policyIdentifiers) } diff --git a/website/docs/r/pki_secret_backend_role.html.md b/website/docs/r/pki_secret_backend_role.html.md index d25ab50d7..e7739937c 100644 --- a/website/docs/r/pki_secret_backend_role.html.md +++ b/website/docs/r/pki_secret_backend_role.html.md @@ -86,6 +86,8 @@ The following arguments are supported: * `client_flag` - (Optional) Flag to specify certificates for client use +* `cn_validations` - (Optional) Validations to run on the Common Name field of the certificate, choices: `email`, `hostname`, `disabled` + * `code_signing_flag` - (Optional) Flag to specify certificates for code signing use * `email_protection_flag` - (Optional) Flag to specify certificates for email protection use