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

Combined Database backend: remove create/delete support #6951

Merged
merged 4 commits into from
Jun 23, 2019
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
238 changes: 102 additions & 136 deletions builtin/logical/database/path_roles.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package database

import (
"context"
"errors"
"fmt"
"strings"
"time"
Expand Down Expand Up @@ -88,32 +87,6 @@ func fieldsForType(roleType string) map[string]*framework.FieldSchema {
Type: framework.TypeString,
Description: "Name of the database this role acts on.",
},
"creation_statements": {
Type: framework.TypeStringSlice,
Description: `Specifies the database statements executed to
create and configure a user. See the plugin's API page for more
information on support and formatting for this parameter.`,
},
"revocation_statements": {
Type: framework.TypeStringSlice,
Description: `Specifies the database statements to be executed
to revoke a user. See the plugin's API page for more information
on support and formatting for this parameter.`,
},
"renew_statements": {
Type: framework.TypeStringSlice,
Description: `Specifies the database statements to be executed
to renew a user. Not every plugin type will support this
functionality. See the plugin's API page for more information on
support and formatting for this parameter. `,
},
"rollback_statements": {
Type: framework.TypeStringSlice,
Description: `Specifies the database statements to be executed
rollback a create operation in the event of an error. Not every plugin
type will support this functionality. See the plugin's API page for
more information on support and formatting for this parameter.`,
},
}

// Get the fields that are specific to the type of role, and add them to the
Expand Down Expand Up @@ -141,11 +114,36 @@ func dynamicFields() map[string]*framework.FieldSchema {
Type: framework.TypeDurationSecond,
Description: "Default ttl for role.",
},

