mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-11-01 19:17:58 +00:00
Use the role name in the db username (#2812)
This commit is contained in:
committed by
Jeff Mitchell
parent
2631bde3ef
commit
abc900157b
@@ -78,10 +78,10 @@ func (dr *databasePluginRPCClient) Type() (string, error) {
|
||||
return fmt.Sprintf("plugin-%s", dbType), err
|
||||
}
|
||||
|
||||
func (dr *databasePluginRPCClient) CreateUser(statements Statements, usernamePrefix string, expiration time.Time) (username string, password string, err error) {
|
||||
func (dr *databasePluginRPCClient) CreateUser(statements Statements, usernameConfig UsernameConfig, expiration time.Time) (username string, password string, err error) {
|
||||
req := CreateUserRequest{
|
||||
Statements: statements,
|
||||
UsernamePrefix: usernamePrefix,
|
||||
UsernameConfig: usernameConfig,
|
||||
Expiration: expiration,
|
||||
}
|
||||
|
||||
|
||||
@@ -22,13 +22,13 @@ func (mw *databaseTracingMiddleware) Type() (string, error) {
|
||||
return mw.next.Type()
|
||||
}
|
||||
|
||||
func (mw *databaseTracingMiddleware) CreateUser(statements Statements, usernamePrefix string, expiration time.Time) (username string, password string, err error) {
|
||||
func (mw *databaseTracingMiddleware) CreateUser(statements Statements, usernameConfig UsernameConfig, expiration time.Time) (username string, password string, err error) {
|
||||
defer func(then time.Time) {
|
||||
mw.logger.Trace("database", "operation", "CreateUser", "status", "finished", "type", mw.typeStr, "err", err, "took", time.Since(then))
|
||||
}(time.Now())
|
||||
|
||||
mw.logger.Trace("database", "operation", "CreateUser", "status", "started", "type", mw.typeStr)
|
||||
return mw.next.CreateUser(statements, usernamePrefix, expiration)
|
||||
return mw.next.CreateUser(statements, usernameConfig, expiration)
|
||||
}
|
||||
|
||||
func (mw *databaseTracingMiddleware) RenewUser(statements Statements, username string, expiration time.Time) (err error) {
|
||||
@@ -81,7 +81,7 @@ func (mw *databaseMetricsMiddleware) Type() (string, error) {
|
||||
return mw.next.Type()
|
||||
}
|
||||
|
||||
func (mw *databaseMetricsMiddleware) CreateUser(statements Statements, usernamePrefix string, expiration time.Time) (username string, password string, err error) {
|
||||
func (mw *databaseMetricsMiddleware) CreateUser(statements Statements, usernameConfig UsernameConfig, expiration time.Time) (username string, password string, err error) {
|
||||
defer func(now time.Time) {
|
||||
metrics.MeasureSince([]string{"database", "CreateUser"}, now)
|
||||
metrics.MeasureSince([]string{"database", mw.typeStr, "CreateUser"}, now)
|
||||
@@ -94,7 +94,7 @@ func (mw *databaseMetricsMiddleware) CreateUser(statements Statements, usernameP
|
||||
|
||||
metrics.IncrCounter([]string{"database", "CreateUser"}, 1)
|
||||
metrics.IncrCounter([]string{"database", mw.typeStr, "CreateUser"}, 1)
|
||||
return mw.next.CreateUser(statements, usernamePrefix, expiration)
|
||||
return mw.next.CreateUser(statements, usernameConfig, expiration)
|
||||
}
|
||||
|
||||
func (mw *databaseMetricsMiddleware) RenewUser(statements Statements, username string, expiration time.Time) (err error) {
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
// Database is the interface that all database objects must implement.
|
||||
type Database interface {
|
||||
Type() (string, error)
|
||||
CreateUser(statements Statements, usernamePrefix string, expiration time.Time) (username string, password string, err error)
|
||||
CreateUser(statements Statements, usernameConfig UsernameConfig, expiration time.Time) (username string, password string, err error)
|
||||
RenewUser(statements Statements, username string, expiration time.Time) error
|
||||
RevokeUser(statements Statements, username string) error
|
||||
|
||||
@@ -29,6 +29,13 @@ type Statements struct {
|
||||
RenewStatements string `json:"renew_statements" mapstructure:"renew_statements" structs:"renew_statements"`
|
||||
}
|
||||
|
||||
// UsernameConfig is used to configure prefixes for the username to be
|
||||
// generated.
|
||||
type UsernameConfig struct {
|
||||
DisplayName string
|
||||
RoleName string
|
||||
}
|
||||
|
||||
// PluginFactory is used to build plugin database types. It wraps the database
|
||||
// object in a logging and metrics middleware.
|
||||
func PluginFactory(pluginName string, sys pluginutil.LookRunnerUtil, logger log.Logger) (Database, error) {
|
||||
@@ -89,7 +96,7 @@ func PluginFactory(pluginName string, sys pluginutil.LookRunnerUtil, logger log.
|
||||
// This prevents users from executing bad plugins or executing a plugin
|
||||
// directory. It is a UX feature, not a security feature.
|
||||
var handshakeConfig = plugin.HandshakeConfig{
|
||||
ProtocolVersion: 1,
|
||||
ProtocolVersion: 2,
|
||||
MagicCookieKey: "VAULT_DATABASE_PLUGIN",
|
||||
MagicCookieValue: "926a0820-aea2-be28-51d6-83cdf00e8edb",
|
||||
}
|
||||
@@ -117,7 +124,7 @@ type InitializeRequest struct {
|
||||
|
||||
type CreateUserRequest struct {
|
||||
Statements Statements
|
||||
UsernamePrefix string
|
||||
UsernameConfig UsernameConfig
|
||||
Expiration time.Time
|
||||
}
|
||||
|
||||
|
||||
@@ -21,19 +21,19 @@ type mockPlugin struct {
|
||||
}
|
||||
|
||||
func (m *mockPlugin) Type() (string, error) { return "mock", nil }
|
||||
func (m *mockPlugin) CreateUser(statements dbplugin.Statements, usernamePrefix string, expiration time.Time) (username string, password string, err error) {
|
||||
func (m *mockPlugin) CreateUser(statements dbplugin.Statements, usernameConf dbplugin.UsernameConfig, expiration time.Time) (username string, password string, err error) {
|
||||
err = errors.New("err")
|
||||
if usernamePrefix == "" || expiration.IsZero() {
|
||||
if usernameConf.DisplayName == "" || expiration.IsZero() {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
if _, ok := m.users[usernamePrefix]; ok {
|
||||
if _, ok := m.users[usernameConf.DisplayName]; ok {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
m.users[usernamePrefix] = []string{password}
|
||||
m.users[usernameConf.DisplayName] = []string{password}
|
||||
|
||||
return usernamePrefix, "test", nil
|
||||
return usernameConf.DisplayName, "test", nil
|
||||
}
|
||||
func (m *mockPlugin) RenewUser(statements dbplugin.Statements, username string, expiration time.Time) error {
|
||||
err := errors.New("err")
|
||||
@@ -162,7 +162,12 @@ func TestPlugin_CreateUser(t *testing.T) {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
us, pw, err := db.CreateUser(dbplugin.Statements{}, "test", time.Now().Add(time.Minute))
|
||||
usernameConf := dbplugin.UsernameConfig{
|
||||
DisplayName: "test",
|
||||
RoleName: "test",
|
||||
}
|
||||
|
||||
us, pw, err := db.CreateUser(dbplugin.Statements{}, usernameConf, time.Now().Add(time.Minute))
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
@@ -172,7 +177,7 @@ func TestPlugin_CreateUser(t *testing.T) {
|
||||
|
||||
// try and save the same user again to verify it saved the first time, this
|
||||
// should return an error
|
||||
_, _, err = db.CreateUser(dbplugin.Statements{}, "test", time.Now().Add(time.Minute))
|
||||
_, _, err = db.CreateUser(dbplugin.Statements{}, usernameConf, time.Now().Add(time.Minute))
|
||||
if err == nil {
|
||||
t.Fatal("expected an error, user wasn't created correctly")
|
||||
}
|
||||
@@ -198,7 +203,12 @@ func TestPlugin_RenewUser(t *testing.T) {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
us, _, err := db.CreateUser(dbplugin.Statements{}, "test", time.Now().Add(time.Minute))
|
||||
usernameConf := dbplugin.UsernameConfig{
|
||||
DisplayName: "test",
|
||||
RoleName: "test",
|
||||
}
|
||||
|
||||
us, _, err := db.CreateUser(dbplugin.Statements{}, usernameConf, time.Now().Add(time.Minute))
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
@@ -229,7 +239,12 @@ func TestPlugin_RevokeUser(t *testing.T) {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
us, _, err := db.CreateUser(dbplugin.Statements{}, "test", time.Now().Add(time.Minute))
|
||||
usernameConf := dbplugin.UsernameConfig{
|
||||
DisplayName: "test",
|
||||
RoleName: "test",
|
||||
}
|
||||
|
||||
us, _, err := db.CreateUser(dbplugin.Statements{}, usernameConf, time.Now().Add(time.Minute))
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
@@ -241,7 +256,7 @@ func TestPlugin_RevokeUser(t *testing.T) {
|
||||
}
|
||||
|
||||
// Try adding the same username back so we can verify it was removed
|
||||
_, _, err = db.CreateUser(dbplugin.Statements{}, "test", time.Now().Add(time.Minute))
|
||||
_, _, err = db.CreateUser(dbplugin.Statements{}, usernameConf, time.Now().Add(time.Minute))
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ func (ds *databasePluginRPCServer) Type(_ struct{}, resp *string) error {
|
||||
|
||||
func (ds *databasePluginRPCServer) CreateUser(args *CreateUserRequest, resp *CreateUserResponse) error {
|
||||
var err error
|
||||
resp.Username, resp.Password, err = ds.impl.CreateUser(args.Statements, args.UsernamePrefix, args.Expiration)
|
||||
resp.Username, resp.Password, err = ds.impl.CreateUser(args.Statements, args.UsernameConfig, args.Expiration)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/vault/builtin/logical/database/dbplugin"
|
||||
"github.com/hashicorp/vault/helper/strutil"
|
||||
"github.com/hashicorp/vault/logical"
|
||||
"github.com/hashicorp/vault/logical/framework"
|
||||
@@ -74,8 +75,13 @@ func (b *databaseBackend) pathCredsCreateRead() framework.OperationFunc {
|
||||
|
||||
expiration := time.Now().Add(role.DefaultTTL)
|
||||
|
||||
usernameConfig := dbplugin.UsernameConfig{
|
||||
DisplayName: req.DisplayName,
|
||||
RoleName: name,
|
||||
}
|
||||
|
||||
// Create the user
|
||||
username, password, err := db.CreateUser(role.Statements, req.DisplayName, expiration)
|
||||
username, password, err := db.CreateUser(role.Statements, usernameConfig, expiration)
|
||||
// Unlock
|
||||
unlockFunc()
|
||||
if err != nil {
|
||||
|
||||
@@ -13,10 +13,10 @@ type BuiltinFactory func() (interface{}, error)
|
||||
var plugins map[string]BuiltinFactory = map[string]BuiltinFactory{
|
||||
// These four plugins all use the same mysql implementation but with
|
||||
// different username settings passed by the constructor.
|
||||
"mysql-database-plugin": mysql.New(mysql.DisplayNameLen, mysql.UsernameLen),
|
||||
"mysql-aurora-database-plugin": mysql.New(mysql.LegacyDisplayNameLen, mysql.LegacyUsernameLen),
|
||||
"mysql-rds-database-plugin": mysql.New(mysql.LegacyDisplayNameLen, mysql.LegacyUsernameLen),
|
||||
"mysql-legacy-database-plugin": mysql.New(mysql.LegacyDisplayNameLen, mysql.LegacyUsernameLen),
|
||||
"mysql-database-plugin": mysql.New(mysql.MetadataLen, mysql.UsernameLen),
|
||||
"mysql-aurora-database-plugin": mysql.New(mysql.LegacyMetadataLen, mysql.LegacyUsernameLen),
|
||||
"mysql-rds-database-plugin": mysql.New(mysql.LegacyMetadataLen, mysql.LegacyUsernameLen),
|
||||
"mysql-legacy-database-plugin": mysql.New(mysql.LegacyMetadataLen, mysql.LegacyUsernameLen),
|
||||
|
||||
"postgresql-database-plugin": postgresql.New,
|
||||
"mssql-database-plugin": mssql.New,
|
||||
|
||||
@@ -32,7 +32,12 @@ func New() (interface{}, error) {
|
||||
connProducer := &cassandraConnectionProducer{}
|
||||
connProducer.Type = cassandraTypeName
|
||||
|
||||
credsProducer := &cassandraCredentialsProducer{}
|
||||
credsProducer := &credsutil.SQLCredentialsProducer{
|
||||
DisplayNameLen: 15,
|
||||
RoleNameLen: 15,
|
||||
UsernameLen: 100,
|
||||
Separator: "_",
|
||||
}
|
||||
|
||||
dbType := &Cassandra{
|
||||
ConnectionProducer: connProducer,
|
||||
@@ -70,7 +75,7 @@ func (c *Cassandra) getConnection() (*gocql.Session, error) {
|
||||
|
||||
// CreateUser generates the username/password on the underlying Cassandra secret backend as instructed by
|
||||
// the CreationStatement provided.
|
||||
func (c *Cassandra) CreateUser(statements dbplugin.Statements, usernamePrefix string, expiration time.Time) (username string, password string, err error) {
|
||||
func (c *Cassandra) CreateUser(statements dbplugin.Statements, usernameConfig dbplugin.UsernameConfig, expiration time.Time) (username string, password string, err error) {
|
||||
// Grab the lock
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
@@ -90,10 +95,12 @@ func (c *Cassandra) CreateUser(statements dbplugin.Statements, usernamePrefix st
|
||||
rollbackCQL = defaultUserDeletionCQL
|
||||
}
|
||||
|
||||
username, err = c.GenerateUsername(usernamePrefix)
|
||||
username, err = c.GenerateUsername(usernameConfig)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
// Cassandra doesn't like the uppercase usernames
|
||||
username = strings.ToLower(username)
|
||||
|
||||
password, err = c.GeneratePassword()
|
||||
if err != nil {
|
||||
|
||||
@@ -126,7 +126,12 @@ func TestCassandra_CreateUser(t *testing.T) {
|
||||
CreationStatements: testCassandraRole,
|
||||
}
|
||||
|
||||
username, password, err := db.CreateUser(statements, "test", time.Now().Add(time.Minute))
|
||||
usernameConfig := dbplugin.UsernameConfig{
|
||||
DisplayName: "test",
|
||||
RoleName: "test",
|
||||
}
|
||||
|
||||
username, password, err := db.CreateUser(statements, usernameConfig, time.Now().Add(time.Minute))
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
@@ -161,7 +166,12 @@ func TestMyCassandra_RenewUser(t *testing.T) {
|
||||
CreationStatements: testCassandraRole,
|
||||
}
|
||||
|
||||
username, password, err := db.CreateUser(statements, "test", time.Now().Add(time.Minute))
|
||||
usernameConfig := dbplugin.UsernameConfig{
|
||||
DisplayName: "test",
|
||||
RoleName: "test",
|
||||
}
|
||||
|
||||
username, password, err := db.CreateUser(statements, usernameConfig, time.Now().Add(time.Minute))
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
@@ -201,7 +211,12 @@ func TestCassandra_RevokeUser(t *testing.T) {
|
||||
CreationStatements: testCassandraRole,
|
||||
}
|
||||
|
||||
username, password, err := db.CreateUser(statements, "test", time.Now().Add(time.Minute))
|
||||
usernameConfig := dbplugin.UsernameConfig{
|
||||
DisplayName: "test",
|
||||
RoleName: "test",
|
||||
}
|
||||
|
||||
username, password, err := db.CreateUser(statements, usernameConfig, time.Now().Add(time.Minute))
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
package cassandra
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
uuid "github.com/hashicorp/go-uuid"
|
||||
)
|
||||
|
||||
// cassandraCredentialsProducer implements CredentialsProducer and provides an
|
||||
// interface for cassandra databases to generate user information.
|
||||
type cassandraCredentialsProducer struct{}
|
||||
|
||||
func (ccp *cassandraCredentialsProducer) GenerateUsername(displayName string) (string, error) {
|
||||
userUUID, err := uuid.GenerateUUID()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
username := fmt.Sprintf("vault_%s_%s_%d", displayName, userUUID, time.Now().Unix())
|
||||
username = strings.Replace(username, "-", "_", -1)
|
||||
|
||||
return username, nil
|
||||
}
|
||||
|
||||
func (ccp *cassandraCredentialsProducer) GeneratePassword() (string, error) {
|
||||
password, err := uuid.GenerateUUID()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return password, nil
|
||||
}
|
||||
|
||||
func (ccp *cassandraCredentialsProducer) GenerateExpiration(ttl time.Time) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package mongodb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
uuid "github.com/hashicorp/go-uuid"
|
||||
)
|
||||
|
||||
// mongoDBCredentialsProducer implements CredentialsProducer and provides an
|
||||
// interface for databases to generate user information.
|
||||
type mongoDBCredentialsProducer struct{}
|
||||
|
||||
func (cp *mongoDBCredentialsProducer) GenerateUsername(displayName string) (string, error) {
|
||||
userUUID, err := uuid.GenerateUUID()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
username := fmt.Sprintf("vault-%s-%s", displayName, userUUID)
|
||||
|
||||
return username, nil
|
||||
}
|
||||
|
||||
func (cp *mongoDBCredentialsProducer) GeneratePassword() (string, error) {
|
||||
password, err := uuid.GenerateUUID()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return password, nil
|
||||
}
|
||||
|
||||
func (cp *mongoDBCredentialsProducer) GenerateExpiration(ttl time.Time) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
@@ -29,7 +29,12 @@ func New() (interface{}, error) {
|
||||
connProducer := &mongoDBConnectionProducer{}
|
||||
connProducer.Type = mongoDBTypeName
|
||||
|
||||
credsProducer := &mongoDBCredentialsProducer{}
|
||||
credsProducer := &credsutil.SQLCredentialsProducer{
|
||||
DisplayNameLen: 15,
|
||||
RoleNameLen: 15,
|
||||
UsernameLen: 100,
|
||||
Separator: "-",
|
||||
}
|
||||
|
||||
dbType := &MongoDB{
|
||||
ConnectionProducer: connProducer,
|
||||
@@ -72,7 +77,7 @@ func (m *MongoDB) getConnection() (*mgo.Session, error) {
|
||||
//
|
||||
// JSON Example:
|
||||
// { "db": "admin", "roles": [{ "role": "readWrite" }, {"role": "read", "db": "foo"}] }
|
||||
func (m *MongoDB) CreateUser(statements dbplugin.Statements, usernamePrefix string, expiration time.Time) (username string, password string, err error) {
|
||||
func (m *MongoDB) CreateUser(statements dbplugin.Statements, usernameConfig dbplugin.UsernameConfig, expiration time.Time) (username string, password string, err error) {
|
||||
// Grab the lock
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
@@ -86,7 +91,7 @@ func (m *MongoDB) CreateUser(statements dbplugin.Statements, usernamePrefix stri
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
username, err = m.GenerateUsername(usernamePrefix)
|
||||
username, err = m.GenerateUsername(usernameConfig)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
@@ -114,7 +114,12 @@ func TestMongoDB_CreateUser(t *testing.T) {
|
||||
CreationStatements: testMongoDBRole,
|
||||
}
|
||||
|
||||
username, password, err := db.CreateUser(statements, "test", time.Now().Add(time.Minute))
|
||||
usernameConfig := dbplugin.UsernameConfig{
|
||||
DisplayName: "test",
|
||||
RoleName: "test",
|
||||
}
|
||||
|
||||
username, password, err := db.CreateUser(statements, usernameConfig, time.Now().Add(time.Minute))
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
@@ -146,7 +151,12 @@ func TestMongoDB_RevokeUser(t *testing.T) {
|
||||
CreationStatements: testMongoDBRole,
|
||||
}
|
||||
|
||||
username, password, err := db.CreateUser(statements, "test", time.Now().Add(time.Minute))
|
||||
usernameConfig := dbplugin.UsernameConfig{
|
||||
DisplayName: "test",
|
||||
RoleName: "test",
|
||||
}
|
||||
|
||||
username, password, err := db.CreateUser(statements, usernameConfig, time.Now().Add(time.Minute))
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
@@ -29,7 +29,9 @@ func New() (interface{}, error) {
|
||||
|
||||
credsProducer := &credsutil.SQLCredentialsProducer{
|
||||
DisplayNameLen: 20,
|
||||
RoleNameLen: 20,
|
||||
UsernameLen: 128,
|
||||
Separator: "-",
|
||||
}
|
||||
|
||||
dbType := &MSSQL{
|
||||
@@ -68,7 +70,7 @@ func (m *MSSQL) getConnection() (*sql.DB, error) {
|
||||
|
||||
// CreateUser generates the username/password on the underlying MSSQL secret backend as instructed by
|
||||
// the CreationStatement provided.
|
||||
func (m *MSSQL) CreateUser(statements dbplugin.Statements, usernamePrefix string, expiration time.Time) (username string, password string, err error) {
|
||||
func (m *MSSQL) CreateUser(statements dbplugin.Statements, usernameConfig dbplugin.UsernameConfig, expiration time.Time) (username string, password string, err error) {
|
||||
// Grab the lock
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
@@ -83,7 +85,7 @@ func (m *MSSQL) CreateUser(statements dbplugin.Statements, usernamePrefix string
|
||||
return "", "", dbutil.ErrEmptyCreationStatement
|
||||
}
|
||||
|
||||
username, err = m.GenerateUsername(usernamePrefix)
|
||||
username, err = m.GenerateUsername(usernameConfig)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
@@ -63,8 +63,13 @@ func TestMSSQL_CreateUser(t *testing.T) {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
usernameConfig := dbplugin.UsernameConfig{
|
||||
DisplayName: "test",
|
||||
RoleName: "test",
|
||||
}
|
||||
|
||||
// Test with no configured Creation Statememt
|
||||
_, _, err = db.CreateUser(dbplugin.Statements{}, "test", time.Now().Add(time.Minute))
|
||||
_, _, err = db.CreateUser(dbplugin.Statements{}, usernameConfig, time.Now().Add(time.Minute))
|
||||
if err == nil {
|
||||
t.Fatal("Expected error when no creation statement is provided")
|
||||
}
|
||||
@@ -73,7 +78,7 @@ func TestMSSQL_CreateUser(t *testing.T) {
|
||||
CreationStatements: testMSSQLRole,
|
||||
}
|
||||
|
||||
username, password, err := db.CreateUser(statements, "test", time.Now().Add(time.Minute))
|
||||
username, password, err := db.CreateUser(statements, usernameConfig, time.Now().Add(time.Minute))
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
@@ -104,7 +109,12 @@ func TestMSSQL_RevokeUser(t *testing.T) {
|
||||
CreationStatements: testMSSQLRole,
|
||||
}
|
||||
|
||||
username, password, err := db.CreateUser(statements, "test", time.Now().Add(2*time.Second))
|
||||
usernameConfig := dbplugin.UsernameConfig{
|
||||
DisplayName: "test",
|
||||
RoleName: "test",
|
||||
}
|
||||
|
||||
username, password, err := db.CreateUser(statements, usernameConfig, time.Now().Add(2*time.Second))
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
@@ -123,7 +133,7 @@ func TestMSSQL_RevokeUser(t *testing.T) {
|
||||
t.Fatal("Credentials were not revoked")
|
||||
}
|
||||
|
||||
username, password, err = db.CreateUser(statements, "test", time.Now().Add(2*time.Second))
|
||||
username, password, err = db.CreateUser(statements, usernameConfig, time.Now().Add(2*time.Second))
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
@@ -23,8 +23,8 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
DisplayNameLen int = 10
|
||||
LegacyDisplayNameLen int = 4
|
||||
MetadataLen int = 10
|
||||
LegacyMetadataLen int = 4
|
||||
UsernameLen int = 32
|
||||
LegacyUsernameLen int = 16
|
||||
)
|
||||
@@ -35,14 +35,16 @@ type MySQL struct {
|
||||
}
|
||||
|
||||
// New implements builtinplugins.BuiltinFactory
|
||||
func New(displayLen, usernameLen int) func() (interface{}, error) {
|
||||
func New(metadataLen, usernameLen int) func() (interface{}, error) {
|
||||
return func() (interface{}, error) {
|
||||
connProducer := &connutil.SQLConnectionProducer{}
|
||||
connProducer.Type = mySQLTypeName
|
||||
|
||||
credsProducer := &credsutil.SQLCredentialsProducer{
|
||||
DisplayNameLen: displayLen,
|
||||
DisplayNameLen: metadataLen,
|
||||
RoleNameLen: metadataLen,
|
||||
UsernameLen: usernameLen,
|
||||
Separator: "-",
|
||||
}
|
||||
|
||||
dbType := &MySQL{
|
||||
@@ -56,7 +58,7 @@ func New(displayLen, usernameLen int) func() (interface{}, error) {
|
||||
|
||||
// Run instantiates a MySQL object, and runs the RPC server for the plugin
|
||||
func Run(apiTLSConfig *api.TLSConfig) error {
|
||||
f := New(DisplayNameLen, UsernameLen)
|
||||
f := New(MetadataLen, UsernameLen)
|
||||
dbType, err := f()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -80,7 +82,7 @@ func (m *MySQL) getConnection() (*sql.DB, error) {
|
||||
return db.(*sql.DB), nil
|
||||
}
|
||||
|
||||
func (m *MySQL) CreateUser(statements dbplugin.Statements, usernamePrefix string, expiration time.Time) (username string, password string, err error) {
|
||||
func (m *MySQL) CreateUser(statements dbplugin.Statements, usernameConfig dbplugin.UsernameConfig, expiration time.Time) (username string, password string, err error) {
|
||||
// Grab the lock
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
@@ -95,7 +97,7 @@ func (m *MySQL) CreateUser(statements dbplugin.Statements, usernamePrefix string
|
||||
return "", "", dbutil.ErrEmptyCreationStatement
|
||||
}
|
||||
|
||||
username, err = m.GenerateUsername(usernamePrefix)
|
||||
username, err = m.GenerateUsername(usernameConfig)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ func TestMySQL_Initialize(t *testing.T) {
|
||||
"connection_url": connURL,
|
||||
}
|
||||
|
||||
f := New(DisplayNameLen, UsernameLen)
|
||||
f := New(MetadataLen, UsernameLen)
|
||||
dbRaw, _ := f()
|
||||
db := dbRaw.(*MySQL)
|
||||
connProducer := db.ConnectionProducer.(*connutil.SQLConnectionProducer)
|
||||
@@ -94,7 +94,7 @@ func TestMySQL_CreateUser(t *testing.T) {
|
||||
"connection_url": connURL,
|
||||
}
|
||||
|
||||
f := New(DisplayNameLen, UsernameLen)
|
||||
f := New(MetadataLen, UsernameLen)
|
||||
dbRaw, _ := f()
|
||||
db := dbRaw.(*MySQL)
|
||||
|
||||
@@ -103,8 +103,13 @@ func TestMySQL_CreateUser(t *testing.T) {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
usernameConfig := dbplugin.UsernameConfig{
|
||||
DisplayName: "test",
|
||||
RoleName: "test",
|
||||
}
|
||||
|
||||
// Test with no configured Creation Statememt
|
||||
_, _, err = db.CreateUser(dbplugin.Statements{}, "test", time.Now().Add(time.Minute))
|
||||
_, _, err = db.CreateUser(dbplugin.Statements{}, usernameConfig, time.Now().Add(time.Minute))
|
||||
if err == nil {
|
||||
t.Fatal("Expected error when no creation statement is provided")
|
||||
}
|
||||
@@ -113,7 +118,7 @@ func TestMySQL_CreateUser(t *testing.T) {
|
||||
CreationStatements: testMySQLRoleWildCard,
|
||||
}
|
||||
|
||||
username, password, err := db.CreateUser(statements, "test", time.Now().Add(time.Minute))
|
||||
username, password, err := db.CreateUser(statements, usernameConfig, time.Now().Add(time.Minute))
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
@@ -131,7 +136,7 @@ func TestMySQL_RevokeUser(t *testing.T) {
|
||||
"connection_url": connURL,
|
||||
}
|
||||
|
||||
f := New(DisplayNameLen, UsernameLen)
|
||||
f := New(MetadataLen, UsernameLen)
|
||||
dbRaw, _ := f()
|
||||
db := dbRaw.(*MySQL)
|
||||
|
||||
@@ -144,7 +149,12 @@ func TestMySQL_RevokeUser(t *testing.T) {
|
||||
CreationStatements: testMySQLRoleWildCard,
|
||||
}
|
||||
|
||||
username, password, err := db.CreateUser(statements, "test", time.Now().Add(time.Minute))
|
||||
usernameConfig := dbplugin.UsernameConfig{
|
||||
DisplayName: "test",
|
||||
RoleName: "test",
|
||||
}
|
||||
|
||||
username, password, err := db.CreateUser(statements, usernameConfig, time.Now().Add(time.Minute))
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
@@ -164,7 +174,7 @@ func TestMySQL_RevokeUser(t *testing.T) {
|
||||
}
|
||||
|
||||
statements.CreationStatements = testMySQLRoleWildCard
|
||||
username, password, err = db.CreateUser(statements, "test", time.Now().Add(time.Minute))
|
||||
username, password, err = db.CreateUser(statements, usernameConfig, time.Now().Add(time.Minute))
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
@@ -29,8 +29,10 @@ func New() (interface{}, error) {
|
||||
connProducer.Type = postgreSQLTypeName
|
||||
|
||||
credsProducer := &credsutil.SQLCredentialsProducer{
|
||||
DisplayNameLen: 10,
|
||||
DisplayNameLen: 8,
|
||||
RoleNameLen: 8,
|
||||
UsernameLen: 63,
|
||||
Separator: "-",
|
||||
}
|
||||
|
||||
dbType := &PostgreSQL{
|
||||
@@ -71,7 +73,7 @@ func (p *PostgreSQL) getConnection() (*sql.DB, error) {
|
||||
return db.(*sql.DB), nil
|
||||
}
|
||||
|
||||
func (p *PostgreSQL) CreateUser(statements dbplugin.Statements, usernamePrefix string, expiration time.Time) (username string, password string, err error) {
|
||||
func (p *PostgreSQL) CreateUser(statements dbplugin.Statements, usernameConfig dbplugin.UsernameConfig, expiration time.Time) (username string, password string, err error) {
|
||||
if statements.CreationStatements == "" {
|
||||
return "", "", dbutil.ErrEmptyCreationStatement
|
||||
}
|
||||
@@ -80,7 +82,7 @@ func (p *PostgreSQL) CreateUser(statements dbplugin.Statements, usernamePrefix s
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
username, err = p.GenerateUsername(usernamePrefix)
|
||||
username, err = p.GenerateUsername(usernameConfig)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
@@ -101,8 +101,13 @@ func TestPostgreSQL_CreateUser(t *testing.T) {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
usernameConfig := dbplugin.UsernameConfig{
|
||||
DisplayName: "test",
|
||||
RoleName: "test",
|
||||
}
|
||||
|
||||
// Test with no configured Creation Statememt
|
||||
_, _, err = db.CreateUser(dbplugin.Statements{}, "test", time.Now().Add(time.Minute))
|
||||
_, _, err = db.CreateUser(dbplugin.Statements{}, usernameConfig, time.Now().Add(time.Minute))
|
||||
if err == nil {
|
||||
t.Fatal("Expected error when no creation statement is provided")
|
||||
}
|
||||
@@ -111,7 +116,7 @@ func TestPostgreSQL_CreateUser(t *testing.T) {
|
||||
CreationStatements: testPostgresRole,
|
||||
}
|
||||
|
||||
username, password, err := db.CreateUser(statements, "test", time.Now().Add(time.Minute))
|
||||
username, password, err := db.CreateUser(statements, usernameConfig, time.Now().Add(time.Minute))
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
@@ -121,7 +126,7 @@ func TestPostgreSQL_CreateUser(t *testing.T) {
|
||||
}
|
||||
|
||||
statements.CreationStatements = testPostgresReadOnlyRole
|
||||
username, password, err = db.CreateUser(statements, "test", time.Now().Add(time.Minute))
|
||||
username, password, err = db.CreateUser(statements, usernameConfig, time.Now().Add(time.Minute))
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
@@ -150,7 +155,12 @@ func TestPostgreSQL_RenewUser(t *testing.T) {
|
||||
CreationStatements: testPostgresRole,
|
||||
}
|
||||
|
||||
username, password, err := db.CreateUser(statements, "test", time.Now().Add(2*time.Second))
|
||||
usernameConfig := dbplugin.UsernameConfig{
|
||||
DisplayName: "test",
|
||||
RoleName: "test",
|
||||
}
|
||||
|
||||
username, password, err := db.CreateUser(statements, usernameConfig, time.Now().Add(2*time.Second))
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
@@ -171,7 +181,7 @@ func TestPostgreSQL_RenewUser(t *testing.T) {
|
||||
t.Fatalf("Could not connect with new credentials: %s", err)
|
||||
}
|
||||
statements.RenewStatements = defaultPostgresRenewSQL
|
||||
username, password, err = db.CreateUser(statements, "test", time.Now().Add(2*time.Second))
|
||||
username, password, err = db.CreateUser(statements, usernameConfig, time.Now().Add(2*time.Second))
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
@@ -213,7 +223,12 @@ func TestPostgreSQL_RevokeUser(t *testing.T) {
|
||||
CreationStatements: testPostgresRole,
|
||||
}
|
||||
|
||||
username, password, err := db.CreateUser(statements, "test", time.Now().Add(2*time.Second))
|
||||
usernameConfig := dbplugin.UsernameConfig{
|
||||
DisplayName: "test",
|
||||
RoleName: "test",
|
||||
}
|
||||
|
||||
username, password, err := db.CreateUser(statements, usernameConfig, time.Now().Add(2*time.Second))
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
@@ -232,7 +247,7 @@ func TestPostgreSQL_RevokeUser(t *testing.T) {
|
||||
t.Fatal("Credentials were not revoked")
|
||||
}
|
||||
|
||||
username, password, err = db.CreateUser(statements, "test", time.Now().Add(2*time.Second))
|
||||
username, password, err = db.CreateUser(statements, usernameConfig, time.Now().Add(2*time.Second))
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
@@ -1,12 +1,64 @@
|
||||
package credsutil
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"crypto/rand"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/vault/builtin/logical/database/dbplugin"
|
||||
)
|
||||
|
||||
// CredentialsProducer can be used as an embeded interface in the Database
|
||||
// definition. It implements the methods for generating user information for a
|
||||
// particular database type and is used in all the builtin database types.
|
||||
type CredentialsProducer interface {
|
||||
GenerateUsername(displayName string) (string, error)
|
||||
GenerateUsername(usernameConfig dbplugin.UsernameConfig) (string, error)
|
||||
GeneratePassword() (string, error)
|
||||
GenerateExpiration(ttl time.Time) (string, error)
|
||||
}
|
||||
|
||||
// RandomAlphaNumericOfLen returns a random byte slice of characters [A-Za-z0-9]
|
||||
// of the provided length.
|
||||
func RandomAlphaNumericOfLen(len int) ([]byte, error) {
|
||||
retBytes := make([]byte, len)
|
||||
size := 0
|
||||
|
||||
for size < len {
|
||||
// Extend the len of the random byte slice to lower odds of having to
|
||||
// re-roll.
|
||||
c := len + 3
|
||||
bArr := make([]byte, c)
|
||||
_, err := rand.Read(bArr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, b := range bArr {
|
||||
if size == len {
|
||||
break
|
||||
}
|
||||
|
||||
/**
|
||||
* Each byte will be in [0, 256), but we only care about:
|
||||
*
|
||||
* [48, 57] 0-9
|
||||
* [65, 90] A-Z
|
||||
* [97, 122] a-z
|
||||
*
|
||||
* Which means that the highest bit will always be zero, since the last byte with high bit
|
||||
* zero is 01111111 = 127 which is higher than 122. Lower our odds of having to re-roll a byte by
|
||||
* dividing by two (right bit shift of 1).
|
||||
*/
|
||||
|
||||
b = b >> 1
|
||||
|
||||
// The byte is any of 0-9 A-Z a-z
|
||||
byteIsAllowable := (b >= 48 && b <= 57) || (b >= 65 && b <= 90) || (b >= 97 && b <= 122)
|
||||
if byteIsAllowable {
|
||||
retBytes[size] = b
|
||||
size++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return retBytes, nil
|
||||
}
|
||||
|
||||
30
plugins/helper/database/credsutil/credsutil_test.go
Normal file
30
plugins/helper/database/credsutil/credsutil_test.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package credsutil
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// RandomAlphaNumericOfLen returns a random byte slice of characters [A-Za-z0-9]
|
||||
// of the provided length.
|
||||
func TestRandomAlphaNumericOfLen(t *testing.T) {
|
||||
s, err := RandomAlphaNumericOfLen(1)
|
||||
if err != nil {
|
||||
t.Fatal("Unexpected error: %s", err)
|
||||
}
|
||||
if len(s) != 1 {
|
||||
t.Fatal("Unexpected length of string, expected 1, got string: %s", s)
|
||||
}
|
||||
|
||||
s, err = RandomAlphaNumericOfLen(10)
|
||||
if err != nil {
|
||||
t.Fatal("Unexpected error: %s", err)
|
||||
}
|
||||
if len(s) != 10 {
|
||||
t.Fatal("Unexpected length of string, expected 10, got string: %s", s)
|
||||
}
|
||||
|
||||
if bytes.Equal(s, make([]byte, 10)) {
|
||||
t.Fatal("returned byte slice is empty")
|
||||
}
|
||||
}
|
||||
@@ -2,26 +2,37 @@ package credsutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
uuid "github.com/hashicorp/go-uuid"
|
||||
"github.com/hashicorp/vault/builtin/logical/database/dbplugin"
|
||||
)
|
||||
|
||||
// SQLCredentialsProducer implements CredentialsProducer and provides a generic credentials producer for most sql database types.
|
||||
type SQLCredentialsProducer struct {
|
||||
DisplayNameLen int
|
||||
RoleNameLen int
|
||||
UsernameLen int
|
||||
Separator string
|
||||
}
|
||||
|
||||
func (scp *SQLCredentialsProducer) GenerateUsername(displayName string) (string, error) {
|
||||
func (scp *SQLCredentialsProducer) GenerateUsername(config dbplugin.UsernameConfig) (string, error) {
|
||||
displayName := config.DisplayName
|
||||
if scp.DisplayNameLen > 0 && len(displayName) > scp.DisplayNameLen {
|
||||
displayName = displayName[:scp.DisplayNameLen]
|
||||
}
|
||||
userUUID, err := uuid.GenerateUUID()
|
||||
roleName := config.RoleName
|
||||
if scp.RoleNameLen > 0 && len(roleName) > scp.RoleNameLen {
|
||||
roleName = roleName[:scp.RoleNameLen]
|
||||
}
|
||||
|
||||
userUUID, err := RandomAlphaNumericOfLen(20)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
username := fmt.Sprintf("v-%s-%s", displayName, userUUID)
|
||||
|
||||
username := strings.Join([]string{"v", displayName, roleName, string(userUUID), fmt.Sprint(time.Now().UTC().Unix())}, scp.Separator)
|
||||
if scp.UsernameLen > 0 && len(username) > scp.UsernameLen {
|
||||
username = username[:scp.UsernameLen]
|
||||
}
|
||||
|
||||
@@ -57,7 +57,19 @@ Upon adding a new plugin, the plugin name, SHA256 sum of the executable, and the
|
||||
command that should be used to run the plugin must be provided. The catalog will
|
||||
make sure the executable referenced in the command exists in the plugin
|
||||
directory. When added to the catalog the plugin is not automatically executed,
|
||||
it instead becomes visible to backends and can be executed by them.
|
||||
it instead becomes visible to backends and can be executed by them. For more
|
||||
information on the plugin catalog please see the [Plugin Catalog API
|
||||
docs](/api/system/plugins-catalog.html).
|
||||
|
||||
An example plugin submission looks like:
|
||||
|
||||
```
|
||||
$ vault write sys/plugins/catalog/myplugin-database-plugin \
|
||||
sha_256=<expected SHA256 Hex value of the plugin binary> \
|
||||
command="myplugin"
|
||||
Success! Data written to: sys/plugins/catalog/myplugin-database-plugin
|
||||
```
|
||||
|
||||
|
||||
### Plugin Execution
|
||||
When a backend wants to run a plugin, it first looks up the plugin, by name, in
|
||||
|
||||
@@ -90,6 +90,12 @@ password 8cab931c-d62e-a73d-60d3-5ee85139cd66
|
||||
username v-root-e2978cd0-
|
||||
```
|
||||
|
||||
## Custom Plugins
|
||||
|
||||
This backend allows custom database types to be run through the exposed plugin
|
||||
interface. Please see the [Custom database
|
||||
plugin](/docs/secrets/databases/custom.html) for more information.
|
||||
|
||||
## API
|
||||
|
||||
The Database secret backend has a full HTTP API. Please see the [Database secret
|
||||
|
||||
Reference in New Issue
Block a user