Repository: hashicorp/terraform-provider-vault

Issue:
[Enhancement]: Snowflake forcing key-pair authentication November 2025
### Description

Snowflake is updating its security requirements for users. Specifically, "service account" will no longer support "password" authentication and will move to OAuth or key-pair only. It appears that the current version of the Vault provider (4.6.0) does not support key-pair rotations. The resource "vault_database_secret_backend_static_role" does not include a 'connection_type' to specify key-pair. Additionally, the "vault_database_secret_backend_connection" resource does not allow for key-pair either, which needs to be addressed. This may be a general issue with Vault. I also do not see key-pair support for the "root credentials".

In order for Vault, and Vault Terraform provider, to continue to work past 11/2025 a key-pair solution will need to be created. If I'm missing something and a solution is already available please point me in the right direction. 

https://developer.hashicorp.com/vault/docs/secrets/databases/snowflake#key-pair-credentials-1
https://www.snowflake.com/en/blog/blocking-single-factor-password-authentification/

Related: https://github.com/hashicorp/terraform-provider-vault/issues/1585


### Affected Resource(s) and/or Data Source(s)

vault_database_secret_backend_static_role
vault_database_secret_backend_connection

### Potential Terraform Configuration

```terraform
resource "vault_database_secret_backend_connection" "snowflake" {
  backend       = vault_mount.db.path
  name          = "snowflake"
  allowed_roles = ["*"]
  root_rotation_statements = "ALTER USER {{name}} SET RSA_PUBLIC_KEY = '{{public_key}}'"

  snowflake {
    connection_url = "{{username}}:{{private_key}}@${var.connection_url}"
    username = local.admin_username
    private_key = local.admin_private_key
  }
}

resource "vault_database_secret_backend_static_role" "snowflake_role" {
  name                = "my-period-role"
  backend             = vault_mount.db.path
  db_name             = vault_database_secret_backend_connection.snowflake.name
  username            = "example"
  rotation_period     = "31536000"
  rotation_statements = ["ALTER USER \"{{name}}\" SET RSA_PUBLIC_KEY = '{{public_key}}';"]
}
```

### References

https://developer.hashicorp.com/vault/docs/secrets/databases/snowflake#key-pair-credentials-1
https://www.snowflake.com/en/blog/blocking-single-factor-password-authentification/

https://github.com/hashicorp/terraform-provider-vault/issues/1585

### Would you like to implement a fix?

No

Hints from issue comments:
@Shocktrooper: This is now unblocked as the previously needed work has been completed@vinay-gopalan: Thanks for the reminder @Shocktrooper! Closing this as this was merged and is planned to be released as part of v5.1.0. Please let us know if you face any issues, thanks!@Shocktrooper: @vinay-gopalan Will do! Do you know when 5.1.0 is slated to be released by chance?

Solution patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 34bac35b..638b62d3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,7 @@ FEATURES:
 * Update `vault_pki_secret_backend_config_acme` to support the `max_ttl` field. [#2411](https://github.com/hashicorp/terraform-provider-vault/pull/2411)
 * Add new data source `vault_ssh_secret_backend_sign`. ([#2409](https://github.com/hashicorp/terraform-provider-vault/pull/2409))
 * Add support for `disabled_validations` in `vault_pki_secret_backend_config_cmpv2` [#2412](https://github.com/hashicorp/terraform-provider-vault/pull/2412)
+* 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 [#2384](https://github.com/hashicorp/terraform-provider-vault/pull/2384)
 
 BUGS:
 
diff --git a/vault/resource_database_secret_backend_static_role.go b/vault/resource_database_secret_backend_static_role.go
index d332916f..cf745828 100644
--- a/vault/resource_database_secret_backend_static_role.go
+++ b/vault/resource_database_secret_backend_static_role.go
@@ -27,6 +27,8 @@ var staticRoleFields = []string{
 	consts.FieldRotationPeriod,
 	consts.FieldRotationStatements,
 	consts.FieldDBName,
+	consts.FieldCredentialType,
+	consts.FieldCredentialConfig,
 }
 
 func databaseSecretBackendStaticRoleResource() *schema.Resource {
@@ -105,6 +107,20 @@ func databaseSecretBackendStaticRoleResource() *schema.Resource {
 				Optional:    true,
 				Description: "Skip rotation of the password on import.",
 			},
+			consts.FieldCredentialType: {
+				Type:     schema.TypeString,
+				Optional: true,
+				Computed: true,
+				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\".",
+			},
 		},
 	}
 }
@@ -144,6 +160,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



Test patch:
diff --git a/vault/resource_database_secret_backend_static_role_test.go b/vault/resource_database_secret_backend_static_role_test.go
index d8759d1e..5b54b95e 100644
--- a/vault/resource_database_secret_backend_static_role_test.go
+++ b/vault/resource_database_secret_backend_static_role_test.go
@@ -59,6 +59,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]
 
@@ -284,6 +358,7 @@ func createTestUser(connURL, username string) error {
 	if err != nil {
 		return err
 	}
+
 	tx, err := db.BeginTx(ctx, nil)
 	if err != nil {
 		return err
@@ -304,6 +379,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" {