Skip to content

Commit

Permalink
Couchbase - Add username customization (#18)
Browse files Browse the repository at this point in the history
  • Loading branch information
pcman312 authored Feb 10, 2021
1 parent f7ff396 commit 86ab30a
Show file tree
Hide file tree
Showing 1,533 changed files with 443,380 additions and 75 deletions.
27 changes: 22 additions & 5 deletions couchbase.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,16 @@ import (
hclog "github.com/hashicorp/go-hclog"
dbplugin "github.com/hashicorp/vault/sdk/database/dbplugin/v5"
"github.com/hashicorp/vault/sdk/database/helper/credsutil"
"github.com/hashicorp/vault/sdk/helper/strutil"
"github.com/hashicorp/vault/sdk/helper/template"
)

const (
couchbaseTypeName = "couchbase"
defaultCouchbaseUserRole = `{"Roles": [{"role":"ro_admin"}]}`
defaultTimeout = 20000 * time.Millisecond
maxKeyLength = 64

defaultUserNameTemplate = `V_{{.DisplayName | uppercase | truncate 64}}_{{.RoleName | uppercase | truncate 64}}_{{random 20 | uppercase}}_{{unix_time}}`
)

var (
Expand All @@ -30,6 +33,8 @@ var (
type CouchbaseDB struct {
*couchbaseDBConnectionProducer
credsutil.CredentialsProducer

usernameProducer template.StringTemplate
}

// Type that combines the Couchbase Roles and Groups representing specific account permissions. Used to pass roles and or
Expand Down Expand Up @@ -59,7 +64,21 @@ func new() *CouchbaseDB {
}

func (c *CouchbaseDB) Initialize(ctx context.Context, req dbplugin.InitializeRequest) (dbplugin.InitializeResponse, error) {
err := c.couchbaseDBConnectionProducer.Initialize(ctx, req.Config, req.VerifyConnection)
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)
}
c.usernameProducer = up

err = c.couchbaseDBConnectionProducer.Initialize(ctx, req.Config, req.VerifyConnection)
if err != nil {
return dbplugin.InitializeResponse{}, err
}
Expand All @@ -74,9 +93,7 @@ func (c *CouchbaseDB) NewUser(ctx context.Context, req dbplugin.NewUserRequest)
c.Lock()
defer c.Unlock()

username, err := credsutil.GenerateUsername(
credsutil.DisplayName(req.UsernameConfig.DisplayName, maxKeyLength),
credsutil.RoleName(req.UsernameConfig.RoleName, maxKeyLength))
username, err := c.usernameProducer.Generate(req.UsernameConfig)
if err != nil {
return dbplugin.NewUserResponse{}, fmt.Errorf("failed to generate username: %w", err)
}
Expand Down
121 changes: 59 additions & 62 deletions couchbase_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ import (
"testing"
"time"

"github.com/cenkalti/backoff"
dbplugin "github.com/hashicorp/vault/sdk/database/dbplugin/v5"
dbtesting "github.com/hashicorp/vault/sdk/database/dbplugin/v5/testing"
"github.com/ory/dockertest"
dc "github.com/ory/dockertest/docker"
"github.com/stretchr/testify/require"
)

var pre6dot5 = false // check for Pre 6.5.0 Couchbase
Expand All @@ -23,6 +24,12 @@ const (
bucketName = "travel-sample"
)

var (
testCouchbaseRole = fmt.Sprintf(`{"roles":[{"role":"ro_admin"},{"role":"bucket_admin","bucket_name":"%s"}]}`, bucketName)
testCouchbaseGroup = `{"groups":["g1", "g2"]}`
testCouchbaseRoleAndGroup = fmt.Sprintf(`{"roles":[{"role":"ro_admin"},{"role":"bucket_admin","bucket_name":"%s"}],"groups":["g1", "g2"]}`, bucketName)
)

func prepareCouchbaseTestContainer(t *testing.T) (func(), string, int) {
if os.Getenv("COUCHBASE_HOST") != "" {
return func() {}, os.Getenv("COUCHBASE_HOST"), 8091
Expand Down Expand Up @@ -105,17 +112,15 @@ func prepareCouchbaseTestContainer(t *testing.T) (func(), string, int) {
}
return nil
}); err != nil {
t.Fatalf("Could not connect to couchbase: %s", err)
cleanup()
t.Fatalf("Could not connect to couchbase: %s", err)
}

return cleanup, "0.0.0.0", 8091
}

func TestDriver(t *testing.T) {
// Spin up couchbase
cleanup, address, port := prepareCouchbaseTestContainer(t)

defer cleanup()

err := createUser(address, port, adminUsername, adminPassword, "rotate-root", "rotate-rootpassword",
Expand Down Expand Up @@ -160,27 +165,7 @@ func TestDriver(t *testing.T) {
[{"name":"beer-sample","installed":false,"quotaNeeded":104857600},
{"name":"gamesim-sample","installed":false,"quotaNeeded":104857600},
{"name":"travel-sample","installed":false,"quotaNeeded":104857600}] */

if err = backoff.Retry(func() error {
t.Log("Waiting for the bucket to be installed.")

bucketFound, bucketInstalled, err := waitForBucketInstalled(address, adminUsername, adminPassword, bucketName)
if err != nil {
return err
}
if bucketFound == false {
err := backoff.PermanentError{
Err: fmt.Errorf("bucket %s was not found..", bucketName),
}
return &err
}
if bucketInstalled == false {
return fmt.Errorf("waiting for bucket %s to be installed...", bucketName)
}
return nil
}, backoff.NewExponentialBackOff()); err != nil {
t.Fatalf("bucket %s installed check failed: %s", bucketName, err)
}
waitForBucket(t, address, adminUsername, adminPassword, bucketName)

t.Run("Create/Revoke", func(t *testing.T) { testCouchbaseDBCreateUser(t, address, port) })
t.Run("Create/Revoke", func(t *testing.T) { testCouchbaseDBCreateUser_DefaultRole(t, address, port) })
Expand All @@ -191,6 +176,52 @@ func TestDriver(t *testing.T) {
t.Run("Creds", func(t *testing.T) { testCouchbaseDBSetCredentials(t, address, port) })
t.Run("Secret", func(t *testing.T) { testConnectionProducerSecretValues(t) })
t.Run("TimeoutCalc", func(t *testing.T) { testComputeTimeout(t) })

t.Run("custom username template", func(t *testing.T) {
bucket := ""
if pre6dot5 {
bucket = bucketName
}
initReq := dbplugin.InitializeRequest{
Config: map[string]interface{}{
"hosts": address,
"port": port,
"username": adminUsername,
"password": adminPassword,
"bucket_name": bucket,
"username_template": "{{random 2 | uppercase}}_{{unix_time}}_{{.RoleName | uppercase}}_{{.DisplayName | uppercase}}",
},
VerifyConnection: true,
}

db := new()
defer dbtesting.AssertClose(t, db)

dbtesting.AssertInitialize(t, db, initReq)

password := "98yq3thgnakjsfhjkl"
newUserReq := dbplugin.NewUserRequest{
UsernameConfig: dbplugin.UsernameMetadata{
DisplayName: "token",
RoleName: "testrolenamewithmanycharacters",
},
Statements: dbplugin.Statements{
Commands: []string{testCouchbaseRole},
},
Password: password,
Expiration: time.Now().Add(time.Minute),
}

expectedUsernameRegex := "^[A-Z0-9]{2}_[0-9]{10}_TESTROLENAMEWITHMANYCHARACTERS_TOKEN$"

newUserResp, err := db.NewUser(context.Background(), newUserReq)
require.NoError(t, err)
require.Regexp(t, expectedUsernameRegex, newUserResp.Username)

if err := checkCredsExist(t, newUserResp.Username, password, address, port); err != nil {
t.Fatalf("Could not connect to database: %s", err)
}
})
}

func testGetCouchbaseVersion(t *testing.T, address string) {
Expand Down Expand Up @@ -316,9 +347,6 @@ func testCouchbaseDBInitialize_Pre6dot5NoTLS(t *testing.T, address string, port
}

func testCouchbaseDBCreateUser(t *testing.T, address string, port int) {
if os.Getenv("VAULT_ACC") == "" {
t.SkipNow()
}
t.Log("Testing CreateUser()")

connectionDetails := map[string]interface{}{
Expand Down Expand Up @@ -354,7 +382,7 @@ func testCouchbaseDBCreateUser(t *testing.T, address string, port int) {
RoleName: "test",
},
Statements: dbplugin.Statements{
Commands: []string{fmt.Sprintf(testCouchbaseRole, bucketName)},
Commands: []string{testCouchbaseRole},
},
Password: password,
Expiration: time.Now().Add(time.Minute),
Expand All @@ -378,11 +406,6 @@ func testCouchbaseDBCreateUser(t *testing.T, address string, port int) {
}

func checkCredsExist(t *testing.T, username, password, address string, port int) error {
if os.Getenv("VAULT_ACC") == "" {
t.SkipNow()
}
t.Log("Testing checkCredsExist()")

connectionDetails := map[string]interface{}{
"hosts": address,
"port": port,
Expand Down Expand Up @@ -414,9 +437,6 @@ func checkCredsExist(t *testing.T, username, password, address string, port int)
}

func revokeUser(t *testing.T, username, address string, port int) error {
if os.Getenv("VAULT_ACC") == "" {
t.SkipNow()
}
t.Log("Testing RevokeUser()")

connectionDetails := map[string]interface{}{
Expand Down Expand Up @@ -454,9 +474,6 @@ func revokeUser(t *testing.T, username, address string, port int) error {
}

func testCouchbaseDBCreateUser_DefaultRole(t *testing.T, address string, port int) {
if os.Getenv("VAULT_ACC") == "" {
t.SkipNow()
}
t.Log("Testing CreateUser_DefaultRole()")

connectionDetails := map[string]interface{}{
Expand Down Expand Up @@ -517,9 +534,6 @@ func testCouchbaseDBCreateUser_DefaultRole(t *testing.T, address string, port in
}

func testCouchbaseDBCreateUser_plusRole(t *testing.T, address string, port int) {
if os.Getenv("VAULT_ACC") == "" {
t.SkipNow()
}
t.Log("Testing CreateUser_plusRole()")

connectionDetails := map[string]interface{}{
Expand Down Expand Up @@ -556,7 +570,7 @@ func testCouchbaseDBCreateUser_plusRole(t *testing.T, address string, port int)
RoleName: "test",
},
Statements: dbplugin.Statements{
Commands: []string{fmt.Sprintf(testCouchbaseRole, bucketName)},
Commands: []string{testCouchbaseRole},
},
Password: password,
Expiration: time.Now().Add(time.Minute),
Expand All @@ -581,9 +595,6 @@ func testCouchbaseDBCreateUser_plusRole(t *testing.T, address string, port int)

// g1 & g2 must exist in the database.
func testCouchbaseDBCreateUser_groupOnly(t *testing.T, address string, port int) {
if os.Getenv("VAULT_ACC") == "" {
t.SkipNow()
}
if pre6dot5 {
t.Log("Skipping as groups are not supported pre6.5.0")
t.SkipNow()
Expand Down Expand Up @@ -647,9 +658,6 @@ func testCouchbaseDBCreateUser_groupOnly(t *testing.T, address string, port int)
}
}
func testCouchbaseDBCreateUser_roleAndGroup(t *testing.T, address string, port int) {
if os.Getenv("VAULT_ACC") == "" {
t.SkipNow()
}
if pre6dot5 {
t.Log("Skipping as groups are not supported pre6.5.0")
t.SkipNow()
Expand Down Expand Up @@ -690,7 +698,7 @@ func testCouchbaseDBCreateUser_roleAndGroup(t *testing.T, address string, port i
RoleName: "test",
},
Statements: dbplugin.Statements{
Commands: []string{fmt.Sprintf(testCouchbaseRoleAndGroup, bucketName)},
Commands: []string{testCouchbaseRoleAndGroup},
},
Password: password,
Expiration: time.Now().Add(time.Minute),
Expand All @@ -713,9 +721,6 @@ func testCouchbaseDBCreateUser_roleAndGroup(t *testing.T, address string, port i
}
}
func testCouchbaseDBRotateRootCredentials(t *testing.T, address string, port int) {
if os.Getenv("VAULT_ACC") == "" {
t.SkipNow()
}
t.Log("Testing RotateRootCredentials()")

connectionDetails := map[string]interface{}{
Expand Down Expand Up @@ -829,10 +834,6 @@ func doCouchbaseDBSetCredentials(t *testing.T, username, password, address strin
}

func testCouchbaseDBSetCredentials(t *testing.T, address string, port int) {
if os.Getenv("VAULT_ACC") == "" {
t.SkipNow()
}

doCouchbaseDBSetCredentials(t, "vault-edu", "password", address, port)
}

Expand Down Expand Up @@ -861,7 +862,3 @@ func testComputeTimeout(t *testing.T) {
t.Fatal("WithTimeout failed")
}
}

const testCouchbaseRole = `{"roles":[{"role":"ro_admin"},{"role":"bucket_admin","bucket_name":"%s"}]}`
const testCouchbaseGroup = `{"groups":["g1", "g2"]}`
const testCouchbaseRoleAndGroup = `{"roles":[{"role":"ro_admin"},{"role":"bucket_admin","bucket_name":"%s"}],"groups":["g1", "g2"]}`
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ require (
github.com/hashicorp/errwrap v1.0.0
github.com/hashicorp/go-hclog v0.14.1
github.com/hashicorp/go-version v1.2.1
github.com/hashicorp/vault/sdk v0.1.14-0.20201022214319-d87657199d4b
github.com/hashicorp/vault/sdk v0.1.14-0.20210204230556-cf85a862b7c6
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d // indirect
github.com/lib/pq v1.8.0 // indirect
github.com/mitchellh/mapstructure v1.3.3
github.com/ory/dockertest v3.3.5+incompatible
github.com/sirupsen/logrus v1.6.0 // indirect
github.com/stretchr/testify v1.5.1
)
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,10 @@ github.com/hashicorp/vault/api v1.0.5-0.20200519221902-385fac77e20f/go.mod h1:eu
github.com/hashicorp/vault/sdk v0.1.14-0.20200519221530-14615acda45f/go.mod h1:WX57W2PwkrOPQ6rVQk+dy5/htHIaB4aBM70EwKThu10=
github.com/hashicorp/vault/sdk v0.1.14-0.20201022214319-d87657199d4b h1:kT0HPwthAisVgxAkm/kNGI2IHm0rAco28dOs3geL90E=
github.com/hashicorp/vault/sdk v0.1.14-0.20201022214319-d87657199d4b/go.mod h1:cAGI4nVnEfAyMeqt9oB+Mase8DNn3qA/LDNHURiwssY=
github.com/hashicorp/vault/sdk v0.1.14-0.20210127185906-6b455835fa8c h1:CSvbHEivYEK8njYzPB1Wn972h4U0z+xMGFZnTdVK+s4=
github.com/hashicorp/vault/sdk v0.1.14-0.20210127185906-6b455835fa8c/go.mod h1:cAGI4nVnEfAyMeqt9oB+Mase8DNn3qA/LDNHURiwssY=
github.com/hashicorp/vault/sdk v0.1.14-0.20210204230556-cf85a862b7c6 h1:1G91ESn5mCFH7J61P7JKMF1KVYcVsN+HCEJ1Jab4U6M=
github.com/hashicorp/vault/sdk v0.1.14-0.20210204230556-cf85a862b7c6/go.mod h1:cAGI4nVnEfAyMeqt9oB+Mase8DNn3qA/LDNHURiwssY=
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ=
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
Expand Down
Loading

0 comments on commit 86ab30a

Please sign in to comment.