From e39ff35795d123dc5fe130d9b303fd45f0861aba Mon Sep 17 00:00:00 2001
From: Brian Kassouf <bkassouf@hashicorp.com>
Date: Mon, 5 Jun 2017 14:16:47 -0700
Subject: [PATCH 1/6] Pass the role name into the username created by the
 database backend

---
 builtin/logical/database/dbplugin/client.go   |  4 +-
 .../database/dbplugin/databasemiddleware.go   |  8 +--
 builtin/logical/database/dbplugin/plugin.go   | 13 ++++-
 .../logical/database/dbplugin/plugin_test.go  | 35 ++++++++----
 builtin/logical/database/dbplugin/server.go   |  2 +-
 builtin/logical/database/path_creds_create.go |  8 ++-
 helper/builtinplugins/builtin.go              |  8 +--
 plugins/database/cassandra/cassandra.go       | 11 +++-
 .../cassandra/credentials_producer.go         | 37 ------------
 .../database/mongodb/credentials_producer.go  | 36 ------------
 plugins/database/mongodb/mongodb.go           | 11 +++-
 plugins/database/mssql/mssql.go               |  6 +-
 plugins/database/mysql/mysql.go               | 20 ++++---
 plugins/database/postgresql/postgresql.go     |  8 ++-
 .../helper/database/credsutil/credsutil.go    | 56 ++++++++++++++++++-
 plugins/helper/database/credsutil/sql.go      | 18 ++++--
 16 files changed, 157 insertions(+), 124 deletions(-)
 delete mode 100644 plugins/database/cassandra/credentials_producer.go
 delete mode 100644 plugins/database/mongodb/credentials_producer.go

diff --git a/builtin/logical/database/dbplugin/client.go b/builtin/logical/database/dbplugin/client.go
index 0c095f891035..95b614b8073d 100644
--- a/builtin/logical/database/dbplugin/client.go
+++ b/builtin/logical/database/dbplugin/client.go
@@ -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,
 	}
 
diff --git a/builtin/logical/database/dbplugin/databasemiddleware.go b/builtin/logical/database/dbplugin/databasemiddleware.go
index 83f57ef87e80..87dfa6c3143d 100644
--- a/builtin/logical/database/dbplugin/databasemiddleware.go
+++ b/builtin/logical/database/dbplugin/databasemiddleware.go
@@ -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) {
diff --git a/builtin/logical/database/dbplugin/plugin.go b/builtin/logical/database/dbplugin/plugin.go
index bc63594ae8c0..9ab7020436ab 100644
--- a/builtin/logical/database/dbplugin/plugin.go
+++ b/builtin/logical/database/dbplugin/plugin.go
@@ -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
 }
 
diff --git a/builtin/logical/database/dbplugin/plugin_test.go b/builtin/logical/database/dbplugin/plugin_test.go
index c95e119e0ca0..d0e7073d87e9 100644
--- a/builtin/logical/database/dbplugin/plugin_test.go
+++ b/builtin/logical/database/dbplugin/plugin_test.go
@@ -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)
 	}
diff --git a/builtin/logical/database/dbplugin/server.go b/builtin/logical/database/dbplugin/server.go
index 9546d092c276..381f0ae2a1f4 100644
--- a/builtin/logical/database/dbplugin/server.go
+++ b/builtin/logical/database/dbplugin/server.go
@@ -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
 }
diff --git a/builtin/logical/database/path_creds_create.go b/builtin/logical/database/path_creds_create.go
index 7bc7dfa6fe7e..6fb61a3e5209 100644
--- a/builtin/logical/database/path_creds_create.go
+++ b/builtin/logical/database/path_creds_create.go
@@ -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 {
diff --git a/helper/builtinplugins/builtin.go b/helper/builtinplugins/builtin.go
index f1def367a753..40f3b130730e 100644
--- a/helper/builtinplugins/builtin.go
+++ b/helper/builtinplugins/builtin.go
@@ -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,
diff --git a/plugins/database/cassandra/cassandra.go b/plugins/database/cassandra/cassandra.go
index cafb2545cfb1..bb1d4c090fb7 100644
--- a/plugins/database/cassandra/cassandra.go
+++ b/plugins/database/cassandra/cassandra.go
@@ -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,7 +95,7 @@ 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
 	}
