From 739a781de33685172db5733b6d449f66be0bcfea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20B=C3=B6schen?= Date: Thu, 19 Dec 2024 10:38:18 +0100 Subject: [PATCH] Add credential_type and credential_config to static roles for DBs --- CHANGELOG.md | 1 + ...rce_database_secret_backend_static_role.go | 24 +++ ...atabase_secret_backend_static_role_test.go | 186 ++++++++++++++++++ 3 files changed, 211 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 71d2fdb49..160393e62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ FEATURES: * Update `vault_pki_secret_backend_role` to support the `cn_validations` role field ([#1820](https://github.com/hashicorp/terraform-provider-vault/pull/1820)). * Add new resource `vault_pki_secret_backend_acme_eab` to manage PKI ACME external account binding tokens. Requires Vault 1.14+. ([#2367](https://github.com/hashicorp/terraform-provider-vault/pull/2367)) * Add new data source and resource `vault_pki_secret_backend_config_cmpv2`. Requires Vault 1.18+. *Available only for Vault Enterprise* ([#2330](https://github.com/hashicorp/terraform-provider-vault/pull/2330)) +* Add `credential_type` and `credential_config` to `database_secret_backend_static_role` to support features like rsa keys for Snowflake DB engines with static roles IMPROVEMENTS: diff --git a/vault/resource_database_secret_backend_static_role.go b/vault/resource_database_secret_backend_static_role.go index b627351e1..ab9ee14d3 100644 --- a/vault/resource_database_secret_backend_static_role.go +++ b/vault/resource_database_secret_backend_static_role.go @@ -26,6 +26,8 @@ var staticRoleFields = []string{ consts.FieldRotationPeriod, consts.FieldRotationStatements, consts.FieldDBName, + consts.FieldCredentialType, + consts.FieldCredentialConfig, } func databaseSecretBackendStaticRoleResource() *schema.Resource { @@ -99,6 +101,20 @@ func databaseSecretBackendStaticRoleResource() *schema.Resource { Description: "The password corresponding to the username in the database. " + "Required when using the Rootless Password Rotation workflow for static roles.", }, + consts.FieldCredentialType: { + Type: schema.TypeString, + Optional: true, + Default: "password", + Description: "The credential type for the user, can be one of \"password\", \"rsa_private_key\" or \"client_certificate\"." + + "The configuration can be done in `credential_config`.", + }, + consts.FieldCredentialConfig: { + Type: schema.TypeMap, + Elem: &schema.Schema{Type: schema.TypeString}, + Optional: true, + Description: "The configuration for the credential type." + + "Full documentation for the allowed values can be found under \"https://developer.hashicorp.com/vault/api-docs/secret/databases#credential_config\".", + }, }, } } @@ -138,6 +154,14 @@ func databaseSecretBackendStaticRoleWrite(ctx context.Context, d *schema.Resourc data[consts.FieldRotationPeriod] = v } + if v, ok := d.GetOk(consts.FieldCredentialType); ok && v != "" { + data[consts.FieldCredentialType] = v + } + + if v, ok := d.GetOk(consts.FieldCredentialConfig); ok && v != "" { + data[consts.FieldCredentialConfig] = v + } + if provider.IsAPISupported(meta, provider.VaultVersion118) && provider.IsEnterpriseSupported(meta) { if v, ok := d.GetOk(consts.FieldSelfManagedPassword); ok && v != "" { data[consts.FieldSelfManagedPassword] = v diff --git a/vault/resource_database_secret_backend_static_role_test.go b/vault/resource_database_secret_backend_static_role_test.go index 96df17f7b..878d1f956 100644 --- a/vault/resource_database_secret_backend_static_role_test.go +++ b/vault/resource_database_secret_backend_static_role_test.go @@ -58,6 +58,80 @@ func TestAccDatabaseSecretBackendStaticRole_import(t *testing.T) { }) } +func TestAccDatabaseSecretBackendStaticRole_credentialType(t *testing.T) { + connURL := testutil.SkipTestEnvUnset(t, "MYSQL_URL")[0] + + backend := acctest.RandomWithPrefix("tf-test-db") + username := acctest.RandomWithPrefix("user") + dbName := acctest.RandomWithPrefix("db") + name := acctest.RandomWithPrefix("staticrole") + resourceName := "vault_database_secret_backend_static_role.test" + + if err := createTestUser(connURL, username); err != nil { + t.Fatal(err) + } + + resource.Test(t, resource.TestCase{ + ProviderFactories: providerFactories, + PreCheck: func() { testutil.TestAccPreCheck(t) }, + CheckDestroy: testAccDatabaseSecretBackendStaticRoleCheckDestroy, + Steps: []resource.TestStep{ + { + Config: testAccDatabaseSecretBackendStaticRoleConfig_credentialType(name, username, dbName, backend, connURL), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "name", name), + resource.TestCheckResourceAttr(resourceName, "backend", backend), + resource.TestCheckResourceAttr(resourceName, "username", username), + resource.TestCheckResourceAttr(resourceName, "db_name", dbName), + resource.TestCheckResourceAttr(resourceName, "credential_type", "password"), + ), + }, + }, + }) +} + +func TestAccDatabaseSecretBackendStaticRole_credentialConfig(t *testing.T) { + connURL := testutil.SkipTestEnvUnset(t, "MYSQL_URL")[0] + + backend := acctest.RandomWithPrefix("tf-test-db") + username := acctest.RandomWithPrefix("user") + dbName := acctest.RandomWithPrefix("db") + name := acctest.RandomWithPrefix("staticrole") + resourceName := "vault_database_secret_backend_static_role.test" + + if err := createTestUser(connURL, username); err != nil { + t.Fatal(err) + } + + resource.Test(t, resource.TestCase{ + ProviderFactories: providerFactories, + PreCheck: func() { testutil.TestAccPreCheck(t) }, + CheckDestroy: testAccDatabaseSecretBackendStaticRoleCheckDestroy, + Steps: []resource.TestStep{ + { + Config: testAccDatabaseSecretBackendStaticRoleConfig_credentialConfig(name, username, dbName, backend, connURL), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "name", name), + resource.TestCheckResourceAttr(resourceName, "backend", backend), + resource.TestCheckResourceAttr(resourceName, "username", username), + resource.TestCheckResourceAttr(resourceName, "db_name", dbName), + resource.TestCheckResourceAttr(resourceName, "credential_config.password_policy", "numeric"), + ), + }, + { + Config: testAccDatabaseSecretBackendStaticRoleConfig_updatedCredentialConfig(name, username, dbName, backend, connURL), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "name", name), + resource.TestCheckResourceAttr(resourceName, "backend", backend), + resource.TestCheckResourceAttr(resourceName, "username", username), + resource.TestCheckResourceAttr(resourceName, "db_name", dbName), + resource.TestCheckResourceAttr(resourceName, "credential_config.password_policy", "alphanumeric"), + ), + }, + }, + }) +} + func TestAccDatabaseSecretBackendStaticRole_rotationPeriod(t *testing.T) { connURL := testutil.SkipTestEnvUnset(t, "MYSQL_URL")[0] @@ -237,6 +311,7 @@ func createTestUser(connURL, username string) error { if err != nil { return err } + tx, err := db.BeginTx(ctx, nil) if err != nil { return err @@ -257,6 +332,117 @@ func createTestUser(connURL, username string) error { return nil } +func testAccDatabaseSecretBackendStaticRoleConfig_credentialType(name, username, db, path, connURL string) 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 = ["*"] + + mysql { + connection_url = "%s" + } +} + +resource "vault_database_secret_backend_static_role" "test" { + backend = vault_mount.db.path + db_name = vault_database_secret_backend_connection.test.name + name = "%s" + username = "%s" + credential_type = "password" + rotation_period = 1800 + rotation_statements = ["ALTER USER '{{username}}'@'localhost' IDENTIFIED BY '{{password}}';"] +} +`, path, db, connURL, name, username) +} + +func testAccDatabaseSecretBackendStaticRoleConfig_credentialConfig(name, username, db, path, connURL string) 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 = ["*"] + + mysql { + connection_url = "%s" + } +} + +resource "vault_password_policy" "test" { + name = "numeric" + + policy = <