mirror of
https://github.com/hashicorp/vault.git
synced 2026-02-18 18:38:08 -05:00
* Initial implementation * Use rotation_statements, handle both password and private_key * Remove debug prints * Merge in main * Remove duplicated error text * Rename keypair root rotation function * Use NewRotateRootCredentialsWALPasswordEntry * Add changelog file * Move back to original file for now, for review * put generatePassword into function * Fix names, call helper for generatePassword * Generalize the rotation flow and keypair path * Fix conditional check, remove new file * Fix changelog * Add test file * Fix username check var name * Fix name variable * Return an error when both fields are set during rotation, and return an error if somehow walEntry is nil * Fix test godoc * Remove print * change rotated key bits to 4096 Co-authored-by: Robert <17119716+robmonte@users.noreply.github.com>
This commit is contained in:
parent
45e3f36c28
commit
23fd7533aa
9 changed files with 340 additions and 110 deletions
|
|
@ -131,14 +131,7 @@ func Backend(conf *logical.BackendConfig) *databaseBackend {
|
|||
WALRollback: b.walRollback,
|
||||
WALRollbackMinAge: minRootCredRollbackAge,
|
||||
BackendType: logical.TypeLogical,
|
||||
RotateCredential: func(ctx context.Context, request *logical.Request) error {
|
||||
name, err := b.getDatabaseConfigNameFromRotationID(request.RotationID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = b.rotateRootCredentials(ctx, request, name)
|
||||
return err
|
||||
},
|
||||
RotateCredential: b.rotateRootCredential,
|
||||
}
|
||||
|
||||
b.logger = conf.Logger
|
||||
|
|
|
|||
|
|
@ -156,21 +156,9 @@ func (b *databaseBackend) pathCredsCreateRead() framework.OperationFunc {
|
|||
// Generate the credential based on the role's credential type
|
||||
switch role.CredentialType {
|
||||
case v5.CredentialTypePassword:
|
||||
generator, err := newPasswordGenerator(role.CredentialConfig)
|
||||
password, err := b.generateNewPassword(ctx, role.CredentialConfig, dbConfig.PasswordPolicy, dbi)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to construct credential generator: %s", err)
|
||||
}
|
||||
|
||||
// Fall back to database config-level password policy if not set on role
|
||||
if generator.PasswordPolicy == "" {
|
||||
generator.PasswordPolicy = dbConfig.PasswordPolicy
|
||||
}
|
||||
|
||||
// Generate the password
|
||||
password, err := generator.generate(ctx, b, dbi.database)
|
||||
if err != nil {
|
||||
b.CloseIfShutdown(dbi, err)
|
||||
return nil, fmt.Errorf("failed to generate password: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Set input credential
|
||||
|
|
@ -178,15 +166,9 @@ func (b *databaseBackend) pathCredsCreateRead() framework.OperationFunc {
|
|||
newUserReq.Password = password
|
||||
|
||||
case v5.CredentialTypeRSAPrivateKey:
|
||||
generator, err := newRSAKeyGenerator(role.CredentialConfig)
|
||||
public, private, err := b.generateNewKeypair(role.CredentialConfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to construct credential generator: %s", err)
|
||||
}
|
||||
|
||||
// Generate the RSA key pair
|
||||
public, private, err := generator.generate(b.GetRandomReader())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate RSA key pair: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Set input credential
|
||||
|
|
|
|||
|
|
@ -173,7 +173,7 @@ func TestBackend_Roles_CredentialTypes(t *testing.T) {
|
|||
Storage: config.StorageView,
|
||||
Data: map[string]interface{}{
|
||||
"db_name": "test-database",
|
||||
"creation_statements": "CREATE USER {{name}}",
|
||||
"creation_statements": `CREATE USER "{{name}}"`,
|
||||
"credential_type": tt.args.credentialType.String(),
|
||||
"credential_config": tt.args.credentialConfig,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -74,10 +74,21 @@ func pathRotateRootCredentials(b *databaseBackend) []*framework.Path {
|
|||
}
|
||||
}
|
||||
|
||||
func (b *databaseBackend) rotateRootCredential(ctx context.Context, req *logical.Request) error {
|
||||
name, err := b.getDatabaseConfigNameFromRotationID(req.RotationID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = b.performRootRotation(ctx, req, name)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (b *databaseBackend) pathRotateRootCredentialsUpdate() framework.OperationFunc {
|
||||
return func(ctx context.Context, req *logical.Request, data *framework.FieldData) (resp *logical.Response, err error) {
|
||||
name := data.Get("name").(string)
|
||||
resp, err = b.rotateRootCredentials(ctx, req, name)
|
||||
resp, err = b.performRootRotation(ctx, req, name)
|
||||
if err != nil {
|
||||
b.Logger().Error("failed to rotate root credential on user request", "path", req.Path, "error", err.Error())
|
||||
} else {
|
||||
|
|
@ -87,7 +98,7 @@ func (b *databaseBackend) pathRotateRootCredentialsUpdate() framework.OperationF
|
|||
}
|
||||
}
|
||||
|
||||
func (b *databaseBackend) rotateRootCredentials(ctx context.Context, req *logical.Request, name string) (resp *logical.Response, err error) {
|
||||
func (b *databaseBackend) performRootRotation(ctx context.Context, req *logical.Request, name string) (resp *logical.Response, err error) {
|
||||
if name == "" {
|
||||
return logical.ErrorResponse(respErrEmptyName), nil
|
||||
}
|
||||
|
|
@ -118,21 +129,37 @@ func (b *databaseBackend) rotateRootCredentials(ctx context.Context, req *logica
|
|||
}
|
||||
}()
|
||||
|
||||
rootUsername, ok := config.ConnectionDetails["username"].(string)
|
||||
if !ok || rootUsername == "" {
|
||||
rootUsername, userOk := config.ConnectionDetails["username"].(string)
|
||||
if !userOk || rootUsername == "" {
|
||||
return nil, fmt.Errorf("unable to rotate root credentials: no username in configuration")
|
||||
}
|
||||
|
||||
rootPassword, ok := config.ConnectionDetails["password"].(string)
|
||||
if !ok || rootPassword == "" {
|
||||
return nil, fmt.Errorf("unable to rotate root credentials: no password in configuration")
|
||||
}
|
||||
|
||||
dbi, err := b.GetConnection(ctx, req.Storage, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dbType, err := dbi.database.Type()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to determine database type: %w", err)
|
||||
}
|
||||
|
||||
rootPassword, passOk := config.ConnectionDetails["password"].(string)
|
||||
isPasswordSet := passOk && rootPassword != ""
|
||||
|
||||
rootPrivateKey, pkeyOk := config.ConnectionDetails["private_key"].(string)
|
||||
isPrivateKeySet := pkeyOk && rootPrivateKey != ""
|
||||
|
||||
// If both are unset, return an error. If we get past this, we know at least one is set.
|
||||
if !isPasswordSet && !isPrivateKeySet {
|
||||
return nil, fmt.Errorf("unable to rotate root credentials: both private_key and password fields are missing from the configuration")
|
||||
}
|
||||
|
||||
// If both are set, return an error.
|
||||
if isPasswordSet && isPrivateKeySet {
|
||||
return nil, fmt.Errorf("unable to rotate root credentials: both private_key and password fields are set in the configuration")
|
||||
}
|
||||
|
||||
// Take the write lock on the instance
|
||||
dbi.Lock()
|
||||
defer func() {
|
||||
|
|
@ -148,42 +175,67 @@ func (b *databaseBackend) rotateRootCredentials(ctx context.Context, req *logica
|
|||
}
|
||||
}()
|
||||
|
||||
generator, err := newPasswordGenerator(nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to construct credential generator: %s", err)
|
||||
}
|
||||
generator.PasswordPolicy = config.PasswordPolicy
|
||||
var walEntry *rotateRootCredentialsWAL
|
||||
var updateReq v5.UpdateUserRequest
|
||||
// If private key is set, use it. This takes precedence over password.
|
||||
if isPrivateKeySet {
|
||||
// For now snowflake is the only database type to support private key rotation.
|
||||
if dbType == "snowflake" {
|
||||
newKeypairCredentialConfig := map[string]interface{}{
|
||||
"format": "pkcs8",
|
||||
"key_bits": 4096,
|
||||
}
|
||||
newPublicKey, newPrivateKey, err := b.generateNewKeypair(newKeypairCredentialConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config.ConnectionDetails["private_key"] = string(newPrivateKey)
|
||||
|
||||
// Generate new credentials
|
||||
oldPassword := config.ConnectionDetails["password"].(string)
|
||||
newPassword, err := generator.generate(ctx, b, dbi.database)
|
||||
if err != nil {
|
||||
b.CloseIfShutdown(dbi, err)
|
||||
return nil, fmt.Errorf("failed to generate password: %s", err)
|
||||
oldPrivateKey := config.ConnectionDetails["private_key"].(string)
|
||||
walEntry = NewRotateRootCredentialsWALPrivateKeyEntry(name, rootUsername, string(newPublicKey), string(newPrivateKey), oldPrivateKey)
|
||||
updateReq = v5.UpdateUserRequest{
|
||||
Username: rootUsername,
|
||||
CredentialType: v5.CredentialTypeRSAPrivateKey,
|
||||
PublicKey: &v5.ChangePublicKey{
|
||||
NewPublicKey: newPublicKey,
|
||||
Statements: v5.Statements{
|
||||
Commands: config.RootCredentialsRotateStatements,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Private key isn't set so we're using the password field.
|
||||
newPassword, err := b.generateNewPassword(ctx, nil, config.PasswordPolicy, dbi)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config.ConnectionDetails["password"] = newPassword
|
||||
|
||||
oldPassword := config.ConnectionDetails["password"].(string)
|
||||
walEntry = NewRotateRootCredentialsWALPasswordEntry(name, rootUsername, newPassword, oldPassword)
|
||||
updateReq = v5.UpdateUserRequest{
|
||||
Username: rootUsername,
|
||||
CredentialType: v5.CredentialTypePassword,
|
||||
Password: &v5.ChangePassword{
|
||||
NewPassword: newPassword,
|
||||
Statements: v5.Statements{
|
||||
Commands: config.RootCredentialsRotateStatements,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if walEntry == nil {
|
||||
return nil, fmt.Errorf("unable to rotate root credentials: no valid credential type found")
|
||||
}
|
||||
config.ConnectionDetails["password"] = newPassword
|
||||
|
||||
// Write a WAL entry
|
||||
walID, err := framework.PutWAL(ctx, req.Storage, rotateRootWALKey, &rotateRootCredentialsWAL{
|
||||
ConnectionName: name,
|
||||
UserName: rootUsername,
|
||||
OldPassword: oldPassword,
|
||||
NewPassword: newPassword,
|
||||
})
|
||||
walID, err := framework.PutWAL(ctx, req.Storage, rotateRootWALKey, walEntry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
updateReq := v5.UpdateUserRequest{
|
||||
Username: rootUsername,
|
||||
CredentialType: v5.CredentialTypePassword,
|
||||
Password: &v5.ChangePassword{
|
||||
NewPassword: newPassword,
|
||||
Statements: v5.Statements{
|
||||
Commands: config.RootCredentialsRotateStatements,
|
||||
},
|
||||
},
|
||||
}
|
||||
newConfigDetails, err := dbi.database.UpdateUser(ctx, updateReq, true)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to update user: %w", err)
|
||||
|
|
|
|||
171
builtin/logical/database/path_rotate_credentials_test.go
Normal file
171
builtin/logical/database/path_rotate_credentials_test.go
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -22,8 +22,32 @@ const rotateRootWALKey = "rotateRootWALKey"
|
|||
type rotateRootCredentialsWAL struct {
|
||||
ConnectionName string
|
||||
UserName string
|
||||
NewPassword string
|
||||
OldPassword string
|
||||
|
||||
NewPassword string
|
||||
OldPassword string
|
||||
|
||||
NewPublicKey string
|
||||
NewPrivateKey string
|
||||
OldPrivateKey string
|
||||
}
|
||||
|
||||
func NewRotateRootCredentialsWALPasswordEntry(connectionName, userName, newPassword, oldPassword string) *rotateRootCredentialsWAL {
|
||||
return &rotateRootCredentialsWAL{
|
||||
ConnectionName: connectionName,
|
||||
UserName: userName,
|
||||
NewPassword: newPassword,
|
||||
OldPassword: oldPassword,
|
||||
}
|
||||
}
|
||||
|
||||
func NewRotateRootCredentialsWALPrivateKeyEntry(connectionName, userName, newPublicKey, newPrivateKey, oldPrivateKey string) *rotateRootCredentialsWAL {
|
||||
return &rotateRootCredentialsWAL{
|
||||
ConnectionName: connectionName,
|
||||
UserName: userName,
|
||||
NewPublicKey: newPublicKey,
|
||||
NewPrivateKey: newPrivateKey,
|
||||
OldPrivateKey: oldPrivateKey,
|
||||
}
|
||||
}
|
||||
|
||||
// walRollback handles WAL entries that result from partial failures
|
||||
|
|
|
|||
|
|
@ -129,12 +129,7 @@ func TestBackend_RotateRootCredentials_WAL_rollback(t *testing.T) {
|
|||
}
|
||||
|
||||
// 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",
|
||||
}
|
||||
walEntry := NewRotateRootCredentialsWALPasswordEntry("plugin-test", databaseUser, "newSecret", defaultPassword)
|
||||
_, err = framework.PutWAL(context.Background(), config.StorageView, rotateRootWALKey, walEntry)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
@ -235,12 +230,7 @@ func TestBackend_RotateRootCredentials_WAL_no_rollback_1(t *testing.T) {
|
|||
}
|
||||
|
||||
// Put a WAL entry
|
||||
walEntry := &rotateRootCredentialsWAL{
|
||||
ConnectionName: "plugin-test",
|
||||
UserName: databaseUser,
|
||||
OldPassword: defaultPassword,
|
||||
NewPassword: "newSecret",
|
||||
}
|
||||
walEntry := NewRotateRootCredentialsWALPasswordEntry("plugin-test", databaseUser, "newSecret", defaultPassword)
|
||||
_, err = framework.PutWAL(context.Background(), config.StorageView, rotateRootWALKey, walEntry)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
@ -391,12 +381,7 @@ func TestBackend_RotateRootCredentials_WAL_no_rollback_2(t *testing.T) {
|
|||
}
|
||||
|
||||
// Put a WAL entry
|
||||
walEntry := &rotateRootCredentialsWAL{
|
||||
ConnectionName: "plugin-test",
|
||||
UserName: databaseUser,
|
||||
OldPassword: defaultPassword,
|
||||
NewPassword: "newSecret",
|
||||
}
|
||||
walEntry := NewRotateRootCredentialsWALPasswordEntry("plugin-test", databaseUser, "newSecret", defaultPassword)
|
||||
_, err = framework.PutWAL(context.Background(), config.StorageView, rotateRootWALKey, walEntry)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
|
|||
|
|
@ -498,21 +498,9 @@ func (b *databaseBackend) setStaticAccount(ctx context.Context, s logical.Storag
|
|||
|
||||
switch input.Role.CredentialType {
|
||||
case v5.CredentialTypePassword:
|
||||
generator, err := newPasswordGenerator(input.Role.CredentialConfig)
|
||||
newPassword, err := b.generateNewPassword(ctx, input.Role.CredentialConfig, dbConfig.PasswordPolicy, dbi)
|
||||
if err != nil {
|
||||
return output, fmt.Errorf("failed to construct credential generator: %s", err)
|
||||
}
|
||||
|
||||
// Fall back to database config-level password policy if not set on role
|
||||
if generator.PasswordPolicy == "" {
|
||||
generator.PasswordPolicy = dbConfig.PasswordPolicy
|
||||
}
|
||||
|
||||
// Generate the password
|
||||
newPassword, err := generator.generate(ctx, b, dbi.database)
|
||||
if err != nil {
|
||||
b.CloseIfShutdown(dbi, err)
|
||||
return output, fmt.Errorf("failed to generate password: %s", err)
|
||||
return output, err
|
||||
}
|
||||
|
||||
// Set new credential in WAL entry and update user request
|
||||
|
|
@ -526,15 +514,9 @@ func (b *databaseBackend) setStaticAccount(ctx context.Context, s logical.Storag
|
|||
// Set new credential in static account
|
||||
input.Role.StaticAccount.Password = newPassword
|
||||
case v5.CredentialTypeRSAPrivateKey:
|
||||
generator, err := newRSAKeyGenerator(input.Role.CredentialConfig)
|
||||
public, private, err := b.generateNewKeypair(input.Role.CredentialConfig)
|
||||
if err != nil {
|
||||
return output, fmt.Errorf("failed to construct credential generator: %s", err)
|
||||
}
|
||||
|
||||
// Generate the RSA key pair
|
||||
public, private, err := generator.generate(b.GetRandomReader())
|
||||
if err != nil {
|
||||
return output, fmt.Errorf("failed to generate RSA key pair: %s", err)
|
||||
return output, err
|
||||
}
|
||||
|
||||
// Set new credential in WAL entry and update user request
|
||||
|
|
@ -600,6 +582,44 @@ func (b *databaseBackend) setStaticAccount(ctx context.Context, s logical.Storag
|
|||
return &setStaticAccountOutput{RotationTime: lvr}, nil
|
||||
}
|
||||
|
||||
// Returns a new password, error.
|
||||
func (b *databaseBackend) generateNewPassword(ctx context.Context, credentialConfig map[string]interface{}, passwordPolicy string, dbi *dbPluginInstance) (string, error) {
|
||||
generator, err := newPasswordGenerator(credentialConfig)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to construct credential generator: %s", err)
|
||||
}
|
||||
|
||||
// Fall back to database config-level password policy if not set on role
|
||||
if generator.PasswordPolicy == "" {
|
||||
generator.PasswordPolicy = passwordPolicy
|
||||
}
|
||||
|
||||
// Generate the password
|
||||
newPassword, err := generator.generate(ctx, b, dbi.database)
|
||||
if err != nil {
|
||||
b.CloseIfShutdown(dbi, err)
|
||||
return "", fmt.Errorf("failed to generate password: %s", err)
|
||||
}
|
||||
|
||||
return newPassword, nil
|
||||
}
|
||||
|
||||
// Returns a new public key, private key, error.
|
||||
func (b *databaseBackend) generateNewKeypair(credentialConfig map[string]interface{}) ([]byte, []byte, error) {
|
||||
generator, err := newRSAKeyGenerator(credentialConfig)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to construct credential generator: %s", err)
|
||||
}
|
||||
|
||||
// Generate the RSA key pair
|
||||
public, private, err := generator.generate(b.GetRandomReader())
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to generate RSA key pair: %s", err)
|
||||
}
|
||||
|
||||
return public, private, nil
|
||||
}
|
||||
|
||||
// initQueue preforms the necessary checks and initializations needed to perform
|
||||
// automatic credential rotation for roles associated with static accounts. This
|
||||
// method verifies if a queue is needed (primary server or local mount), and if
|
||||
|
|
|
|||
3
changelog/_9432.txt
Normal file
3
changelog/_9432.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
```release-note:improvement
|
||||
secrets/database: Add root rotation support for Snowflake database secrets engines using key-pair credentials.
|
||||
```
|
||||
Loading…
Reference in a new issue