diff --git a/plugins/database/cassandra/credentials_producer.go b/plugins/database/cassandra/credentials_producer.go
deleted file mode 100644
index 8e9c3a509adf..000000000000
--- a/plugins/database/cassandra/credentials_producer.go
+++ /dev/null
@@ -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
-}
diff --git a/plugins/database/mongodb/credentials_producer.go b/plugins/database/mongodb/credentials_producer.go
deleted file mode 100644
index 80dc2c3d39d1..000000000000
--- a/plugins/database/mongodb/credentials_producer.go
+++ /dev/null
@@ -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
-}
diff --git a/plugins/database/mongodb/mongodb.go b/plugins/database/mongodb/mongodb.go
index 5d7aa09b1c71..31fdd85cc82a 100644
--- a/plugins/database/mongodb/mongodb.go
+++ b/plugins/database/mongodb/mongodb.go
@@ -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
 	}
diff --git a/plugins/database/mssql/mssql.go b/plugins/database/mssql/mssql.go
index 9b22aa87cdfa..2f4a628b94d2 100644
--- a/plugins/database/mssql/mssql.go
+++ b/plugins/database/mssql/mssql.go
@@ -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
 	}
diff --git a/plugins/database/mysql/mysql.go b/plugins/database/mysql/mysql.go
index b875af520d56..31a7b7c7d60e 100644
--- a/plugins/database/mysql/mysql.go
+++ b/plugins/database/mysql/mysql.go
@@ -23,10 +23,10 @@ const (
 )
 
 var (
-	DisplayNameLen       int = 10
-	LegacyDisplayNameLen int = 4
-	UsernameLen          int = 32
-	LegacyUsernameLen    int = 16
+	MetadataLen       int = 10
+	LegacyMetadataLen int = 4
+	UsernameLen       int = 32
+	LegacyUsernameLen int = 16
 )
 
 type MySQL struct {
@@ -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
 	}
diff --git a/plugins/database/postgresql/postgresql.go b/plugins/database/postgresql/postgresql.go
index 69bfe3405959..b1ebc9987ac8 100644
--- a/plugins/database/postgresql/postgresql.go
+++ b/plugins/database/postgresql/postgresql.go
@@ -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
 	}
diff --git a/plugins/helper/database/credsutil/credsutil.go b/plugins/helper/database/credsutil/credsutil.go
index bc35617ac215..01b5f3d259db 100644
--- a/plugins/helper/database/credsutil/credsutil.go
+++ b/plugins/helper/database/credsutil/credsutil.go
@@ -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
+}
diff --git a/plugins/helper/database/credsutil/sql.go b/plugins/helper/database/credsutil/sql.go
index a7929ccb1a80..dd79d558234c 100644
--- a/plugins/helper/database/credsutil/sql.go
+++ b/plugins/helper/database/credsutil/sql.go
@@ -1,27 +1,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), string(time.Now().UTC().Unix())}, scp.Separator)
 	if scp.UsernameLen > 0 && len(username) > scp.UsernameLen {
 		username = username[:scp.UsernameLen]
 	}

From 8f5a8f94d4a5b333e6efc031901d3a16bbee18d0 Mon Sep 17 00:00:00 2001
From: Brian Kassouf <bkassouf@hashicorp.com>
Date: Mon, 5 Jun 2017 14:27:32 -0700
Subject: [PATCH 2/6] Fix tests

---
 plugins/database/cassandra/cassandra_test.go  | 21 ++++++++++++--
 plugins/database/mongodb/mongodb_test.go      | 14 +++++++--
 plugins/database/mssql/mssql_test.go          | 18 +++++++++---
 plugins/database/mysql/mysql_test.go          | 24 ++++++++++-----
 .../database/postgresql/postgresql_test.go    | 29 ++++++++++++++-----
 5 files changed, 83 insertions(+), 23 deletions(-)

diff --git a/plugins/database/cassandra/cassandra_test.go b/plugins/database/cassandra/cassandra_test.go
index b10008e3519d..17f7d0a92a09 100644
--- a/plugins/database/cassandra/cassandra_test.go
+++ b/plugins/database/cassandra/cassandra_test.go
@@ -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)
 	}
diff --git a/plugins/database/mongodb/mongodb_test.go b/plugins/database/mongodb/mongodb_test.go
index 1fa14aa37fc4..95f6e90888c3 100644
--- a/plugins/database/mongodb/mongodb_test.go
+++ b/plugins/database/mongodb/mongodb_test.go
@@ -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)
 	}
diff --git a/plugins/database/mssql/mssql_test.go b/plugins/database/mssql/mssql_test.go
index 830e38abbd33..e524455cc333 100644
--- a/plugins/database/mssql/mssql_test.go
+++ b/plugins/database/mssql/mssql_test.go
@@ -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)
 	}
diff --git a/plugins/database/mysql/mysql_test.go b/plugins/database/mysql/mysql_test.go
index 72dbd81560ad..616859be8b66 100644
--- a/plugins/database/mysql/mysql_test.go
+++ b/plugins/database/mysql/mysql_test.go
@@ -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)
 	}