"max_ttl": {
Type: framework.TypeDurationSecond,
Description: "Maximum time a credential is valid for",
},
"creation_statements": {
Type: framework.TypeStringSlice,
Description: `Specifies the database statements executed to
create and configure a user. See the plugin's API page for more
information on support and formatting for this parameter.`,
},
"revocation_statements": {
Type: framework.TypeStringSlice,
Description: `Specifies the database statements to be executed
to revoke a user. See the plugin's API page for more information
on support and formatting for this parameter.`,
},
"renew_statements": {
Type: framework.TypeStringSlice,
Description: `Specifies the database statements to be executed
to renew a user. Not every plugin type will support this
functionality. See the plugin's API page for more information on
support and formatting for this parameter. `,
},
"rollback_statements": {
Type: framework.TypeStringSlice,
Description: `Specifies the database statements to be executed
rollback a create operation in the event of an error. Not every plugin
type will support this functionality. See the plugin's API page for
more information on support and formatting for this parameter.`,
},
}
return fields
}
Expand All @@ -172,13 +170,6 @@ func staticFields() map[string]*framework.FieldSchema {
this functionality. See the plugin's API page for more information on
support and formatting for this parameter.`,
},
"revoke_user_on_delete": {
Type: framework.TypeBool,
Default: false,
Description: `Revoke the database user identified by the username when
this Role is deleted. Revocation will use the configured
revocation_statements if provided. Default false.`,
},
}
return fields
}
Expand Down Expand Up @@ -219,34 +210,7 @@ func (b *databaseBackend) pathStaticRoleDelete(ctx context.Context, req *logical
// Remove the item from the queue
_, _ = b.popFromRotationQueueByKey(name)

// If this role is a static account, we need to revoke the user from the
// database
role, err := b.StaticRole(ctx, req.Storage, name)
if err != nil {
return nil, err
}
if role == nil {
return nil, nil
}

// Clean up the static useraccount, if it exists
revoke := role.StaticAccount.RevokeUserOnDelete
if revoke {
db, err := b.GetConnection(ctx, req.Storage, role.DBName)
if err != nil {
return nil, err
}

db.RLock()
defer db.RUnlock()

if err := db.RevokeUser(ctx, role.Statements, role.StaticAccount.Username); err != nil {
b.CloseIfShutdown(db, err)
return nil, err
}
}

err = req.Storage.Delete(ctx, databaseStaticRolePath+name)
err := req.Storage.Delete(ctx, databaseStaticRolePath+name)
if err != nil {
return nil, err
}
Expand All @@ -262,14 +226,24 @@ func (b *databaseBackend) pathStaticRoleRead(ctx context.Context, req *logical.R
if role == nil {
return nil, nil
}
data := pathRoleReadCommon(role)

data := map[string]interface{}{
"db_name": role.DBName,
"rotation_statements": role.Statements.Rotation,
}

// guard against nil StaticAccount; shouldn't happen but we'll be safe
if role.StaticAccount != nil {
data["username"] = role.StaticAccount.Username
data["rotation_statements"] = role.Statements.Rotation
data["rotation_period"] = role.StaticAccount.RotationPeriod.Seconds()
if !role.StaticAccount.LastVaultRotation.IsZero() {
data["last_vault_rotation"] = role.StaticAccount.LastVaultRotation
}
data["revoke_user_on_delete"] = role.StaticAccount.RevokeUserOnDelete
}

if len(role.Statements.Rotation) == 0 {
data["rotation_statements"] = []string{}
}

return &logical.Response{
Expand All @@ -286,19 +260,12 @@ func (b *databaseBackend) pathRoleRead(ctx context.Context, req *logical.Request
return nil, nil
}

return &logical.Response{
Data: pathRoleReadCommon(role),
}, nil
}

func pathRoleReadCommon(role *roleEntry) map[string]interface{} {
data := map[string]interface{}{
"db_name": role.DBName,
"creation_statements": role.Statements.Creation,
"revocation_statements": role.Statements.Revocation,
"rollback_statements": role.Statements.Rollback,
"renew_statements": role.Statements.Renewal,
"rotation_statements": role.Statements.Rotation,
"default_ttl": role.DefaultTTL.Seconds(),
"max_ttl": role.MaxTTL.Seconds(),
}
Expand All @@ -314,10 +281,10 @@ func pathRoleReadCommon(role *roleEntry) map[string]interface{} {
if len(role.Statements.Renewal) == 0 {
data["renew_statements"] = []string{}
}
if len(role.Statements.Rotation) == 0 {
data["rotation_statements"] = []string{}
}
return data

return &logical.Response{
Data: data,
}, nil
}

func (b *databaseBackend) pathRoleList(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
Expand Down Expand Up @@ -355,20 +322,65 @@ func (b *databaseBackend) pathRoleCreateUpdate(ctx context.Context, req *logical
role = &roleEntry{}
}

if err := pathRoleCreateUpdateCommon(ctx, role, req.Operation, data); err != nil {
return logical.ErrorResponse(err.Error()), nil
createOperation := (req.Operation == logical.CreateOperation)

// DB Attributes
{
if dbNameRaw, ok := data.GetOk("db_name"); ok {
role.DBName = dbNameRaw.(string)
} else if createOperation {
role.DBName = data.Get("db_name").(string)
}
if role.DBName == "" {
return logical.ErrorResponse("database name is required"), nil
}
}

// Statements
{
if creationStmtsRaw, ok := data.GetOk("creation_statements"); ok {
role.Statements.Creation = creationStmtsRaw.([]string)
} else if createOperation {
role.Statements.Creation = data.Get("creation_statements").([]string)
}
catsby marked this conversation as resolved.
Show resolved Hide resolved

if revocationStmtsRaw, ok := data.GetOk("revocation_statements"); ok {
role.Statements.Revocation = revocationStmtsRaw.([]string)
} else if createOperation {
role.Statements.Revocation = data.Get("revocation_statements").([]string)
}

if rollbackStmtsRaw, ok := data.GetOk("rollback_statements"); ok {
role.Statements.Rollback = rollbackStmtsRaw.([]string)
} else if createOperation {
role.Statements.Rollback = data.Get("rollback_statements").([]string)
}

if renewStmtsRaw, ok := data.GetOk("renew_statements"); ok {
role.Statements.Renewal = renewStmtsRaw.([]string)
} else if createOperation {
role.Statements.Renewal = data.Get("renew_statements").([]string)
}

// Do not persist deprecated statements that are populated on role read
role.Statements.CreationStatements = ""
role.Statements.RevocationStatements = ""
role.Statements.RenewStatements = ""
role.Statements.RollbackStatements = ""
}

role.Statements.Revocation = strutil.RemoveEmpty(role.Statements.Revocation)
tyrannosaurus-becks marked this conversation as resolved.
Show resolved Hide resolved

// TTLs
{
if defaultTTLRaw, ok := data.GetOk("default_ttl"); ok {
role.DefaultTTL = time.Duration(defaultTTLRaw.(int)) * time.Second
} else if req.Operation == logical.CreateOperation {
} else if createOperation {
role.DefaultTTL = time.Duration(data.Get("default_ttl").(int)) * time.Second
}
if maxTTLRaw, ok := data.GetOk("max_ttl"); ok {
role.MaxTTL = time.Duration(maxTTLRaw.(int)) * time.Second
} else if req.Operation == logical.CreateOperation {
} else if createOperation {
role.MaxTTL = time.Duration(data.Get("max_ttl").(int)) * time.Second
}
}
Expand Down Expand Up @@ -414,16 +426,23 @@ func (b *databaseBackend) pathStaticRoleCreateUpdate(ctx context.Context, req *l
// createRole is a boolean to indicate if this is a new role creation. This is
// can be used later by database plugins that distinguish between creating and
// updating roles, and may use seperate statements depending on the context.
createRole := req.Operation == logical.CreateOperation
createRole := (req.Operation == logical.CreateOperation)
if role == nil {
role = &roleEntry{
StaticAccount: &staticAccount{},
}
createRole = true
}

if err := pathRoleCreateUpdateCommon(ctx, role, req.Operation, data); err != nil {
return logical.ErrorResponse(err.Error()), nil
// DB Attributes
if dbNameRaw, ok := data.GetOk("db_name"); ok {
role.DBName = dbNameRaw.(string)
} else if createRole {
role.DBName = data.Get("db_name").(string)
}

if role.DBName == "" {
return logical.ErrorResponse("database name is a required field"), nil
}

username := data.Get("username").(string)
Expand Down Expand Up @@ -458,8 +477,6 @@ func (b *databaseBackend) pathStaticRoleCreateUpdate(ctx context.Context, req *l
role.Statements.Rotation = data.Get("rotation_statements").([]string)
}

role.StaticAccount.RevokeUserOnDelete = data.Get("revoke_user_on_delete").(bool)

// lvr represents the roles' LastVaultRotation
lvr := role.StaticAccount.LastVaultRotation

Expand Down Expand Up @@ -504,57 +521,6 @@ func (b *databaseBackend) pathStaticRoleCreateUpdate(ctx context.Context, req *l
return nil, nil
}

func pathRoleCreateUpdateCommon(ctx context.Context, role *roleEntry, operation logical.Operation, data *framework.FieldData) error {
// DB Attributes
{
if dbNameRaw, ok := data.GetOk("db_name"); ok {
role.DBName = dbNameRaw.(string)
} else if operation == logical.CreateOperation {
role.DBName = data.Get("db_name").(string)
}
if role.DBName == "" {
return errors.New("empty database name attribute")
}
}

// Statements
{
if creationStmtsRaw, ok := data.GetOk("creation_statements"); ok {
role.Statements.Creation = creationStmtsRaw.([]string)
} else if operation == logical.CreateOperation {
role.Statements.Creation = data.Get("creation_statements").([]string)
}

if revocationStmtsRaw, ok := data.GetOk("revocation_statements"); ok {
role.Statements.Revocation = revocationStmtsRaw.([]string)
} else if operation == logical.CreateOperation {
role.Statements.Revocation = data.Get("revocation_statements").([]string)
}

if rollbackStmtsRaw, ok := data.GetOk("rollback_statements"); ok {
role.Statements.Rollback = rollbackStmtsRaw.([]string)
} else if operation == logical.CreateOperation {
role.Statements.Rollback = data.Get("rollback_statements").([]string)
}

if renewStmtsRaw, ok := data.GetOk("renew_statements"); ok {
role.Statements.Renewal = renewStmtsRaw.([]string)
} else if operation == logical.CreateOperation {
role.Statements.Renewal = data.Get("renew_statements").([]string)
}

// Do not persist deprecated statements that are populated on role read
role.Statements.CreationStatements = ""
role.Statements.RevocationStatements = ""
role.Statements.RenewStatements = ""
role.Statements.RollbackStatements = ""
}

role.Statements.Revocation = strutil.RemoveEmpty(role.Statements.Revocation)

return nil
}

type roleEntry struct {
DBName string `json:"db_name"`
Statements dbplugin.Statements `json:"statements"`
Expand Down