Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

backport release/1.18.x: db skip auto import rotation #29310

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions builtin/logical/database/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"github.com/hashicorp/vault/sdk/helper/locksutil"
"github.com/hashicorp/vault/sdk/logical"
"github.com/hashicorp/vault/sdk/queue"
"github.com/mitchellh/mapstructure"
)

const (
Expand Down Expand Up @@ -205,6 +206,19 @@ func (b *databaseBackend) DatabaseConfig(ctx context.Context, s logical.Storage,
return &config, nil
}

// ConnectionDetails decodes the DatabaseConfig.ConnectionDetails map into a
// struct
func (b *databaseBackend) ConnectionDetails(ctx context.Context, config *DatabaseConfig) (*ConnectionDetails, error) {
cd := &ConnectionDetails{}

err := mapstructure.WeakDecode(config.ConnectionDetails, &cd)
if err != nil {
return nil, err
}

return cd, nil
}

type upgradeStatements struct {
// This json tag has a typo in it, the new version does not. This
// necessitates this upgrade logic.
Expand All @@ -228,6 +242,20 @@ func (b *databaseBackend) StaticRole(ctx context.Context, s logical.Storage, rol
return b.roleAtPath(ctx, s, roleName, databaseStaticRolePath)
}

func (b *databaseBackend) StoreStaticRole(ctx context.Context, s logical.Storage, r *roleEntry) error {
logger := b.Logger().With("role", r.Name, "database", r.DBName)
entry, err := logical.StorageEntryJSON(databaseStaticRolePath+r.Name, r)
if err != nil {
logger.Error("unable to encode entry for storage", "error", err)
return err
}
if err := s.Put(ctx, entry); err != nil {
logger.Error("unable to write to storage", "error", err)
return err
}
return nil
}

func (b *databaseBackend) roleAtPath(ctx context.Context, s logical.Storage, roleName string, pathPrefix string) (*roleEntry, error) {
entry, err := s.Get(ctx, pathPrefix+roleName)
if err != nil {
Expand All @@ -247,6 +275,11 @@ func (b *databaseBackend) roleAtPath(ctx context.Context, s logical.Storage, rol
return nil, err
}

// handle upgrade for new field Name
if result.Name == "" {
result.Name = roleName
}

switch {
case upgradeCh.Statements != nil:
var stmts v4.Statements
Expand Down
7 changes: 6 additions & 1 deletion builtin/logical/database/backend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ func TestBackend_RoleUpgrade(t *testing.T) {
backend := &databaseBackend{}

roleExpected := &roleEntry{
Name: "test",
Statements: v4.Statements{
CreationStatements: "test",
Creation: []string{"test"},
Expand Down Expand Up @@ -211,6 +212,7 @@ func TestBackend_config_connection(t *testing.T) {
"password_policy": "",
"plugin_version": "",
"verify_connection": false,
"skip_static_role_import_rotation": false,
}
configReq.Operation = logical.ReadOperation
resp, err = b.HandleRequest(namespace.RootContext(nil), configReq)
Expand Down Expand Up @@ -266,6 +268,7 @@ func TestBackend_config_connection(t *testing.T) {
"password_policy": "",
"plugin_version": "",
"verify_connection": false,
"skip_static_role_import_rotation": false,
}
configReq.Operation = logical.ReadOperation
resp, err = b.HandleRequest(namespace.RootContext(nil), configReq)
Expand Down Expand Up @@ -310,6 +313,7 @@ func TestBackend_config_connection(t *testing.T) {
"password_policy": "",
"plugin_version": "",
"verify_connection": false,
"skip_static_role_import_rotation": false,
}
configReq.Operation = logical.ReadOperation
resp, err = b.HandleRequest(namespace.RootContext(nil), configReq)
Expand Down Expand Up @@ -417,7 +421,7 @@ func TestBackend_basic(t *testing.T) {
defer b.Cleanup(context.Background())

cleanup, connURL := postgreshelper.PrepareTestContainer(t)
defer cleanup()
t.Cleanup(cleanup)

// Configure a connection
data := map[string]interface{}{
Expand Down Expand Up @@ -768,6 +772,7 @@ func TestBackend_connectionCrud(t *testing.T) {
"password_policy": "",
"plugin_version": "",
"verify_connection": false,
"skip_static_role_import_rotation": false,
}
resp, err = client.Read("database/config/plugin-test")
if err != nil {
Expand Down
93 changes: 57 additions & 36 deletions builtin/logical/database/path_config_connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,19 @@ type DatabaseConfig struct {

PasswordPolicy string `json:"password_policy" structs:"password_policy" mapstructure:"password_policy"`
VerifyConnection bool `json:"verify_connection" structs:"verify_connection" mapstructure:"verify_connection"`

// SkipStaticRoleImportRotation is a flag to toggle wether or not a given
// static account's password should be rotated on creation of the static
// roles associated with this DB config. This can be overridden at the
// role-level by the role's skip_import_rotation field. The default is
// false. Enterprise only.
SkipStaticRoleImportRotation bool `json:"skip_static_role_import_rotation" structs:"skip_static_role_import_rotation" mapstructure:"skip_static_role_import_rotation"`
}

// ConnectionDetails represents the DatabaseConfig.ConnectionDetails map as a
// struct
type ConnectionDetails struct {
SelfManaged bool `json:"self_managed" structs:"self_managed" mapstructure:"self_managed"`
}

func (c *DatabaseConfig) SupportsCredentialType(credentialType v5.CredentialType) bool {
Expand Down Expand Up @@ -205,57 +218,60 @@ func (b *databaseBackend) reloadPlugin() framework.OperationFunc {
// pathConfigurePluginConnection returns a configured framework.Path setup to
// operate on plugins.
func pathConfigurePluginConnection(b *databaseBackend) *framework.Path {
return &framework.Path{
Pattern: fmt.Sprintf("config/%s", framework.GenericNameRegex("name")),

DisplayAttrs: &framework.DisplayAttributes{
OperationPrefix: operationPrefixDatabase,
fields := map[string]*framework.FieldSchema{
"name": {
Type: framework.TypeString,
Description: "Name of this database connection",
},

Fields: map[string]*framework.FieldSchema{
"name": {
Type: framework.TypeString,
Description: "Name of this database connection",
},

"plugin_name": {
Type: framework.TypeString,
Description: `The name of a builtin or previously registered
"plugin_name": {
Type: framework.TypeString,
Description: `The name of a builtin or previously registered
plugin known to vault. This endpoint will create an instance of
that plugin type.`,
},
},

"plugin_version": {
Type: framework.TypeString,
Description: `The version of the plugin to use.`,
},
"plugin_version": {
Type: framework.TypeString,
Description: `The version of the plugin to use.`,
},

"verify_connection": {
Type: framework.TypeBool,
Default: true,
Description: `If true, the connection details are verified by
"verify_connection": {
Type: framework.TypeBool,
Default: true,
Description: `If true, the connection details are verified by
actually connecting to the database. Defaults to true.`,
},
},

"allowed_roles": {
Type: framework.TypeCommaStringSlice,
Description: `Comma separated string or array of the role names
"allowed_roles": {
Type: framework.TypeCommaStringSlice,
Description: `Comma separated string or array of the role names
allowed to get creds from this database connection. If empty no
roles are allowed. If "*" all roles are allowed.`,
},
},

"root_rotation_statements": {
Type: framework.TypeStringSlice,
Description: `Specifies the database statements to be executed
"root_rotation_statements": {
Type: framework.TypeStringSlice,
Description: `Specifies the database statements to be executed
to rotate the root user's credentials. See the plugin's API
page for more information on support and formatting for this
parameter.`,
},
"password_policy": {
Type: framework.TypeString,
Description: `Password policy to use when generating passwords.`,
},
},
"password_policy": {
Type: framework.TypeString,
Description: `Password policy to use when generating passwords.`,
},
}
AddConnectionFieldsEnt(fields)

return &framework.Path{
Pattern: fmt.Sprintf("config/%s", framework.GenericNameRegex("name")),

DisplayAttrs: &framework.DisplayAttributes{
OperationPrefix: operationPrefixDatabase,
},

Fields: fields,

ExistenceCheck: b.connectionExistenceCheck(),

Expand Down Expand Up @@ -480,6 +496,10 @@ func (b *databaseBackend) connectionWriteHandler() framework.OperationFunc {
config.PasswordPolicy = passwordPolicyRaw.(string)
}

if skipImportRotationRaw, ok := data.GetOk("skip_static_role_import_rotation"); ok {
config.SkipStaticRoleImportRotation = skipImportRotationRaw.(bool)
}

// Remove these entries from the data before we store it keyed under
// ConnectionDetails.
delete(data.Raw, "name")
Expand All @@ -489,6 +509,7 @@ func (b *databaseBackend) connectionWriteHandler() framework.OperationFunc {
delete(data.Raw, "verify_connection")
delete(data.Raw, "root_rotation_statements")
delete(data.Raw, "password_policy")
delete(data.Raw, "skip_static_role_import_rotation")

id, err := uuid.GenerateUUID()
if err != nil {
Expand Down
13 changes: 13 additions & 0 deletions builtin/logical/database/path_config_connection_ce.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

//go:build !enterprise

package database

import "github.com/hashicorp/vault/sdk/framework"

// AddConnectionFieldsEnt is a no-op for community edition
func AddConnectionFieldsEnt(fields map[string]*framework.FieldSchema) {
// no-op
}
Loading
Loading