mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-11-01 11:08:10 +00:00
Combined Database backend: remove create/delete support (#6951)
* remove create/update database user for static accounts * update tests after create/delete removed * small cleanups * update postgresql setcredentials test
This commit is contained in:
@@ -2,7 +2,6 @@ package database
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -88,32 +87,6 @@ func fieldsForType(roleType string) map[string]*framework.FieldSchema {
|
|||||||
Type: framework.TypeString,
|
Type: framework.TypeString,
|
||||||
Description: "Name of the database this role acts on.",
|
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
|
// Get the fields that are specific to the type of role, and add them to the
|
||||||
@@ -141,11 +114,36 @@ func dynamicFields() map[string]*framework.FieldSchema {
|
|||||||
Type: framework.TypeDurationSecond,
|
Type: framework.TypeDurationSecond,
|
||||||
Description: "Default ttl for role.",
|
Description: "Default ttl for role.",
|
||||||
},
|
},
|
||||||
|
|
||||||
"max_ttl": {
|
"max_ttl": {
|
||||||
Type: framework.TypeDurationSecond,
|
Type: framework.TypeDurationSecond,
|
||||||
Description: "Maximum time a credential is valid for",
|
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
|
return fields
|
||||||
}
|
}
|
||||||
@@ -172,13 +170,6 @@ func staticFields() map[string]*framework.FieldSchema {
|
|||||||
this functionality. See the plugin's API page for more information on
|
this functionality. See the plugin's API page for more information on
|
||||||
support and formatting for this parameter.`,
|
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
|
return fields
|
||||||
}
|
}
|
||||||
@@ -219,34 +210,7 @@ func (b *databaseBackend) pathStaticRoleDelete(ctx context.Context, req *logical
|
|||||||
// Remove the item from the queue
|
// Remove the item from the queue
|
||||||
_, _ = b.popFromRotationQueueByKey(name)
|
_, _ = b.popFromRotationQueueByKey(name)
|
||||||
|
|
||||||
// If this role is a static account, we need to revoke the user from the
|
err := req.Storage.Delete(ctx, databaseStaticRolePath+name)
|
||||||
// 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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -262,14 +226,24 @@ func (b *databaseBackend) pathStaticRoleRead(ctx context.Context, req *logical.R
|
|||||||
if role == nil {
|
if role == nil {
|
||||||
return nil, 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 {
|
if role.StaticAccount != nil {
|
||||||
data["username"] = role.StaticAccount.Username
|
data["username"] = role.StaticAccount.Username
|
||||||
|
data["rotation_statements"] = role.Statements.Rotation
|
||||||
data["rotation_period"] = role.StaticAccount.RotationPeriod.Seconds()
|
data["rotation_period"] = role.StaticAccount.RotationPeriod.Seconds()
|
||||||
if !role.StaticAccount.LastVaultRotation.IsZero() {
|
if !role.StaticAccount.LastVaultRotation.IsZero() {
|
||||||
data["last_vault_rotation"] = role.StaticAccount.LastVaultRotation
|
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{
|
return &logical.Response{
|
||||||
@@ -286,19 +260,12 @@ func (b *databaseBackend) pathRoleRead(ctx context.Context, req *logical.Request
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return &logical.Response{
|
|
||||||
Data: pathRoleReadCommon(role),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func pathRoleReadCommon(role *roleEntry) map[string]interface{} {
|
|
||||||
data := map[string]interface{}{
|
data := map[string]interface{}{
|
||||||
"db_name": role.DBName,
|
"db_name": role.DBName,
|
||||||
"creation_statements": role.Statements.Creation,
|
"creation_statements": role.Statements.Creation,
|
||||||
"revocation_statements": role.Statements.Revocation,
|
"revocation_statements": role.Statements.Revocation,
|
||||||
"rollback_statements": role.Statements.Rollback,
|
"rollback_statements": role.Statements.Rollback,
|
||||||
"renew_statements": role.Statements.Renewal,
|
"renew_statements": role.Statements.Renewal,
|
||||||
"rotation_statements": role.Statements.Rotation,
|
|
||||||
"default_ttl": role.DefaultTTL.Seconds(),
|
"default_ttl": role.DefaultTTL.Seconds(),
|
||||||
"max_ttl": role.MaxTTL.Seconds(),
|
"max_ttl": role.MaxTTL.Seconds(),
|
||||||
}
|
}
|
||||||
@@ -314,10 +281,10 @@ func pathRoleReadCommon(role *roleEntry) map[string]interface{} {
|
|||||||
if len(role.Statements.Renewal) == 0 {
|
if len(role.Statements.Renewal) == 0 {
|
||||||
data["renew_statements"] = []string{}
|
data["renew_statements"] = []string{}
|
||||||
}
|
}
|
||||||
if len(role.Statements.Rotation) == 0 {
|
|
||||||
data["rotation_statements"] = []string{}
|
return &logical.Response{
|
||||||
}
|
Data: data,
|
||||||
return data
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *databaseBackend) pathRoleList(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
func (b *databaseBackend) pathRoleList(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||||
@@ -355,20 +322,65 @@ func (b *databaseBackend) pathRoleCreateUpdate(ctx context.Context, req *logical
|
|||||||
role = &roleEntry{}
|
role = &roleEntry{}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := pathRoleCreateUpdateCommon(ctx, role, req.Operation, data); err != nil {
|
createOperation := (req.Operation == logical.CreateOperation)
|
||||||
return logical.ErrorResponse(err.Error()), nil
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
// TTLs
|
// TTLs
|
||||||
{
|
{
|
||||||
if defaultTTLRaw, ok := data.GetOk("default_ttl"); ok {
|
if defaultTTLRaw, ok := data.GetOk("default_ttl"); ok {
|
||||||
role.DefaultTTL = time.Duration(defaultTTLRaw.(int)) * time.Second
|
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
|
role.DefaultTTL = time.Duration(data.Get("default_ttl").(int)) * time.Second
|
||||||
}
|
}
|
||||||
if maxTTLRaw, ok := data.GetOk("max_ttl"); ok {
|
if maxTTLRaw, ok := data.GetOk("max_ttl"); ok {
|
||||||
role.MaxTTL = time.Duration(maxTTLRaw.(int)) * time.Second
|
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
|
role.MaxTTL = time.Duration(data.Get("max_ttl").(int)) * time.Second
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -414,7 +426,7 @@ func (b *databaseBackend) pathStaticRoleCreateUpdate(ctx context.Context, req *l
|
|||||||
// createRole is a boolean to indicate if this is a new role creation. This is
|
// 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
|
// can be used later by database plugins that distinguish between creating and
|
||||||
// updating roles, and may use seperate statements depending on the context.
|
// updating roles, and may use seperate statements depending on the context.
|
||||||
createRole := req.Operation == logical.CreateOperation
|
createRole := (req.Operation == logical.CreateOperation)
|
||||||
if role == nil {
|
if role == nil {
|
||||||
role = &roleEntry{
|
role = &roleEntry{
|
||||||
StaticAccount: &staticAccount{},
|
StaticAccount: &staticAccount{},
|
||||||
@@ -422,8 +434,15 @@ func (b *databaseBackend) pathStaticRoleCreateUpdate(ctx context.Context, req *l
|
|||||||
createRole = true
|
createRole = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := pathRoleCreateUpdateCommon(ctx, role, req.Operation, data); err != nil {
|
// DB Attributes
|
||||||
return logical.ErrorResponse(err.Error()), nil
|
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)
|
username := data.Get("username").(string)
|
||||||
@@ -458,8 +477,6 @@ func (b *databaseBackend) pathStaticRoleCreateUpdate(ctx context.Context, req *l
|
|||||||
role.Statements.Rotation = data.Get("rotation_statements").([]string)
|
role.Statements.Rotation = data.Get("rotation_statements").([]string)
|
||||||
}
|
}
|
||||||
|
|
||||||
role.StaticAccount.RevokeUserOnDelete = data.Get("revoke_user_on_delete").(bool)
|
|
||||||
|
|
||||||
// lvr represents the roles' LastVaultRotation
|
// lvr represents the roles' LastVaultRotation
|
||||||
lvr := role.StaticAccount.LastVaultRotation
|
lvr := role.StaticAccount.LastVaultRotation
|
||||||
|
|
||||||
@@ -504,57 +521,6 @@ func (b *databaseBackend) pathStaticRoleCreateUpdate(ctx context.Context, req *l
|
|||||||
return nil, nil
|
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 {
|
type roleEntry struct {
|
||||||
DBName string `json:"db_name"`
|
DBName string `json:"db_name"`
|
||||||
Statements dbplugin.Statements `json:"statements"`
|
Statements dbplugin.Statements `json:"statements"`
|
||||||
|
|||||||
@@ -34,6 +34,9 @@ func TestBackend_StaticRole_Config(t *testing.T) {
|
|||||||
cleanup, connURL := preparePostgresTestContainer(t, config.StorageView, b)
|
cleanup, connURL := preparePostgresTestContainer(t, config.StorageView, b)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
|
// create the database user
|
||||||
|
createTestPGUser(t, connURL, dbUser, "password", testRoleStaticCreate)
|
||||||
|
|
||||||
// Configure a connection
|
// Configure a connection
|
||||||
data := map[string]interface{}{
|
data := map[string]interface{}{
|
||||||
"connection_url": connURL,
|
"connection_url": connURL,
|
||||||
@@ -62,17 +65,17 @@ func TestBackend_StaticRole_Config(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
"basic": {
|
"basic": {
|
||||||
account: map[string]interface{}{
|
account: map[string]interface{}{
|
||||||
"username": "statictest",
|
"username": dbUser,
|
||||||
"rotation_period": "5400s",
|
"rotation_period": "5400s",
|
||||||
},
|
},
|
||||||
expected: map[string]interface{}{
|
expected: map[string]interface{}{
|
||||||
"username": "statictest",
|
"username": dbUser,
|
||||||
"rotation_period": float64(5400),
|
"rotation_period": float64(5400),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"missing rotation period": {
|
"missing rotation period": {
|
||||||
account: map[string]interface{}{
|
account: map[string]interface{}{
|
||||||
"username": "statictest",
|
"username": dbUser,
|
||||||
},
|
},
|
||||||
err: errors.New("rotation_period is required to create static accounts"),
|
err: errors.New("rotation_period is required to create static accounts"),
|
||||||
},
|
},
|
||||||
@@ -81,13 +84,9 @@ func TestBackend_StaticRole_Config(t *testing.T) {
|
|||||||
for name, tc := range testCases {
|
for name, tc := range testCases {
|
||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
data := map[string]interface{}{
|
data := map[string]interface{}{
|
||||||
"name": "plugin-role-test",
|
"name": "plugin-role-test",
|
||||||
"db_name": "plugin-test",
|
"db_name": "plugin-test",
|
||||||
"creation_statements": testRoleStaticCreate,
|
"rotation_statements": testRoleStaticUpdate,
|
||||||
"rotation_statements": testRoleStaticUpdate,
|
|
||||||
"revocation_statements": defaultRevocationSQL,
|
|
||||||
"default_ttl": "5m",
|
|
||||||
"max_ttl": "10m",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for k, v := range tc.account {
|
for k, v := range tc.account {
|
||||||
@@ -205,6 +204,9 @@ func TestBackend_StaticRole_Updates(t *testing.T) {
|
|||||||
cleanup, connURL := preparePostgresTestContainer(t, config.StorageView, b)
|
cleanup, connURL := preparePostgresTestContainer(t, config.StorageView, b)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
|
// create the database user
|
||||||
|
createTestPGUser(t, connURL, dbUser, "password", testRoleStaticCreate)
|
||||||
|
|
||||||
// Configure a connection
|
// Configure a connection
|
||||||
data := map[string]interface{}{
|
data := map[string]interface{}{
|
||||||
"connection_url": connURL,
|
"connection_url": connURL,
|
||||||
@@ -226,15 +228,11 @@ func TestBackend_StaticRole_Updates(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
data = map[string]interface{}{
|
data = map[string]interface{}{
|
||||||
"name": "plugin-role-test-updates",
|
"name": "plugin-role-test-updates",
|
||||||
"db_name": "plugin-test",
|
"db_name": "plugin-test",
|
||||||
"creation_statements": testRoleStaticCreate,
|
"rotation_statements": testRoleStaticUpdate,
|
||||||
"rotation_statements": testRoleStaticUpdate,
|
"username": dbUser,
|
||||||
"revocation_statements": defaultRevocationSQL,
|
"rotation_period": "5400s",
|
||||||
"default_ttl": "5m",
|
|
||||||
"max_ttl": "10m",
|
|
||||||
"username": "statictest",
|
|
||||||
"rotation_period": "5400s",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
req = &logical.Request{
|
req = &logical.Request{
|
||||||
@@ -285,7 +283,7 @@ func TestBackend_StaticRole_Updates(t *testing.T) {
|
|||||||
updateData := map[string]interface{}{
|
updateData := map[string]interface{}{
|
||||||
"name": "plugin-role-test-updates",
|
"name": "plugin-role-test-updates",
|
||||||
"db_name": "plugin-test",
|
"db_name": "plugin-test",
|
||||||
"username": "statictest",
|
"username": dbUser,
|
||||||
"rotation_period": "6400s",
|
"rotation_period": "6400s",
|
||||||
}
|
}
|
||||||
req = &logical.Request{
|
req = &logical.Request{
|
||||||
@@ -340,7 +338,7 @@ func TestBackend_StaticRole_Updates(t *testing.T) {
|
|||||||
updateData = map[string]interface{}{
|
updateData = map[string]interface{}{
|
||||||
"name": "plugin-role-test-updates",
|
"name": "plugin-role-test-updates",
|
||||||
"db_name": "plugin-test",
|
"db_name": "plugin-test",
|
||||||
"username": "statictest",
|
"username": dbUser,
|
||||||
"rotation_statements": testRoleStaticUpdateRotation,
|
"rotation_statements": testRoleStaticUpdateRotation,
|
||||||
}
|
}
|
||||||
req = &logical.Request{
|
req = &logical.Request{
|
||||||
@@ -399,6 +397,9 @@ func TestBackend_StaticRole_Role_name_check(t *testing.T) {
|
|||||||
cleanup, connURL := preparePostgresTestContainer(t, config.StorageView, b)
|
cleanup, connURL := preparePostgresTestContainer(t, config.StorageView, b)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
|
// create the database user
|
||||||
|
createTestPGUser(t, connURL, dbUser, "password", testRoleStaticCreate)
|
||||||
|
|
||||||
// Configure a connection
|
// Configure a connection
|
||||||
data := map[string]interface{}{
|
data := map[string]interface{}{
|
||||||
"connection_url": connURL,
|
"connection_url": connURL,
|
||||||
@@ -466,13 +467,11 @@ func TestBackend_StaticRole_Role_name_check(t *testing.T) {
|
|||||||
|
|
||||||
// repeat, with a static role first
|
// repeat, with a static role first
|
||||||
data = map[string]interface{}{
|
data = map[string]interface{}{
|
||||||
"name": "plugin-role-test-2",
|
"name": "plugin-role-test-2",
|
||||||
"db_name": "plugin-test",
|
"db_name": "plugin-test",
|
||||||
"creation_statements": testRoleStaticCreate,
|
"rotation_statements": testRoleStaticUpdate,
|
||||||
"rotation_statements": testRoleStaticUpdate,
|
"username": dbUser,
|
||||||
"revocation_statements": defaultRevocationSQL,
|
"rotation_period": "1h",
|
||||||
"username": "testusername",
|
|
||||||
"rotation_period": "1h",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
req = &logical.Request{
|
req = &logical.Request{
|
||||||
@@ -514,7 +513,6 @@ const testRoleStaticCreate = `
|
|||||||
CREATE ROLE "{{name}}" WITH
|
CREATE ROLE "{{name}}" WITH
|
||||||
LOGIN
|
LOGIN
|
||||||
PASSWORD '{{password}}';
|
PASSWORD '{{password}}';
|
||||||
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO "{{name}}";
|
|
||||||
`
|
`
|
||||||
|
|
||||||
const testRoleStaticUpdate = `
|
const testRoleStaticUpdate = `
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package database
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"log"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -10,11 +11,14 @@ import (
|
|||||||
|
|
||||||
"github.com/hashicorp/vault/helper/namespace"
|
"github.com/hashicorp/vault/helper/namespace"
|
||||||
"github.com/hashicorp/vault/sdk/framework"
|
"github.com/hashicorp/vault/sdk/framework"
|
||||||
|
"github.com/hashicorp/vault/sdk/helper/dbtxn"
|
||||||
"github.com/hashicorp/vault/sdk/logical"
|
"github.com/hashicorp/vault/sdk/logical"
|
||||||
|
|
||||||
_ "github.com/lib/pq"
|
"github.com/lib/pq"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const dbUser = "vaultstatictest"
|
||||||
|
|
||||||
func TestBackend_StaticRole_Rotate_basic(t *testing.T) {
|
func TestBackend_StaticRole_Rotate_basic(t *testing.T) {
|
||||||
cluster, sys := getCluster(t)
|
cluster, sys := getCluster(t)
|
||||||
defer cluster.Cleanup()
|
defer cluster.Cleanup()
|
||||||
@@ -36,6 +40,11 @@ func TestBackend_StaticRole_Rotate_basic(t *testing.T) {
|
|||||||
cleanup, connURL := preparePostgresTestContainer(t, config.StorageView, b)
|
cleanup, connURL := preparePostgresTestContainer(t, config.StorageView, b)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
|
// create the database user
|
||||||
|
createTestPGUser(t, connURL, dbUser, "password", testRoleStaticCreate)
|
||||||
|
|
||||||
|
verifyPgConn(t, dbUser, "password", connURL)
|
||||||
|
|
||||||
// Configure a connection
|
// Configure a connection
|
||||||
data := map[string]interface{}{
|
data := map[string]interface{}{
|
||||||
"connection_url": connURL,
|
"connection_url": connURL,
|
||||||
@@ -57,13 +66,11 @@ func TestBackend_StaticRole_Rotate_basic(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
data = map[string]interface{}{
|
data = map[string]interface{}{
|
||||||
"name": "plugin-role-test",
|
"name": "plugin-role-test",
|
||||||
"db_name": "plugin-test",
|
"db_name": "plugin-test",
|
||||||
"creation_statements": testRoleStaticCreate,
|
"rotation_statements": testRoleStaticUpdate,
|
||||||
"rotation_statements": testRoleStaticUpdate,
|
"username": dbUser,
|
||||||
"revocation_statements": defaultRevocationSQL,
|
"rotation_period": "5400s",
|
||||||
"username": "statictest",
|
|
||||||
"rotation_period": "5400s",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
req = &logical.Request{
|
req = &logical.Request{
|
||||||
@@ -99,9 +106,7 @@ func TestBackend_StaticRole_Rotate_basic(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Verify username/password
|
// Verify username/password
|
||||||
if err := verifyPgConn(t, username, password, connURL); err != nil {
|
verifyPgConn(t, dbUser, password, connURL)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Re-read the creds, verifying they aren't changing on read
|
// Re-read the creds, verifying they aren't changing on read
|
||||||
data = map[string]interface{}{}
|
data = map[string]interface{}{}
|
||||||
@@ -156,9 +161,7 @@ func TestBackend_StaticRole_Rotate_basic(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Verify new username/password
|
// Verify new username/password
|
||||||
if err := verifyPgConn(t, username, newPassword, connURL); err != nil {
|
verifyPgConn(t, username, newPassword, connURL)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sanity check to make sure we don't allow an attempt of rotating credentials
|
// Sanity check to make sure we don't allow an attempt of rotating credentials
|
||||||
@@ -185,6 +188,9 @@ func TestBackend_StaticRole_Rotate_NonStaticError(t *testing.T) {
|
|||||||
cleanup, connURL := preparePostgresTestContainer(t, config.StorageView, b)
|
cleanup, connURL := preparePostgresTestContainer(t, config.StorageView, b)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
|
// create the database user
|
||||||
|
createTestPGUser(t, connURL, dbUser, "password", testRoleStaticCreate)
|
||||||
|
|
||||||
// Configure a connection
|
// Configure a connection
|
||||||
data := map[string]interface{}{
|
data := map[string]interface{}{
|
||||||
"connection_url": connURL,
|
"connection_url": connURL,
|
||||||
@@ -245,10 +251,7 @@ func TestBackend_StaticRole_Rotate_NonStaticError(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Verify username/password
|
// Verify username/password
|
||||||
if err := verifyPgConn(t, username, password, connURL); err != nil {
|
verifyPgConn(t, dbUser, "password", connURL)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trigger rotation
|
// Trigger rotation
|
||||||
data = map[string]interface{}{"name": "plugin-role-test"}
|
data = map[string]interface{}{"name": "plugin-role-test"}
|
||||||
req = &logical.Request{
|
req = &logical.Request{
|
||||||
@@ -289,6 +292,9 @@ func TestBackend_StaticRole_Revoke_user(t *testing.T) {
|
|||||||
cleanup, connURL := preparePostgresTestContainer(t, config.StorageView, b)
|
cleanup, connURL := preparePostgresTestContainer(t, config.StorageView, b)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
|
// create the database user
|
||||||
|
createTestPGUser(t, connURL, dbUser, "password", testRoleStaticCreate)
|
||||||
|
|
||||||
// Configure a connection
|
// Configure a connection
|
||||||
data := map[string]interface{}{
|
data := map[string]interface{}{
|
||||||
"connection_url": connURL,
|
"connection_url": connURL,
|
||||||
@@ -331,13 +337,11 @@ func TestBackend_StaticRole_Revoke_user(t *testing.T) {
|
|||||||
for k, tc := range testCases {
|
for k, tc := range testCases {
|
||||||
t.Run(k, func(t *testing.T) {
|
t.Run(k, func(t *testing.T) {
|
||||||
data = map[string]interface{}{
|
data = map[string]interface{}{
|
||||||
"name": "plugin-role-test",
|
"name": "plugin-role-test",
|
||||||
"db_name": "plugin-test",
|
"db_name": "plugin-test",
|
||||||
"creation_statements": testRoleStaticCreate,
|
"rotation_statements": testRoleStaticUpdate,
|
||||||
"rotation_statements": testRoleStaticUpdate,
|
"username": dbUser,
|
||||||
"revocation_statements": defaultRevocationSQL,
|
"rotation_period": "5400s",
|
||||||
"username": "statictest",
|
|
||||||
"rotation_period": "5400s",
|
|
||||||
}
|
}
|
||||||
if tc.revoke != nil {
|
if tc.revoke != nil {
|
||||||
data["revoke_user_on_delete"] = *tc.revoke
|
data["revoke_user_on_delete"] = *tc.revoke
|
||||||
@@ -376,9 +380,7 @@ func TestBackend_StaticRole_Revoke_user(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Verify username/password
|
// Verify username/password
|
||||||
if err := verifyPgConn(t, username, password, connURL); err != nil {
|
verifyPgConn(t, username, password, connURL)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// delete the role, expect the default where the user is not destroyed
|
// delete the role, expect the default where the user is not destroyed
|
||||||
// Read the creds
|
// Read the creds
|
||||||
@@ -394,25 +396,58 @@ func TestBackend_StaticRole_Revoke_user(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Verify new username/password still work
|
// Verify new username/password still work
|
||||||
if err := verifyPgConn(t, username, password, connURL); err != nil {
|
verifyPgConn(t, username, password, connURL)
|
||||||
if !tc.expectVerifyErr {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func verifyPgConn(t *testing.T, username, password, connURL string) error {
|
func createTestPGUser(t *testing.T, connURL string, username, password, query string) {
|
||||||
|
t.Helper()
|
||||||
|
log.Printf("[TRACE] Creating test user")
|
||||||
|
conn, err := pq.ParseURL(connURL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
db, err := sql.Open("postgres", conn)
|
||||||
|
defer db.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start a transaction
|
||||||
|
ctx := context.Background()
|
||||||
|
tx, err := db.BeginTx(ctx, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
_ = tx.Rollback()
|
||||||
|
}()
|
||||||
|
|
||||||
|
m := map[string]string{
|
||||||
|
"name": username,
|
||||||
|
"password": password,
|
||||||
|
}
|
||||||
|
if err := dbtxn.ExecuteTxQuery(ctx, tx, m, query); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// Commit the transaction
|
||||||
|
if err := tx.Commit(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyPgConn(t *testing.T, username, password, connURL string) {
|
||||||
|
t.Helper()
|
||||||
cURL := strings.Replace(connURL, "postgres:secret", username+":"+password, 1)
|
cURL := strings.Replace(connURL, "postgres:secret", username+":"+password, 1)
|
||||||
db, err := sql.Open("postgres", cURL)
|
db, err := sql.Open("postgres", cURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if err := db.Ping(); err != nil {
|
if err := db.Ping(); err != nil {
|
||||||
return err
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
return db.Close()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// WAL testing
|
// WAL testing
|
||||||
@@ -482,6 +517,9 @@ func TestBackend_Static_QueueWAL_discard_role_newer_rotation_date(t *testing.T)
|
|||||||
cleanup, connURL := preparePostgresTestContainer(t, config.StorageView, b)
|
cleanup, connURL := preparePostgresTestContainer(t, config.StorageView, b)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
|
// create the database user
|
||||||
|
createTestPGUser(t, connURL, dbUser, "password", testRoleStaticCreate)
|
||||||
|
|
||||||
// Configure a connection
|
// Configure a connection
|
||||||
data := map[string]interface{}{
|
data := map[string]interface{}{
|
||||||
"connection_url": connURL,
|
"connection_url": connURL,
|
||||||
@@ -508,12 +546,10 @@ func TestBackend_Static_QueueWAL_discard_role_newer_rotation_date(t *testing.T)
|
|||||||
|
|
||||||
// Create role
|
// Create role
|
||||||
data = map[string]interface{}{
|
data = map[string]interface{}{
|
||||||
"name": roleName,
|
"name": roleName,
|
||||||
"db_name": "plugin-test",
|
"db_name": "plugin-test",
|
||||||
"creation_statements": testRoleStaticCreate,
|
"rotation_statements": testRoleStaticUpdate,
|
||||||
"rotation_statements": testRoleStaticUpdate,
|
"username": dbUser,
|
||||||
"revocation_statements": defaultRevocationSQL,
|
|
||||||
"username": "statictest",
|
|
||||||
// Low value here, to make sure the backend rotates this password at least
|
// Low value here, to make sure the backend rotates this password at least
|
||||||
// once before we compare it to the WAL
|
// once before we compare it to the WAL
|
||||||
"rotation_period": "10s",
|
"rotation_period": "10s",
|
||||||
@@ -548,7 +584,7 @@ func TestBackend_Static_QueueWAL_discard_role_newer_rotation_date(t *testing.T)
|
|||||||
RoleName: roleName,
|
RoleName: roleName,
|
||||||
NewPassword: walPassword,
|
NewPassword: walPassword,
|
||||||
LastVaultRotation: oldRotationTime,
|
LastVaultRotation: oldRotationTime,
|
||||||
Username: "statictest",
|
Username: dbUser,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("error with PutWAL: %s", err)
|
t.Fatalf("error with PutWAL: %s", err)
|
||||||
@@ -664,6 +700,11 @@ func TestBackend_StaticRole_Rotations_PostgreSQL(t *testing.T) {
|
|||||||
// Configure backend, add item and confirm length
|
// Configure backend, add item and confirm length
|
||||||
cleanup, connURL := preparePostgresTestContainer(t, config.StorageView, b)
|
cleanup, connURL := preparePostgresTestContainer(t, config.StorageView, b)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
testCases := []string{"65", "130", "5400"}
|
||||||
|
// Create database users ahead
|
||||||
|
for _, tc := range testCases {
|
||||||
|
createTestPGUser(t, connURL, dbUser+tc, "password", testRoleStaticCreate)
|
||||||
|
}
|
||||||
|
|
||||||
// Configure a connection
|
// Configure a connection
|
||||||
data := map[string]interface{}{
|
data := map[string]interface{}{
|
||||||
@@ -685,17 +726,14 @@ func TestBackend_StaticRole_Rotations_PostgreSQL(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create three static roles with different rotation periods
|
// Create three static roles with different rotation periods
|
||||||
testCases := []string{"65", "130", "5400"}
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
roleName := "plugin-static-role-" + tc
|
roleName := "plugin-static-role-" + tc
|
||||||
data = map[string]interface{}{
|
data = map[string]interface{}{
|
||||||
"name": roleName,
|
"name": roleName,
|
||||||
"db_name": "plugin-test",
|
"db_name": "plugin-test",
|
||||||
"creation_statements": testRoleStaticCreate,
|
"rotation_statements": testRoleStaticUpdate,
|
||||||
"rotation_statements": testRoleStaticUpdate,
|
"username": dbUser + tc,
|
||||||
"revocation_statements": defaultRevocationSQL,
|
"rotation_period": tc,
|
||||||
"username": "statictest" + tc,
|
|
||||||
"rotation_period": tc,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
req = &logical.Request{
|
req = &logical.Request{
|
||||||
|
|||||||
@@ -99,8 +99,8 @@ func (p *PostgreSQL) getConnection(ctx context.Context) (*sql.DB, error) {
|
|||||||
// passwords in the database in the event an updated database fails to save in
|
// passwords in the database in the event an updated database fails to save in
|
||||||
// Vault's storage.
|
// Vault's storage.
|
||||||
func (p *PostgreSQL) SetCredentials(ctx context.Context, statements dbplugin.Statements, staticUser dbplugin.StaticUserConfig) (username, password string, err error) {
|
func (p *PostgreSQL) SetCredentials(ctx context.Context, statements dbplugin.Statements, staticUser dbplugin.StaticUserConfig) (username, password string, err error) {
|
||||||
if len(statements.Creation) == 0 {
|
if len(statements.Rotation) == 0 {
|
||||||
return "", "", errors.New("empty creation statements")
|
return "", "", errors.New("empty rotation statements")
|
||||||
}
|
}
|
||||||
|
|
||||||
username = staticUser.Username
|
username = staticUser.Username
|
||||||
@@ -126,16 +126,9 @@ func (p *PostgreSQL) SetCredentials(ctx context.Context, statements dbplugin.Sta
|
|||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default to using Creation statements, which are required by the Vault
|
// Vault requires the database user already exist, and that the credentials
|
||||||
// backend. If the user exists, use the rotation statements, using the default
|
// used to execute the rotation statements has sufficient privileges.
|
||||||
// ones if there are none provided
|
stmts := statements.Rotation
|
||||||
stmts := statements.Creation
|
|
||||||
if exists {
|
|
||||||
stmts = statements.Rotation
|
|
||||||
if len(stmts) == 0 {
|
|
||||||
stmts = []string{defaultPostgresRotateCredentialsSQL}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start a transaction
|
// Start a transaction
|
||||||
tx, err := db.BeginTx(ctx, nil)
|
tx, err := db.BeginTx(ctx, nil)
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import (
|
|||||||
|
|
||||||
"github.com/hashicorp/vault/helper/testhelpers/docker"
|
"github.com/hashicorp/vault/helper/testhelpers/docker"
|
||||||
"github.com/hashicorp/vault/sdk/database/dbplugin"
|
"github.com/hashicorp/vault/sdk/database/dbplugin"
|
||||||
|
"github.com/hashicorp/vault/sdk/helper/dbtxn"
|
||||||
|
"github.com/lib/pq"
|
||||||
"github.com/ory/dockertest"
|
"github.com/ory/dockertest"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -321,6 +323,10 @@ func TestPostgresSQL_SetCredentials(t *testing.T) {
|
|||||||
cleanup, connURL := preparePostgresTestContainer(t)
|
cleanup, connURL := preparePostgresTestContainer(t)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
|
// create the database user
|
||||||
|
dbUser := "vaultstatictest"
|
||||||
|
createTestPGUser(t, connURL, dbUser, "password", testRoleStaticCreate)
|
||||||
|
|
||||||
connectionDetails := map[string]interface{}{
|
connectionDetails := map[string]interface{}{
|
||||||
"connection_url": connURL,
|
"connection_url": connURL,
|
||||||
}
|
}
|
||||||
@@ -337,18 +343,18 @@ func TestPostgresSQL_SetCredentials(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
usernameConfig := dbplugin.StaticUserConfig{
|
usernameConfig := dbplugin.StaticUserConfig{
|
||||||
Username: "test",
|
Username: dbUser,
|
||||||
Password: password,
|
Password: password,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test with no configured Creation Statement
|
// Test with no configured Rotation Statement
|
||||||
username, password, err := db.SetCredentials(context.Background(), dbplugin.Statements{}, usernameConfig)
|
username, password, err := db.SetCredentials(context.Background(), dbplugin.Statements{}, usernameConfig)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
statements := dbplugin.Statements{
|
statements := dbplugin.Statements{
|
||||||
Creation: []string{testPostgresStaticRole},
|
Rotation: []string{testPostgresStaticRoleRotate},
|
||||||
}
|
}
|
||||||
// User should not exist, make sure we can create
|
// User should not exist, make sure we can create
|
||||||
username, password, err = db.SetCredentials(context.Background(), statements, usernameConfig)
|
username, password, err = db.SetCredentials(context.Background(), statements, usernameConfig)
|
||||||
@@ -360,8 +366,7 @@ func TestPostgresSQL_SetCredentials(t *testing.T) {
|
|||||||
t.Fatalf("Could not connect with new credentials: %s", err)
|
t.Fatalf("Could not connect with new credentials: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// call SetCredentials again, the user will already exist, password will
|
// call SetCredentials again, password will change
|
||||||
// change. Without rotation statements, this should use the defaults
|
|
||||||
newPassword, _ := db.GenerateCredentials(context.Background())
|
newPassword, _ := db.GenerateCredentials(context.Background())
|
||||||
usernameConfig.Password = newPassword
|
usernameConfig.Password = newPassword
|
||||||
username, password, err = db.SetCredentials(context.Background(), statements, usernameConfig)
|
username, password, err = db.SetCredentials(context.Background(), statements, usernameConfig)
|
||||||
@@ -376,23 +381,6 @@ func TestPostgresSQL_SetCredentials(t *testing.T) {
|
|||||||
if err := testCredsExist(t, connURL, username, password); err != nil {
|
if err := testCredsExist(t, connURL, username, password); err != nil {
|
||||||
t.Fatalf("Could not connect with new credentials: %s", err)
|
t.Fatalf("Could not connect with new credentials: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// generate a new password and supply owr own rotation statements
|
|
||||||
newPassword2, _ := db.GenerateCredentials(context.Background())
|
|
||||||
usernameConfig.Password = newPassword2
|
|
||||||
statements.Rotation = []string{testPostgresStaticRoleRotate, testPostgresStaticRoleGrant}
|
|
||||||
username, password, err = db.SetCredentials(context.Background(), statements, usernameConfig)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if password != newPassword2 {
|
|
||||||
t.Fatal("passwords should have changed")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := testCredsExist(t, connURL, username, password); err != nil {
|
|
||||||
t.Fatalf("Could not connect with new credentials: %s", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func testCredsExist(t testing.TB, connURL, username, password string) error {
|
func testCredsExist(t testing.TB, connURL, username, password string) error {
|
||||||
@@ -484,6 +472,12 @@ CREATE ROLE "{{name}}" WITH
|
|||||||
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO "{{name}}";
|
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO "{{name}}";
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const testRoleStaticCreate = `
|
||||||
|
CREATE ROLE "{{name}}" WITH
|
||||||
|
LOGIN
|
||||||
|
PASSWORD '{{password}}';
|
||||||
|
`
|
||||||
|
|
||||||
const testPostgresStaticRoleRotate = `
|
const testPostgresStaticRoleRotate = `
|
||||||
ALTER ROLE "{{name}}" WITH PASSWORD '{{password}}';
|
ALTER ROLE "{{name}}" WITH PASSWORD '{{password}}';
|
||||||
`
|
`
|
||||||
@@ -491,3 +485,42 @@ ALTER ROLE "{{name}}" WITH PASSWORD '{{password}}';
|
|||||||
const testPostgresStaticRoleGrant = `
|
const testPostgresStaticRoleGrant = `
|
||||||
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO "{{name}}";
|
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO "{{name}}";
|
||||||
`
|
`
|
||||||
|
|
||||||
|
// This is a copy of a test helper method also found in
|
||||||
|
// builtin/logical/database/rotation_test.go , and should be moved into a shared
|
||||||
|
// helper file in the future.
|
||||||
|
func createTestPGUser(t *testing.T, connURL string, username, password, query string) {
|
||||||
|
t.Helper()
|
||||||
|
conn, err := pq.ParseURL(connURL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
db, err := sql.Open("postgres", conn)
|
||||||
|
defer db.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start a transaction
|
||||||
|
ctx := context.Background()
|
||||||
|
tx, err := db.BeginTx(ctx, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
_ = tx.Rollback()
|
||||||
|
}()
|
||||||
|
|
||||||
|
m := map[string]string{
|
||||||
|
"name": username,
|
||||||
|
"password": password,
|
||||||
|
}
|
||||||
|
if err := dbtxn.ExecuteTxQuery(ctx, tx, m, query); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// Commit the transaction
|
||||||
|
if err := tx.Commit(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -425,35 +425,11 @@ Static Roles, please see the database-specific documentation.
|
|||||||
- `db_name` `(string: <required>)` - The name of the database connection to use
|
- `db_name` `(string: <required>)` - The name of the database connection to use
|
||||||
for this role.
|
for this role.
|
||||||
|
|
||||||
- `creation_statements` `(list: <required>)` – 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` `(list: [])` – 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.
|
|
||||||
|
|
||||||
- `rollback_statements` `(list: [])` – 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.
|
|
||||||
|
|
||||||
- `renew_statements` `(list: [])` – 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.
|
|
||||||
|
|
||||||
- `rotation_statements` `(list: [])` – Specifies the database statements to be
|
- `rotation_statements` `(list: [])` – Specifies the database statements to be
|
||||||
executed to rotate the password for the configured database user. Not every
|
executed to rotate the password for the configured database user. Not every
|
||||||
plugin type will support this functionality. See the plugin's API page for
|
plugin type will support this functionality. See the plugin's API page for
|
||||||
more information on support and formatting for this parameter.
|
more information on support and formatting for this parameter.
|
||||||
|
|
||||||
- `revoke_user_on_delete` `(boolean: false)` – Specifies if Vault should attempt
|
|
||||||
to revoke the database user associated with this static role, indicated by the
|
|
||||||
`username`. If `true`, when Vault deletes this Role it will attempt to revoke
|
|
||||||
the database user using the configured `revocation_statements` if they exist.
|
|
||||||
Default `false`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Sample Payload
|
### Sample Payload
|
||||||
@@ -462,7 +438,6 @@ Static Roles, please see the database-specific documentation.
|
|||||||
{
|
{
|
||||||
"db_name": "mysql",
|
"db_name": "mysql",
|
||||||
"username": "static-database-user",
|
"username": "static-database-user",
|
||||||
"creation_statements": ["CREATE USER '{{name}}'@'%' IDENTIFIED BY '{{password}}'", "GRANT SELECT ON *.* TO '{{name}}'@'%'"],
|
|
||||||
"rotation_statements": ["ALTER USER "{{name}}" WITH PASSWORD '{{password}}';"],
|
"rotation_statements": ["ALTER USER "{{name}}" WITH PASSWORD '{{password}}';"],
|
||||||
"rotation_period": "1h"
|
"rotation_period": "1h"
|
||||||
}
|
}
|
||||||
@@ -506,13 +481,8 @@ $ curl \
|
|||||||
"data": {
|
"data": {
|
||||||
"db_name": "mysql",
|
"db_name": "mysql",
|
||||||
"username":"static-user",
|
"username":"static-user",
|
||||||
"creation_statements": ["CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}';"], "GRANT SELECT ON ALL TABLES IN SCHEMA public TO \"{{name}}\";"],
|
|
||||||
"rotation_statements": ["ALTER USER "{{name}}" WITH PASSWORD '{{password}}';"],
|
"rotation_statements": ["ALTER USER "{{name}}" WITH PASSWORD '{{password}}';"],
|
||||||
"rotation_period":"1h",
|
"rotation_period":"1h",
|
||||||
"renew_statements": [],
|
|
||||||
"revocation_statements": [],
|
|
||||||
"rollback_statements": []
|
|
||||||
"revoke_user_on_delete": false,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
Reference in New Issue
Block a user