Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add credential_type and credential_config to static roles for DBs #2384

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
24 changes: 24 additions & 0 deletions vault/resource_database_secret_backend_static_role.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ var staticRoleFields = []string{
consts.FieldRotationPeriod,
consts.FieldRotationStatements,
consts.FieldDBName,
consts.FieldCredentialType,
consts.FieldCredentialConfig,
}

func databaseSecretBackendStaticRoleResource() *schema.Resource {
Expand Down Expand Up @@ -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\".",
},
},
}
}
Expand Down Expand Up @@ -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
Expand Down
186 changes: 186 additions & 0 deletions vault/resource_database_secret_backend_static_role_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand Down Expand Up @@ -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
Expand All @@ -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 = <<EOT
length = 20
rule "charset" {
charset = "0123456789"
}
EOT
}
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"
credential_config = { "password_policy" = "numeric" }
rotation_period = 1800
rotation_statements = ["ALTER USER '{{username}}'@'localhost' IDENTIFIED BY '{{password}}';"]
}
`, path, db, connURL, name, username)
}

func testAccDatabaseSecretBackendStaticRoleConfig_updatedCredentialConfig(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 = "alphanumeric"
policy = <<EOT
length = 20
rule "charset" {
charset = "abcdefghijklmnopqrstuvwxyz0123456789"
}
EOT
}
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"
credential_config = { "password_policy" = "alphanumeric" }
rotation_period = 1800
rotation_statements = ["ALTER USER '{{username}}'@'localhost' IDENTIFIED BY '{{password}}';"]
}
`, path, db, connURL, name, username)
}

func testAccDatabaseSecretBackendStaticRoleConfig_rotationSchedule(name, username, db, path, connURL string) string {
return fmt.Sprintf(`
resource "vault_mount" "db" {
Expand Down