vault/builtin/logical/database/rollback_test.go
hashicorp-copywrite[bot] 0b12cdcfd1
[COMPLIANCE] License changes (#22290)
* Adding explicit MPL license for sub-package.

This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository.

* Adding explicit MPL license for sub-package.

This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository.

* Updating the license from MPL to Business Source License.

Going forward, this project will be licensed under the Business Source License v1.1. Please see our blog post for more details at https://hashi.co/bsl-blog, FAQ at www.hashicorp.com/licensing-faq, and details of the license at www.hashicorp.com/bsl.

* add missing license headers

* Update copyright file headers to BUS-1.1

* Fix test that expected exact offset on hcl file

---------

Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com>
Co-authored-by: Sarah Thompson <sthompson@hashicorp.com>
Co-authored-by: Brian Kassouf <bkassouf@hashicorp.com>
2023-08-10 18:14:03 -07:00

426 lines
12 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
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.ReplaceAll(connURL, "postgres:secret", "{{username}}:{{password}}")
// 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.ReplaceAll(connURL, "postgres:secret", "{{username}}:{{password}}")
// 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.ReplaceAll(connURL, "postgres:secret", "{{username}}:{{password}}")
// 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)
}
}