diff --git a/plugins/database/postgresql/postgresql_test.go b/plugins/database/postgresql/postgresql_test.go
index 3fd441bc59bd..944a3b7b5c4c 100644
--- a/plugins/database/postgresql/postgresql_test.go
+++ b/plugins/database/postgresql/postgresql_test.go
@@ -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)
 	}

From 12d05e72a66252e1f441630833dfee7192ca4da0 Mon Sep 17 00:00:00 2001
From: Brian Kassouf <bkassouf@hashicorp.com>
Date: Mon, 5 Jun 2017 14:43:18 -0700
Subject: [PATCH 3/6] Fix unix time stamp to string conversion

---
 plugins/helper/database/credsutil/sql.go | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/plugins/helper/database/credsutil/sql.go b/plugins/helper/database/credsutil/sql.go
index dd79d558234c..ecd9b306a9ec 100644
--- a/plugins/helper/database/credsutil/sql.go
+++ b/plugins/helper/database/credsutil/sql.go
@@ -1,6 +1,7 @@
 package credsutil
 
 import (
+	"fmt"
 	"strings"
 	"time"
 
@@ -31,7 +32,7 @@ func (scp *SQLCredentialsProducer) GenerateUsername(config dbplugin.UsernameConf
 		return "", err
 	}
 
-	username := strings.Join([]string{"v", displayName, roleName, string(userUUID), string(time.Now().UTC().Unix())}, scp.Separator)
+	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]
 	}

From 328786e478d3a2906fc43ff0fc92b0fc134dde03 Mon Sep 17 00:00:00 2001
From: Brian Kassouf <bkassouf@hashicorp.com>
Date: Mon, 5 Jun 2017 15:02:37 -0700
Subject: [PATCH 4/6] Add test for RandomAlphaNumericOfLen function

---
 .../database/credsutil/credsutil_test.go      | 30 +++++++++++++++++++
 1 file changed, 30 insertions(+)
 create mode 100644 plugins/helper/database/credsutil/credsutil_test.go

diff --git a/plugins/helper/database/credsutil/credsutil_test.go b/plugins/helper/database/credsutil/credsutil_test.go
new file mode 100644
index 000000000000..c49aab59ca3d
--- /dev/null
+++ b/plugins/helper/database/credsutil/credsutil_test.go
@@ -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")
+	}
+}

From ae07499006d3eb58d462a59466f27bee27a09ae5 Mon Sep 17 00:00:00 2001
From: Brian Kassouf <bkassouf@hashicorp.com>
Date: Mon, 5 Jun 2017 15:29:48 -0700
Subject: [PATCH 5/6] Lowercase the cassandra password

---
 plugins/database/cassandra/cassandra.go | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/plugins/database/cassandra/cassandra.go b/plugins/database/cassandra/cassandra.go
index bb1d4c090fb7..3ed59a8465c8 100644
--- a/plugins/database/cassandra/cassandra.go
+++ b/plugins/database/cassandra/cassandra.go
@@ -99,6 +99,8 @@ func (c *Cassandra) CreateUser(statements dbplugin.Statements, usernameConfig db
 	if err != nil {
 		return "", "", err
 	}
+	// Cassandra doesn't like the uppercase usernames
+	username = strings.ToLower(username)
 
 	password, err = c.GeneratePassword()
 	if err != nil {

From 0017c15b007b3e7a7a140d95cf722ad6cbe0fde2 Mon Sep 17 00:00:00 2001
From: Brian Kassouf <bkassouf@hashicorp.com>
Date: Mon, 5 Jun 2017 16:23:51 -0700
Subject: [PATCH 6/6] Upate docs to add more references to running custom
 plugins

---
 website/source/docs/internals/plugins.html.md      | 14 +++++++++++++-
 .../source/docs/secrets/databases/index.html.md    |  6 ++++++
 2 files changed, 19 insertions(+), 1 deletion(-)

diff --git a/website/source/docs/internals/plugins.html.md b/website/source/docs/internals/plugins.html.md
index 937eeda54de5..02f151249189 100644
--- a/website/source/docs/internals/plugins.html.md
+++ b/website/source/docs/internals/plugins.html.md
@@ -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
diff --git a/website/source/docs/secrets/databases/index.html.md b/website/source/docs/secrets/databases/index.html.md
index c88699c44705..ee6ef86a085a 100644
--- a/website/source/docs/secrets/databases/index.html.md
+++ b/website/source/docs/secrets/databases/index.html.md
@@ -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