vault/builtin/logical/database/path_rotate_credentials_test.go
Vault Automation 0c6c13dd38
license: update headers to IBM Corp. (#10229) (#10233)
* license: update headers to IBM Corp.
* `make proto`
* update offset because source file changed

Signed-off-by: Ryan Cragun <me@ryan.ec>
Co-authored-by: Ryan Cragun <me@ryan.ec>
2025-10-21 15:20:20 -06:00

172 lines
5.2 KiB
Go

// Copyright IBM Corp. 2016, 2025
// SPDX-License-Identifier: BUSL-1.1
package database
import (
"context"
"os"
"strings"
"testing"
"github.com/hashicorp/vault/helper/namespace"
postgreshelper "github.com/hashicorp/vault/helper/testhelpers/postgresql"
"github.com/hashicorp/vault/sdk/logical"
)
// TestRotateRootPassword_Postgres tests database secrets root rotation for password credentials with Postgres.
func TestRotateRootPassword_Postgres(t *testing.T) {
cleanup, connURL := postgreshelper.PrepareTestContainer(t)
defer cleanup()
cluster, sys := getClusterWithFactory(t, Factory)
defer cluster.Cleanup()
connURL = strings.Replace(connURL, "postgres:secret", "{{username}}:{{password}}", 1)
testRotateRoot(t, sys, true, connURL, "postgresql-database-plugin", "postgres", "secret")
}
// TestRotateRootKeypair_Snowflake_Acc tests database secrets root rotation for private key credentials with Snowflake.
func TestRotateRootKeypair_Snowflake_Acc(t *testing.T) {
// SNOWFLAKE_PRIVATE_KEY is the path to the private key file.
privateKeyPath, ok := os.LookupEnv("VAULT_SNOWFLAKE_PRIVATE_KEY")
if !ok {
t.Skip("VAULT_SNOWFLAKE_PRIVATE_KEY not set, skipping test")
}
keyFile, err := os.ReadFile(privateKeyPath)
if err != nil {
t.Fatalf("failed to read private key file: %s", err)
}
connURL, ok := os.LookupEnv("VAULT_SNOWFLAKE_CONNECTION_URL")
if !ok {
t.Skip("VAULT_SNOWFLAKE_CONNECTION_URL not set, skipping test")
}
username, ok := os.LookupEnv("VAULT_SNOWFLAKE_USERNAME")
if !ok {
t.Skip("VAULT_SNOWFLAKE_USERNAME not set, skipping test")
}
cluster, sys := getClusterWithFactory(t, Factory)
defer cluster.Cleanup()
testRotateRoot(t, sys, false, connURL, "snowflake-database-plugin", username, string(keyFile))
}
// Helper function to run rotate root tests.
func testRotateRoot(t *testing.T, sys logical.SystemView, isPassword bool, connURL string, pluginName, username, credential string) {
config := logical.TestBackendConfig()
config.StorageView = &logical.InmemStorage{}
config.System = sys
lb, err := Factory(context.Background(), config)
if err != nil {
t.Fatal(err)
}
b, ok := lb.(*databaseBackend)
if !ok {
t.Fatal("could not convert to db backend")
}
defer b.Cleanup(context.Background())
// Configure a connection
configData := map[string]any{
"connection_url": connURL,
"plugin_name": pluginName,
"verify_connection": false,
"allowed_roles": []string{"*"},
"name": "plugin-test",
"username": username,
}
if isPassword {
configData["password"] = credential
} else {
configData["private_key"] = credential
}
req := &logical.Request{
Operation: logical.UpdateOperation,
Path: "config/plugin-test",
Storage: config.StorageView,
Data: configData,
}
resp, err := b.HandleRequest(namespace.RootContext(context.Background()), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%s resp:%#v\n", err, resp)
}
// Create a dynamic role
req = &logical.Request{
Operation: logical.CreateOperation,
Path: "roles/test",
Storage: config.StorageView,
Data: map[string]interface{}{
"db_name": "plugin-test",
"creation_statements": `CREATE USER "{{name}}"`,
},
}
resp, err = b.HandleRequest(context.Background(), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%s resp:%#v\n", err, resp)
}
// Read some creds to validate the connection
req = &logical.Request{
Operation: logical.ReadOperation,
Path: "creds/test",
Storage: config.StorageView,
}
resp, err = b.HandleRequest(context.Background(), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%s resp:%#v\n", err, resp)
}
// Rotate the root credentials
req = &logical.Request{
Operation: logical.UpdateOperation,
Path: "rotate-root/plugin-test",
Storage: config.StorageView,
}
resp, err = b.HandleRequest(namespace.RootContext(context.Background()), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%s resp:%#v\n", err, resp)
}
// Read creds a second time to validate the root rotation still works
req = &logical.Request{
Operation: logical.ReadOperation,
Path: "creds/test",
Storage: config.StorageView,
}
resp, err = b.HandleRequest(context.Background(), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%s resp:%#v\n", err, resp)
}
// Write back the original credential to ensure it no longer works
req = &logical.Request{
Operation: logical.UpdateOperation,
Path: "config/plugin-test",
Storage: config.StorageView,
Data: configData,
}
resp, err = b.HandleRequest(namespace.RootContext(context.Background()), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%s resp:%#v\n", err, resp)
}
// Read some more creds again and expect an error]
// Note: For Snowflake, this step may fail if you are using the account's 2nd private key
req = &logical.Request{
Operation: logical.ReadOperation,
Path: "creds/test",
Storage: config.StorageView,
}
resp, err = b.HandleRequest(context.Background(), req)
if err == nil || (resp != nil && !resp.IsError()) {
t.Fatalf("expected authentication error but did not receive an error, resp:%#v\n", resp)
}
}