Skip to content

Commit

Permalink
InfluxDB - Add username customization (hashicorp#11796)
Browse files Browse the repository at this point in the history
* Add username_template to influxdb

* go fmt

* goimport for influxdb.go
  • Loading branch information
MilenaHC authored and jartek committed Sep 11, 2021
1 parent eded4b8 commit 5f42e25
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 13 deletions.
40 changes: 30 additions & 10 deletions plugins/database/influxdb/influxdb.go
Expand Up @@ -7,9 +7,9 @@ import (

multierror "github.com/hashicorp/go-multierror"
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"
influx "github.com/influxdata/influxdb/client/v2"
)

Expand All @@ -18,13 +18,17 @@ const (
defaultUserDeletionIFQL = `DROP USER "{{username}}";`
defaultRootCredentialRotationIFQL = `SET PASSWORD FOR "{{username}}" = '{{password}}';`
influxdbTypeName = "influxdb"

defaultUserNameTemplate = `{{ printf "v_%s_%s_%s_%s" (.DisplayName | truncate 15) (.RoleName | truncate 15) (random 20) (unix_time) | truncate 100 | replace "-" "_" | lowercase }}`
)

var _ dbplugin.Database = &Influxdb{}

// Influxdb is an implementation of Database interface
type Influxdb struct {
*influxdbConnectionProducer

usernameProducer template.StringTemplate
}

// New returns a new Cassandra instance
Expand Down Expand Up @@ -58,6 +62,29 @@ func (i *Influxdb) getConnection(ctx context.Context) (influx.Client, error) {
return cli.(influx.Client), nil
}

func (i *Influxdb) Initialize(ctx context.Context, req dbplugin.InitializeRequest) (resp dbplugin.InitializeResponse, err error) {
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)
}
i.usernameProducer = up

_, err = i.usernameProducer.Generate(dbplugin.UsernameMetadata{})
if err != nil {
return dbplugin.InitializeResponse{}, fmt.Errorf("invalid username template: %w", err)
}

return i.influxdbConnectionProducer.Initialize(ctx, req)
}

// NewUser generates the username/password on the underlying Influxdb secret backend as instructed by
// the statements provided.
func (i *Influxdb) NewUser(ctx context.Context, req dbplugin.NewUserRequest) (resp dbplugin.NewUserResponse, err error) {
Expand All @@ -79,17 +106,10 @@ func (i *Influxdb) NewUser(ctx context.Context, req dbplugin.NewUserRequest) (re
rollbackIFQL = []string{defaultUserDeletionIFQL}
}

username, err := credsutil.GenerateUsername(
credsutil.DisplayName(req.UsernameConfig.DisplayName, 15),
credsutil.RoleName(req.UsernameConfig.RoleName, 15),
credsutil.MaxLength(100),
credsutil.Separator("_"),
credsutil.ToLower(),
)
username, err := i.usernameProducer.Generate(req.UsernameConfig)
if err != nil {
return dbplugin.NewUserResponse{}, fmt.Errorf("failed to generate username: %w", err)
return dbplugin.NewUserResponse{}, err
}
username = strings.Replace(username, "-", "_", -1)

for _, stmt := range creationIFQL {
for _, query := range strutil.ParseArbitraryStringSlice(stmt, ";") {
Expand Down
47 changes: 44 additions & 3 deletions plugins/database/influxdb/influxdb_test.go
Expand Up @@ -15,6 +15,7 @@ import (
dbplugin "github.com/hashicorp/vault/sdk/database/dbplugin/v5"
dbtesting "github.com/hashicorp/vault/sdk/database/dbplugin/v5/testing"
influx "github.com/influxdata/influxdb/client/v2"
"github.com/stretchr/testify/require"
)

const createUserStatements = `CREATE USER "{{username}}" WITH PASSWORD '{{password}}';GRANT ALL ON "vault" TO "{{username}}";`
Expand Down Expand Up @@ -220,7 +221,7 @@ func makeConfig(rootConfig map[string]interface{}, keyValues ...interface{}) map
return config
}

func TestInfluxdb_CreateUser(t *testing.T) {
func TestInfluxdb_CreateUser_DefaultUsernameTemplate(t *testing.T) {
cleanup, config := prepareInfluxdbTestContainer(t)
defer cleanup()

Expand All @@ -234,8 +235,46 @@ func TestInfluxdb_CreateUser(t *testing.T) {
password := "nuozxby98523u89bdfnkjl"
newUserReq := dbplugin.NewUserRequest{
UsernameConfig: dbplugin.UsernameMetadata{
DisplayName: "test",
RoleName: "test",
DisplayName: "token",
RoleName: "mylongrolenamewithmanycharacters",
},
Statements: dbplugin.Statements{
Commands: []string{createUserStatements},
},
Password: password,
Expiration: time.Now().Add(1 * time.Minute),
}
resp := dbtesting.AssertNewUser(t, db, newUserReq)

if resp.Username == "" {
t.Fatalf("Missing username")
}

assertCredsExist(t, config.URL().String(), resp.Username, password)

require.Regexp(t, `^v_token_mylongrolenamew_[a-z0-9]{20}_[0-9]{10}$`, resp.Username)
}

func TestInfluxdb_CreateUser_CustomUsernameTemplate(t *testing.T) {
cleanup, config := prepareInfluxdbTestContainer(t)
defer cleanup()

db := new()

conf := config.connectionParams()
conf["username_template"] = "{{.DisplayName}}_{{random 10}}"

req := dbplugin.InitializeRequest{
Config: conf,
VerifyConnection: true,
}
dbtesting.AssertInitialize(t, db, req)

password := "nuozxby98523u89bdfnkjl"
newUserReq := dbplugin.NewUserRequest{
UsernameConfig: dbplugin.UsernameMetadata{
DisplayName: "token",
RoleName: "mylongrolenamewithmanycharacters",
},
Statements: dbplugin.Statements{
Commands: []string{createUserStatements},
Expand All @@ -250,6 +289,8 @@ func TestInfluxdb_CreateUser(t *testing.T) {
}

assertCredsExist(t, config.URL().String(), resp.Username, password)

require.Regexp(t, `^token_[a-zA-Z0-9]{10}$`, resp.Username)
}

func TestUpdateUser_expiration(t *testing.T) {
Expand Down

0 comments on commit 5f42e25

Please sign in to comment.