open-vault/builtin/logical/database/path_roles.go
Chris Hoffman e4832fdbcf
Database Root Credential Rotation (#3976)
* redoing connection handling

* a little more cleanup

* empty implementation of rotation

* updating rotate signature

* signature update

* updating interfaces again :(

* changing back to interface

* adding templated url support and rotation for postgres

* adding correct username

* return updates

* updating statements to be a list

* adding error sanitizing middleware

* fixing log sanitizier

* adding postgres rotate test

* removing conf from rotate

* adding rotate command

* adding mysql rotate

* finishing up the endpoint in the db backend for rotate

* no more structs, just store raw config

* fixing tests

* adding db instance lock

* adding support for statement list in cassandra

* wip redoing interface to support BC

* adding falllback for Initialize implementation

* adding backwards compat for statements

* fix tests

* fix more tests

* fixing up tests, switching to new fields in statements

* fixing more tests

* adding mssql and mysql

* wrapping all the things in middleware, implementing templating for mongodb

* wrapping all db servers with error santizer

* fixing test

* store the name with the db instance

* adding rotate to cassandra

* adding compatibility translation to both server and plugin

* reordering a few things

* store the name with the db instance

* reordering

* adding a few more tests

* switch secret values from slice to map

* addressing some feedback

* reinstate execute plugin after resetting connection

* set database connection to closed

* switching secret values func to map[string]interface for potential future uses

* addressing feedback
2018-03-21 15:05:56 -04:00

234 lines
7.2 KiB
Go

package database
import (
"context"
"time"
"github.com/hashicorp/vault/builtin/logical/database/dbplugin"
"github.com/hashicorp/vault/logical"
"github.com/hashicorp/vault/logical/framework"
)
func pathListRoles(b *databaseBackend) *framework.Path {
return &framework.Path{
Pattern: "roles/?$",
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.ListOperation: b.pathRoleList(),
},
HelpSynopsis: pathRoleHelpSyn,
HelpDescription: pathRoleHelpDesc,
}
}
func pathRoles(b *databaseBackend) *framework.Path {
return &framework.Path{
Pattern: "roles/" + framework.GenericNameRegex("name"),
Fields: map[string]*framework.FieldSchema{
"name": {
Type: framework.TypeString,
Description: "Name of the role.",
},
"db_name": {
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.`,
},
"default_ttl": {
Type: framework.TypeDurationSecond,
Description: "Default ttl for role.",
},
"max_ttl": {
Type: framework.TypeDurationSecond,
Description: "Maximum time a credential is valid for",
},
},
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.ReadOperation: b.pathRoleRead(),
logical.UpdateOperation: b.pathRoleCreate(),
logical.DeleteOperation: b.pathRoleDelete(),
},
HelpSynopsis: pathRoleHelpSyn,
HelpDescription: pathRoleHelpDesc,
}
}
func (b *databaseBackend) pathRoleDelete() framework.OperationFunc {
return func(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
err := req.Storage.Delete(ctx, "role/"+data.Get("name").(string))
if err != nil {
return nil, err
}
return nil, nil
}
}
func (b *databaseBackend) pathRoleRead() framework.OperationFunc {
return func(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
role, err := b.Role(ctx, req.Storage, data.Get("name").(string))
if err != nil {
return nil, err
}
if role == nil {
return nil, nil
}
return &logical.Response{
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,
"default_ttl": role.DefaultTTL.Seconds(),
"max_ttl": role.MaxTTL.Seconds(),
},
}, nil
}
}
func (b *databaseBackend) pathRoleList() framework.OperationFunc {
return func(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
entries, err := req.Storage.List(ctx, "role/")
if err != nil {
return nil, err
}
return logical.ListResponse(entries), nil
}
}
func (b *databaseBackend) pathRoleCreate() framework.OperationFunc {
return func(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
name := data.Get("name").(string)
if name == "" {
return logical.ErrorResponse("empty role name attribute given"), nil
}
dbName := data.Get("db_name").(string)
if dbName == "" {
return logical.ErrorResponse("empty database name attribute given"), nil
}
// Get statements
creationStmts := data.Get("creation_statements").([]string)
revocationStmts := data.Get("revocation_statements").([]string)
rollbackStmts := data.Get("rollback_statements").([]string)
renewStmts := data.Get("renew_statements").([]string)
// Get TTLs
defaultTTLRaw := data.Get("default_ttl").(int)
maxTTLRaw := data.Get("max_ttl").(int)
defaultTTL := time.Duration(defaultTTLRaw) * time.Second
maxTTL := time.Duration(maxTTLRaw) * time.Second
statements := dbplugin.Statements{
Creation: creationStmts,
Revocation: revocationStmts,
Rollback: rollbackStmts,
Renewal: renewStmts,
}
// Store it
entry, err := logical.StorageEntryJSON("role/"+name, &roleEntry{
DBName: dbName,
Statements: statements,
DefaultTTL: defaultTTL,
MaxTTL: maxTTL,
})
if err != nil {
return nil, err
}
if err := req.Storage.Put(ctx, entry); err != nil {
return nil, err
}
return nil, nil
}
}
type roleEntry struct {
DBName string `json:"db_name"`
Statements dbplugin.Statements `json:"statements"`
DefaultTTL time.Duration `json:"default_ttl"`
MaxTTL time.Duration `json:"max_ttl"`
}
const pathRoleHelpSyn = `
Manage the roles that can be created with this backend.
`
const pathRoleHelpDesc = `
This path lets you manage the roles that can be created with this backend.
The "db_name" parameter is required and configures the name of the database
connection to use.
The "creation_statements" parameter customizes the string used to create the
credentials. This can be a sequence of SQL queries, or other statement formats
for a particular database type. Some substitution will be done to the statement
strings for certain keys. The names of the variables must be surrounded by "{{"
and "}}" to be replaced.
* "name" - The random username generated for the DB user.
* "password" - The random password generated for the DB user.
* "expiration" - The timestamp when this user will expire.
Example of a decent creation_statements for a postgresql database plugin:
CREATE ROLE "{{name}}" WITH
LOGIN
PASSWORD '{{password}}'
VALID UNTIL '{{expiration}}';
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO "{{name}}";
The "revocation_statements" parameter customizes the statement string used to
revoke a user. Example of a decent revocation_statements for a postgresql
database plugin:
REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA public FROM {{name}};
REVOKE ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public FROM {{name}};
REVOKE USAGE ON SCHEMA public FROM {{name}};
DROP ROLE IF EXISTS {{name}};
The "renew_statements" parameter customizes the statement string used to renew a
user.
The "rollback_statements' parameter customizes the statement string used to
rollback a change if needed.
`