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

secrets/db: enable skip auto import rotation of static roles #29093

Merged
merged 10 commits into from
Dec 12, 2024
19 changes: 19 additions & 0 deletions builtin/logical/database/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,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)
Dismissed Show dismissed Hide dismissed
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 +261,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
87 changes: 51 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,13 @@ 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"`
}

func (c *DatabaseConfig) SupportsCredentialType(credentialType v5.CredentialType) bool {
Expand Down Expand Up @@ -205,57 +212,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 +490,10 @@ func (b *databaseBackend) connectionWriteHandler() framework.OperationFunc {
config.PasswordPolicy = passwordPolicyRaw.(string)
}

if skipImportRotationRaw, ok := data.GetOk("skip_static_role_import_rotation"); ok {
vinay-gopalan marked this conversation as resolved.
Show resolved Hide resolved
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 +503,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