mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-31 10:37:56 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			426 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			426 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright (c) HashiCorp, Inc.
 | |
| // SPDX-License-Identifier: MPL-2.0
 | |
| 
 | |
| 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)
 | |
| 	}
 | |
| }
 | 
