Skip to content

Commit

Permalink
db: add fields for skip auto rotation of static roles (hashicorp#2386)
Browse files Browse the repository at this point in the history
* db: add fields for skip auto rotation of static roles

* add static role support for role-level field

* add config-level field and tests

* update comments

* update env var

* fix test

* remove config-level field

* bump Vault build matrix versions

* changelog
  • Loading branch information
fairclothjm authored Feb 10, 2025
1 parent f84ff08 commit 0430fcd
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 14 deletions.
14 changes: 8 additions & 6 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,9 @@ jobs:
image:
- "vault-enterprise:1.14.13-ent"
- "vault-enterprise:1.15.16-ent"
- "vault-enterprise:1.16.14-ent"
- "vault-enterprise:1.17.10-ent"
- "vault-enterprise:1.18.3-ent"
- "vault-enterprise:1.16.15-ent"
- "vault-enterprise:1.17.11-ent"
- "vault-enterprise:1.18.4-ent"
- "vault:latest"
services:
vault:
Expand Down Expand Up @@ -208,10 +208,12 @@ jobs:
# POSTGRES_URL is the standard root conn URL for Vault
POSTGRES_URL: "postgres://postgres:secret@postgres:5432/database?sslmode=disable"
# POSTGRES_URL_TEST is used by the TFVP test to connect directly to
# the postgres container. Note: the host is "localhost" because the
# TFVP tests do not run in the same docker network.
# the postgres container so that it can create static users.
# Note: the host is "localhost" because the TFVP tests do not run in
# the same docker network.
POSTGRES_URL_TEST: "postgres://postgres:secret@localhost:5432/database?sslmode=disable"
# POSTGRES_URL_ROOTLESS is used by Vault to connect to the postgres container.
# POSTGRES_URL_ROOTLESS is used by Vault to connect to the postgres
# container for "rootless" static roles".
POSTGRES_URL_ROOTLESS: "postgres://{{username}}:{{password}}@postgres:5432/database?sslmode=disable"
COUCHBASE_HOST: couchbase
COUCHBASE_USERNAME: Administrator
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ FEATURES:
* Add support for certificate revocation with `revoke_with_key` in `vault_pki_secret_backend_cert` ([#2242](https://github.com/hashicorp/terraform-provider-vault/pull/2242))
* Add support for signature_bits field to `vault_pki_secret_backend_role`, `vault_pki_secret_backend_root_cert`, `vault_pki_secret_backend_root_sign_intermediate` and `vault_pki_secret_backend_intermediate_cert_request` ([#2401])(https://github.com/hashicorp/terraform-provider-vault/pull/2401)
* Add support for key_usage and serial_number to `vault_pki_secret_backend_intermediate_cert_request` ([#2404])(https://github.com/hashicorp/terraform-provider-vault/pull/2404)
* Add support for `skip_import_rotation` in `vault_database_secret_backend_static_role`. Requires Vault Enterprise 1.18.5+ ([#2386](https://github.com/hashicorp/terraform-provider-vault/pull/2386)).

BUGS:

Expand Down
1 change: 1 addition & 0 deletions vault/resource_database_secret_backend_connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ func (i *dbEngine) PluginPrefixes() ([]string, error) {
return append([]string{defaultPrefix}, i.pluginAliases...), nil
}

// getDatabaseSchema returns the database-specific schema
func getDatabaseSchema(typ schema.ValueType) schemaMap {
var dbEngineTypes []string
for _, e := range dbEngines {
Expand Down
19 changes: 17 additions & 2 deletions vault/resource_database_secret_backend_static_role.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ package vault
import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-provider-vault/internal/consts"
"log"
"regexp"
"strings"

"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-provider-vault/internal/consts"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"

"github.com/hashicorp/terraform-provider-vault/internal/provider"
Expand Down Expand Up @@ -99,6 +100,11 @@ 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.FieldSkipImportRotation: {
Type: schema.TypeBool,
Optional: true,
Description: "Skip rotation of the password on import.",
},
},
}
}
Expand Down Expand Up @@ -142,6 +148,9 @@ func databaseSecretBackendStaticRoleWrite(ctx context.Context, d *schema.Resourc
if v, ok := d.GetOk(consts.FieldSelfManagedPassword); ok && v != "" {
data[consts.FieldSelfManagedPassword] = v
}
if v, ok := d.Get(consts.FieldSkipImportRotation).(bool); ok {
data[consts.FieldSkipImportRotation] = v
}
}

log.Printf("[DEBUG] Creating static role %q on database backend %q", name, backend)
Expand Down Expand Up @@ -211,6 +220,12 @@ func databaseSecretBackendStaticRoleRead(ctx context.Context, d *schema.Resource
}
}

if provider.IsAPISupported(meta, provider.VaultVersion118) && provider.IsEnterpriseSupported(meta) {
if err := d.Set(consts.FieldSkipImportRotation, role.Data[consts.FieldSkipImportRotation]); err != nil {
return diag.FromErr(err)
}
}

for _, k := range staticRoleFields {
if v, ok := role.Data[k]; ok {
if err := d.Set(k, v); err != nil {
Expand Down
94 changes: 88 additions & 6 deletions vault/resource_database_secret_backend_static_role_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"context"
"database/sql"
"fmt"
"net/url"
"os"
"testing"

Expand Down Expand Up @@ -153,9 +154,12 @@ func TestAccDatabaseSecretBackendStaticRole_rotationSchedule(t *testing.T) {

// TestAccDatabaseSecretBackendStaticRole_Rootless tests the
// Rootless Config and Rotation flow for Static Roles.
//
// To run locally you will need to set the following env vars:
// - POSTGRES_URL_TEST
// - POSTGRES_URL_ROOTLESS
//
// See .github/workflows/build.yml for details.
func TestAccDatabaseSecretBackendStaticRole_Rootless(t *testing.T) {
connURLTestRoot := testutil.SkipTestEnvUnset(t, "POSTGRES_URL_TEST")[0]
connURL := testutil.SkipTestEnvUnset(t, "POSTGRES_URL_ROOTLESS")[0]
Expand All @@ -166,12 +170,6 @@ func TestAccDatabaseSecretBackendStaticRole_Rootless(t *testing.T) {
name := acctest.RandomWithPrefix("staticrole")
resourceName := "vault_database_secret_backend_static_role.test"

testRoleStaticCreate := `
CREATE ROLE "{{name}}" WITH
LOGIN
PASSWORD '{{password}}';
`

// create static database user
testutil.CreateTestPGUser(t, connURLTestRoot, username, "testpassword", testRoleStaticCreate)

Expand Down Expand Up @@ -203,6 +201,55 @@ CREATE ROLE "{{name}}" WITH
})
}

// TestAccDatabaseSecretBackendStaticRole_SkipImportRotation tests the skip
// auto import Rotation configuration.
//
// To run locally you will need to set the following env vars:
// - POSTGRES_URL
// - POSTGRES_URL_TEST
//
// See .github/workflows/build.yml for details.
func TestAccDatabaseSecretBackendStaticRole_SkipImportRotation(t *testing.T) {
connURLTestRoot := testutil.SkipTestEnvUnset(t, "POSTGRES_URL_TEST")[0]
connURL := testutil.SkipTestEnvUnset(t, "POSTGRES_URL")[0]

parsedURL, err := url.Parse(connURLTestRoot)
if err != nil {
t.Fatal(err)
}

vaultAdminUser := parsedURL.User.Username()

backend := acctest.RandomWithPrefix("tf-test-db")
staticUsername := acctest.RandomWithPrefix("user")
dbName := acctest.RandomWithPrefix("db")
roleName := acctest.RandomWithPrefix("staticrole")
resourceName := "vault_database_secret_backend_static_role.test"

// create static database user
testutil.CreateTestPGUser(t, connURLTestRoot, staticUsername, "testpassword", testRoleStaticCreate)

resource.Test(t, resource.TestCase{
ProviderFactories: providerFactories,
PreCheck: func() {
testutil.TestEntPreCheck(t)
SkipIfAPIVersionLT(t, testProvider.Meta(), provider.VaultVersion118)
},
CheckDestroy: testAccDatabaseSecretBackendStaticRoleCheckDestroy,
Steps: []resource.TestStep{
{
Config: testAccDatabaseSecretBackendStaticRoleConfig_skipImportRotation(roleName, staticUsername, dbName, backend, connURL, vaultAdminUser),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(resourceName, "name", roleName),
resource.TestCheckResourceAttr(resourceName, "username", staticUsername),
resource.TestCheckResourceAttr(resourceName, "skip_import_rotation", "true"),
),
},
testutil.GetImportTestStep(resourceName, false, nil, ""),
},
})
}

func testAccDatabaseSecretBackendStaticRoleCheckDestroy(s *terraform.State) error {
for _, rs := range s.RootModule().Resources {
if rs.Type != "vault_database_secret_backend_static_role" {
Expand Down Expand Up @@ -371,6 +418,35 @@ resource "vault_database_secret_backend_static_role" "test" {
`, path, db, connURL, name, username)
}

func testAccDatabaseSecretBackendStaticRoleConfig_skipImportRotation(roleName, staticUsername, db, path, connURL, vaultAdminUser 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 = ["*"]
postgresql {
connection_url = "%s"
username = "%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"
skip_import_rotation = true
rotation_period = 3600
}
`, path, db, connURL, vaultAdminUser, roleName, staticUsername)
}

func testAccDatabaseSecretBackendStaticRoleConfig_rootlessConfig(name, username, db, path, connURL, smPassword string) string {
return fmt.Sprintf(`
resource "vault_mount" "db" {
Expand Down Expand Up @@ -399,3 +475,9 @@ resource "vault_database_secret_backend_static_role" "test" {
}
`, path, db, connURL, name, username, smPassword)
}

var testRoleStaticCreate = `
CREATE ROLE "{{name}}" WITH
LOGIN
PASSWORD '{{password}}';
`
10 changes: 10 additions & 0 deletions vault/resource_database_secrets_mount.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@ func databaseSecretsMountResource() *schema.Resource {
}
}

// getDatabaseSecretsMountSchema is used to define the schema for
// vault_database_secrets_mount
func getDatabaseSecretsMountSchema() schemaMap {
s := getMountSchema("type")
for k, v := range getDatabaseSchema(schema.TypeList) {
Expand Down Expand Up @@ -150,6 +152,12 @@ func addCommonDatabaseSchema(s *schema.Schema) {
}
}

// getCommonDatabaseSchema is used to define the common schema for both
// database resources:
// - vault_database_secrets_mount
// - vault_database_secret_backend_connection
//
// New fields on the DB /config endpoint should be added here.
func getCommonDatabaseSchema() schemaMap {
return schemaMap{
"name": {
Expand Down Expand Up @@ -207,6 +215,8 @@ func getCommonDatabaseSchema() schemaMap {
}
}

// setCommonDatabaseSchema is used to define the schema for
// vault_database_secret_backend_connection
func setCommonDatabaseSchema(s schemaMap) schemaMap {
for k, v := range getCommonDatabaseSchema() {
s[k] = v
Expand Down
3 changes: 3 additions & 0 deletions website/docs/r/database_secret_backend_static_role.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ The following arguments are supported:
Required when using the Rootless Password Rotation workflow for static roles. Only enabled for
select DB engines (Postgres). Requires Vault 1.18+ Enterprise.

* `skip_import_rotation` - (Optional) If set to true, Vault will skip the
initial secret rotation on import. Requires Vault 1.18+ Enterprise.

* `rotation_period` - The amount of time Vault should wait before rotating the password, in seconds.
Mutually exclusive with `rotation_schedule`.

Expand Down

0 comments on commit 0430fcd

Please sign in to comment.