MongoDB - add username customization (#10858)

This commit is contained in:
Michael Golowka
2021-02-11 14:07:58 -07:00
committed by GitHub
parent 2b2d4ff909
commit b08870db30
2 changed files with 105 additions and 10 deletions

View File

@@ -7,9 +7,11 @@ import (
"io"
"strings"
dbplugin "github.com/hashicorp/vault/sdk/database/dbplugin/v5"
"github.com/hashicorp/vault/sdk/database/helper/credsutil"
"github.com/hashicorp/vault/sdk/database/helper/dbutil"
"github.com/hashicorp/vault/sdk/helper/strutil"
"github.com/hashicorp/vault/sdk/helper/template"
dbplugin "github.com/hashicorp/vault/sdk/database/dbplugin/v5"
"github.com/mitchellh/mapstructure"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
@@ -18,11 +20,17 @@ import (
"go.mongodb.org/mongo-driver/x/mongo/driver/connstring"
)
const mongoDBTypeName = "mongodb"
const (
mongoDBTypeName = "mongodb"
defaultUserNameTemplate = `{{ printf "v-%s-%s-%s-%s" (.DisplayName | truncate 15) (.RoleName | truncate 15) (random 20) (unix_time) | truncate 100 }}`
)
// MongoDB is an implementation of Database interface
type MongoDB struct {
*mongoDBConnectionProducer
usernameProducer template.StringTemplate
}
var _ dbplugin.Database = &MongoDB{}
@@ -64,7 +72,26 @@ func (m *MongoDB) Initialize(ctx context.Context, req dbplugin.InitializeRequest
m.RawConfig = req.Config
err := mapstructure.WeakDecode(req.Config, m.mongoDBConnectionProducer)
usernameTemplate, err := strutil.GetString(req.Config, "username_template")
if err != nil {
return dbplugin.InitializeResponse{}, fmt.Errorf("failed to retrieve username_template: %w", err)
}
if usernameTemplate == "" {
usernameTemplate = defaultUserNameTemplate
}
up, err := template.NewTemplate(template.Template(usernameTemplate))
if err != nil {
return dbplugin.InitializeResponse{}, fmt.Errorf("unable to initialize username template: %w", err)
}
m.usernameProducer = up
_, err = m.usernameProducer.Generate(dbplugin.UsernameMetadata{})
if err != nil {
return dbplugin.InitializeResponse{}, fmt.Errorf("invalid username template: %w", err)
}
err = mapstructure.WeakDecode(req.Config, m.mongoDBConnectionProducer)
if err != nil {
return dbplugin.InitializeResponse{}, err
}
@@ -116,12 +143,7 @@ func (m *MongoDB) NewUser(ctx context.Context, req dbplugin.NewUserRequest) (dbp
return dbplugin.NewUserResponse{}, dbutil.ErrEmptyCreationStatement
}
username, err := credsutil.GenerateUsername(
credsutil.DisplayName(req.UsernameConfig.DisplayName, 15),
credsutil.RoleName(req.UsernameConfig.RoleName, 15),
credsutil.MaxLength(100),
credsutil.Separator("-"),
)
username, err := m.usernameProducer.Generate(req.UsernameConfig)
if err != nil {
return dbplugin.NewUserResponse{}, err
}

View File

@@ -14,6 +14,7 @@ import (
"github.com/hashicorp/vault/helper/testhelpers/mongodb"
dbplugin "github.com/hashicorp/vault/sdk/database/dbplugin/v5"
dbtesting "github.com/hashicorp/vault/sdk/database/dbplugin/v5/testing"
"github.com/stretchr/testify/require"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"go.mongodb.org/mongo-driver/mongo/readpref"
@@ -51,6 +52,78 @@ func TestMongoDB_Initialize(t *testing.T) {
}
}
func TestNewUser_usernameTemplate(t *testing.T) {
type testCase struct {
usernameTemplate string
newUserReq dbplugin.NewUserRequest
expectedUsernameRegex string
}
tests := map[string]testCase{
"default username template": {
usernameTemplate: "",
newUserReq: dbplugin.NewUserRequest{
UsernameConfig: dbplugin.UsernameMetadata{
DisplayName: "token",
RoleName: "testrolenamewithmanycharacters",
},
Statements: dbplugin.Statements{
Commands: []string{mongoAdminRole},
},
Password: "98yq3thgnakjsfhjkl",
Expiration: time.Now().Add(time.Minute),
},
expectedUsernameRegex: "^v-token-testrolenamewit-[a-zA-Z0-9]{20}-[0-9]{10}$",
},
"custom username template": {
usernameTemplate: "{{random 2 | uppercase}}_{{unix_time}}_{{.RoleName | uppercase}}_{{.DisplayName | uppercase}}",
newUserReq: dbplugin.NewUserRequest{
UsernameConfig: dbplugin.UsernameMetadata{
DisplayName: "token",
RoleName: "testrolenamewithmanycharacters",
},
Statements: dbplugin.Statements{
Commands: []string{mongoAdminRole},
},
Password: "98yq3thgnakjsfhjkl",
Expiration: time.Now().Add(time.Minute),
},
expectedUsernameRegex: "^[A-Z0-9]{2}_[0-9]{10}_TESTROLENAMEWITHMANYCHARACTERS_TOKEN$",
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
cleanup, connURL := mongodb.PrepareTestContainer(t, "latest")
defer cleanup()
db := new()
defer dbtesting.AssertClose(t, db)
initReq := dbplugin.InitializeRequest{
Config: map[string]interface{}{
"connection_url": connURL,
"username_template": test.usernameTemplate,
},
VerifyConnection: true,
}
dbtesting.AssertInitialize(t, db, initReq)
ctx := context.Background()
newUserResp, err := db.NewUser(ctx, test.newUserReq)
require.NoError(t, err)
require.Regexp(t, test.expectedUsernameRegex, newUserResp.Username)
assertCredsExist(t, newUserResp.Username, test.newUserReq.Password, connURL)
})
}
}
func TestMongoDB_CreateUser(t *testing.T) {
cleanup, connURL := mongodb.PrepareTestContainer(t, "latest")
defer cleanup()