1888323243
This is part 1 of 4 for renaming the `newdbplugin` package. This copies the existing package to the new location but keeps the current one in place so we can migrate the existing references over more easily.
423 lines
12 KiB
Go
423 lines
12 KiB
Go
package database
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/hashicorp/vault/helper/namespace"
|
|
postgreshelper "github.com/hashicorp/vault/helper/testhelpers/postgresql"
|
|
v5 "github.com/hashicorp/vault/sdk/database/dbplugin/v5"
|
|
"github.com/hashicorp/vault/sdk/framework"
|
|
"github.com/hashicorp/vault/sdk/logical"
|
|
)
|
|
|
|
const (
|
|
databaseUser = "postgres"
|
|
defaultPassword = "secret"
|
|
)
|
|
|
|
// Tests that the WAL rollback function rolls back the database password.
|
|
// The database password should be rolled back when:
|
|
// - A WAL entry exists
|
|
// - Password has been altered on the database
|
|
// - Password has not been updated in storage
|
|
func TestBackend_RotateRootCredentials_WAL_rollback(t *testing.T) {
|
|
cluster, sys := getCluster(t)
|
|
defer cluster.Cleanup()
|
|
|
|
config := logical.TestBackendConfig()
|
|
config.StorageView = &logical.InmemStorage{}
|
|
config.System = sys
|
|
|
|
lb, err := Factory(context.Background(), config)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
dbBackend, ok := lb.(*databaseBackend)
|
|
if !ok {
|
|
t.Fatal("could not convert to db backend")
|
|
}
|
|
defer lb.Cleanup(context.Background())
|
|
|
|
cleanup, connURL := postgreshelper.PrepareTestContainer(t, "")
|
|
defer cleanup()
|
|
|
|
connURL = strings.Replace(connURL, "postgres:secret", "{{username}}:{{password}}", -1)
|
|
|
|
// Configure a connection to the database
|
|
data := map[string]interface{}{
|
|
"connection_url": connURL,
|
|
"plugin_name": "postgresql-database-plugin",
|
|
"allowed_roles": []string{"plugin-role-test"},
|
|
"username": databaseUser,
|
|
"password": defaultPassword,
|
|
}
|
|
resp, err := lb.HandleRequest(namespace.RootContext(nil), &logical.Request{
|
|
Operation: logical.UpdateOperation,
|
|
Path: "config/plugin-test",
|
|
Storage: config.StorageView,
|
|
Data: data,
|
|
})
|
|
if err != nil || (resp != nil && resp.IsError()) {
|
|
t.Fatalf("err:%s resp:%#v\n", err, resp)
|
|
}
|
|
|
|
// Create a role
|
|
data = map[string]interface{}{
|
|
"db_name": "plugin-test",
|
|
"creation_statements": testRole,
|
|
"max_ttl": "10m",
|
|
}
|
|
resp, err = lb.HandleRequest(context.Background(), &logical.Request{
|
|
Operation: logical.UpdateOperation,
|
|
Path: "roles/plugin-role-test",
|
|
Storage: config.StorageView,
|
|
Data: data,
|
|
})
|
|
if err != nil || (resp != nil && resp.IsError()) {
|
|
t.Fatalf("err:%s resp:%#v\n", err, resp)
|
|
}
|
|
|
|
// Read credentials to verify this initially works
|
|
credReq := &logical.Request{
|
|
Operation: logical.ReadOperation,
|
|
Path: "creds/plugin-role-test",
|
|
Storage: config.StorageView,
|
|
Data: make(map[string]interface{}),
|
|
}
|
|
credResp, err := lb.HandleRequest(context.Background(), credReq)
|
|
if err != nil || (credResp != nil && credResp.IsError()) {
|
|
t.Fatalf("err:%s resp:%v\n", err, credResp)
|
|
}
|
|
|
|
// Get a connection to the database plugin
|
|
dbi, err := dbBackend.GetConnection(context.Background(),
|
|
config.StorageView, "plugin-test")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Alter the database password so it no longer matches what is in storage
|
|
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
|
defer cancel()
|
|
updateReq := v5.UpdateUserRequest{
|
|
Username: databaseUser,
|
|
Password: &v5.ChangePassword{
|
|
NewPassword: "newSecret",
|
|
},
|
|
}
|
|
_, err = dbi.database.UpdateUser(ctx, updateReq, false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Clear the plugin connection to verify we're no longer able to connect
|
|
err = dbBackend.ClearConnection("plugin-test")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Reading credentials should no longer work
|
|
credResp, err = lb.HandleRequest(namespace.RootContext(nil), credReq)
|
|
if err == nil {
|
|
t.Fatalf("expected authentication to fail when reading credentials")
|
|
}
|
|
|
|
// Put a WAL entry that will be used for rolling back the database password
|
|
walEntry := &rotateRootCredentialsWAL{
|
|
ConnectionName: "plugin-test",
|
|
UserName: databaseUser,
|
|
OldPassword: defaultPassword,
|
|
NewPassword: "newSecret",
|
|
}
|
|
_, err = framework.PutWAL(context.Background(), config.StorageView, rotateRootWALKey, walEntry)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
assertWALCount(t, config.StorageView, 1, rotateRootWALKey)
|
|
|
|
// Trigger an immediate RollbackOperation so that the WAL rollback
|
|
// function can use the WAL entry to roll back the database password
|
|
_, err = lb.HandleRequest(context.Background(), &logical.Request{
|
|
Operation: logical.RollbackOperation,
|
|
Path: "",
|
|
Storage: config.StorageView,
|
|
Data: map[string]interface{}{
|
|
"immediate": true,
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
assertWALCount(t, config.StorageView, 0, rotateRootWALKey)
|
|
|
|
// Reading credentials should work again after the database
|
|
// password has been rolled back.
|
|
credResp, err = lb.HandleRequest(namespace.RootContext(nil), credReq)
|
|
if err != nil || (credResp != nil && credResp.IsError()) {
|
|
t.Fatalf("err:%s resp:%v\n", err, credResp)
|
|
}
|
|
}
|
|
|
|
// Tests that the WAL rollback function does not roll back the database password.
|
|
// The database password should not be rolled back when:
|
|
// - A WAL entry exists
|
|
// - Password has not been altered on the database
|
|
// - Password has not been updated in storage
|
|
func TestBackend_RotateRootCredentials_WAL_no_rollback_1(t *testing.T) {
|
|
cluster, sys := getCluster(t)
|
|
defer cluster.Cleanup()
|
|
|
|
config := logical.TestBackendConfig()
|
|
config.StorageView = &logical.InmemStorage{}
|
|
config.System = sys
|
|
|
|
lb, err := Factory(context.Background(), config)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer lb.Cleanup(context.Background())
|
|
|
|
cleanup, connURL := postgreshelper.PrepareTestContainer(t, "")
|
|
defer cleanup()
|
|
|
|
connURL = strings.Replace(connURL, "postgres:secret", "{{username}}:{{password}}", -1)
|
|
|
|
// Configure a connection to the database
|
|
data := map[string]interface{}{
|
|
"connection_url": connURL,
|
|
"plugin_name": "postgresql-database-plugin",
|
|
"allowed_roles": []string{"plugin-role-test"},
|
|
"username": databaseUser,
|
|
"password": defaultPassword,
|
|
}
|
|
resp, err := lb.HandleRequest(namespace.RootContext(nil), &logical.Request{
|
|
Operation: logical.UpdateOperation,
|
|
Path: "config/plugin-test",
|
|
Storage: config.StorageView,
|
|
Data: data,
|
|
})
|
|
if err != nil || (resp != nil && resp.IsError()) {
|
|
t.Fatalf("err:%s resp:%#v\n", err, resp)
|
|
}
|
|
|
|
// Create a role
|
|
data = map[string]interface{}{
|
|
"db_name": "plugin-test",
|
|
"creation_statements": testRole,
|
|
"max_ttl": "10m",
|
|
}
|
|
resp, err = lb.HandleRequest(context.Background(), &logical.Request{
|
|
Operation: logical.UpdateOperation,
|
|
Path: "roles/plugin-role-test",
|
|
Storage: config.StorageView,
|
|
Data: data,
|
|
})
|
|
if err != nil || (resp != nil && resp.IsError()) {
|
|
t.Fatalf("err:%s resp:%#v\n", err, resp)
|
|
}
|
|
|
|
// Read credentials to verify this initially works
|
|
credReq := &logical.Request{
|
|
Operation: logical.ReadOperation,
|
|
Path: "creds/plugin-role-test",
|
|
Storage: config.StorageView,
|
|
Data: make(map[string]interface{}),
|
|
}
|
|
credResp, err := lb.HandleRequest(context.Background(), credReq)
|
|
if err != nil || (credResp != nil && credResp.IsError()) {
|
|
t.Fatalf("err:%s resp:%v\n", err, credResp)
|
|
}
|
|
|
|
// Put a WAL entry
|
|
walEntry := &rotateRootCredentialsWAL{
|
|
ConnectionName: "plugin-test",
|
|
UserName: databaseUser,
|
|
OldPassword: defaultPassword,
|
|
NewPassword: "newSecret",
|
|
}
|
|
_, err = framework.PutWAL(context.Background(), config.StorageView, rotateRootWALKey, walEntry)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
assertWALCount(t, config.StorageView, 1, rotateRootWALKey)
|
|
|
|
// Trigger an immediate RollbackOperation
|
|
_, err = lb.HandleRequest(context.Background(), &logical.Request{
|
|
Operation: logical.RollbackOperation,
|
|
Path: "",
|
|
Storage: config.StorageView,
|
|
Data: map[string]interface{}{
|
|
"immediate": true,
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
assertWALCount(t, config.StorageView, 0, rotateRootWALKey)
|
|
|
|
// Reading credentials should work
|
|
credResp, err = lb.HandleRequest(namespace.RootContext(nil), credReq)
|
|
if err != nil || (credResp != nil && credResp.IsError()) {
|
|
t.Fatalf("err:%s resp:%v\n", err, credResp)
|
|
}
|
|
}
|
|
|
|
// Tests that the WAL rollback function does not roll back the database password.
|
|
// The database password should not be rolled back when:
|
|
// - A WAL entry exists
|
|
// - Password has been altered on the database
|
|
// - Password has been updated in storage
|
|
func TestBackend_RotateRootCredentials_WAL_no_rollback_2(t *testing.T) {
|
|
cluster, sys := getCluster(t)
|
|
defer cluster.Cleanup()
|
|
|
|
config := logical.TestBackendConfig()
|
|
config.StorageView = &logical.InmemStorage{}
|
|
config.System = sys
|
|
|
|
lb, err := Factory(context.Background(), config)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
dbBackend, ok := lb.(*databaseBackend)
|
|
if !ok {
|
|
t.Fatal("could not convert to db backend")
|
|
}
|
|
defer lb.Cleanup(context.Background())
|
|
|
|
cleanup, connURL := postgreshelper.PrepareTestContainer(t, "")
|
|
defer cleanup()
|
|
|
|
connURL = strings.Replace(connURL, "postgres:secret", "{{username}}:{{password}}", -1)
|
|
|
|
// Configure a connection to the database
|
|
data := map[string]interface{}{
|
|
"connection_url": connURL,
|
|
"plugin_name": "postgresql-database-plugin",
|
|
"allowed_roles": []string{"plugin-role-test"},
|
|
"username": databaseUser,
|
|
"password": defaultPassword,
|
|
}
|
|
resp, err := lb.HandleRequest(namespace.RootContext(nil), &logical.Request{
|
|
Operation: logical.UpdateOperation,
|
|
Path: "config/plugin-test",
|
|
Storage: config.StorageView,
|
|
Data: data,
|
|
})
|
|
if err != nil || (resp != nil && resp.IsError()) {
|
|
t.Fatalf("err:%s resp:%#v\n", err, resp)
|
|
}
|
|
|
|
// Create a role
|
|
data = map[string]interface{}{
|
|
"db_name": "plugin-test",
|
|
"creation_statements": testRole,
|
|
"max_ttl": "10m",
|
|
}
|
|
resp, err = lb.HandleRequest(context.Background(), &logical.Request{
|
|
Operation: logical.UpdateOperation,
|
|
Path: "roles/plugin-role-test",
|
|
Storage: config.StorageView,
|
|
Data: data,
|
|
})
|
|
if err != nil || (resp != nil && resp.IsError()) {
|
|
t.Fatalf("err:%s resp:%#v\n", err, resp)
|
|
}
|
|
|
|
// Read credentials to verify this initially works
|
|
credReq := &logical.Request{
|
|
Operation: logical.ReadOperation,
|
|
Path: "creds/plugin-role-test",
|
|
Storage: config.StorageView,
|
|
Data: make(map[string]interface{}),
|
|
}
|
|
credResp, err := lb.HandleRequest(context.Background(), credReq)
|
|
if err != nil || (credResp != nil && credResp.IsError()) {
|
|
t.Fatalf("err:%s resp:%v\n", err, credResp)
|
|
}
|
|
|
|
// Get a connection to the database plugin
|
|
dbi, err := dbBackend.GetConnection(context.Background(), config.StorageView, "plugin-test")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Alter the database password
|
|
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
|
defer cancel()
|
|
updateReq := v5.UpdateUserRequest{
|
|
Username: databaseUser,
|
|
Password: &v5.ChangePassword{
|
|
NewPassword: "newSecret",
|
|
},
|
|
}
|
|
_, err = dbi.database.UpdateUser(ctx, updateReq, false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Update storage with the new password
|
|
dbConfig, err := dbBackend.DatabaseConfig(context.Background(), config.StorageView,
|
|
"plugin-test")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
dbConfig.ConnectionDetails["password"] = "newSecret"
|
|
entry, err := logical.StorageEntryJSON("config/plugin-test", dbConfig)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
err = config.StorageView.Put(context.Background(), entry)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Clear the plugin connection to verify we can connect to the database
|
|
err = dbBackend.ClearConnection("plugin-test")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Reading credentials should work
|
|
credResp, err = lb.HandleRequest(namespace.RootContext(nil), credReq)
|
|
if err != nil || (credResp != nil && credResp.IsError()) {
|
|
t.Fatalf("err:%s resp:%v\n", err, credResp)
|
|
}
|
|
|
|
// Put a WAL entry
|
|
walEntry := &rotateRootCredentialsWAL{
|
|
ConnectionName: "plugin-test",
|
|
UserName: databaseUser,
|
|
OldPassword: defaultPassword,
|
|
NewPassword: "newSecret",
|
|
}
|
|
_, err = framework.PutWAL(context.Background(), config.StorageView, rotateRootWALKey, walEntry)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
assertWALCount(t, config.StorageView, 1, rotateRootWALKey)
|
|
|
|
// Trigger an immediate RollbackOperation
|
|
_, err = lb.HandleRequest(context.Background(), &logical.Request{
|
|
Operation: logical.RollbackOperation,
|
|
Path: "",
|
|
Storage: config.StorageView,
|
|
Data: map[string]interface{}{
|
|
"immediate": true,
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
assertWALCount(t, config.StorageView, 0, rotateRootWALKey)
|
|
|
|
// Reading credentials should work
|
|
credResp, err = lb.HandleRequest(namespace.RootContext(nil), credReq)
|
|
if err != nil || (credResp != nil && credResp.IsError()) {
|
|
t.Fatalf("err:%s resp:%v\n", err, credResp)
|
|
}
|
|
}
|