From 5aaf0f24c65599e379b2fdf8f7c3cf319a5a03fe Mon Sep 17 00:00:00 2001 From: Zlaticanin Date: Mon, 8 Aug 2022 14:03:11 -0500 Subject: [PATCH 1/4] implement username customization feature --- plugins/database/hana/hana.go | 36 ++++++-- plugins/database/hana/hana_test.go | 92 +++++++++++++++++++ .../api-docs/secret/databases/hanadb.mdx | 2 + 3 files changed, 120 insertions(+), 10 deletions(-) diff --git a/plugins/database/hana/hana.go b/plugins/database/hana/hana.go index 51ac9784e268..bca437c369a6 100644 --- a/plugins/database/hana/hana.go +++ b/plugins/database/hana/hana.go @@ -10,19 +10,22 @@ import ( "github.com/hashicorp/go-secure-stdlib/strutil" "github.com/hashicorp/vault/sdk/database/dbplugin/v5" "github.com/hashicorp/vault/sdk/database/helper/connutil" - "github.com/hashicorp/vault/sdk/database/helper/credsutil" "github.com/hashicorp/vault/sdk/database/helper/dbutil" "github.com/hashicorp/vault/sdk/helper/dbtxn" + "github.com/hashicorp/vault/sdk/helper/template" ) const ( - hanaTypeName = "hdb" - maxIdentifierLength = 127 + hanaTypeName = "hdb" + + defaultUserNameTemplate = `{{ printf "v_%s_%s_%s_%s" (.DisplayName | truncate 32) (.RoleName | truncate 20) (random 20) (unix_time) | truncate 127 | replace "-" "_" | uppercase }}` ) // HANA is an implementation of Database interface type HANA struct { *connutil.SQLConnectionProducer + + usernameProducer template.StringTemplate } var _ dbplugin.Database = (*HANA)(nil) @@ -57,6 +60,25 @@ func (h *HANA) Initialize(ctx context.Context, req dbplugin.InitializeRequest) ( return dbplugin.InitializeResponse{}, fmt.Errorf("error initializing db: %w", err) } + 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) + } + h.usernameProducer = up + + _, err = h.usernameProducer.Generate(dbplugin.UsernameMetadata{}) + if err != nil { + return dbplugin.InitializeResponse{}, fmt.Errorf("invalid username template: %w", err) + } + return dbplugin.InitializeResponse{ Config: conf, }, nil @@ -94,13 +116,7 @@ func (h *HANA) NewUser(ctx context.Context, req dbplugin.NewUserRequest) (respon } // Generate username - username, err := credsutil.GenerateUsername( - credsutil.DisplayName(req.UsernameConfig.DisplayName, 32), - credsutil.RoleName(req.UsernameConfig.RoleName, 20), - credsutil.MaxLength(maxIdentifierLength), - credsutil.Separator("_"), - credsutil.ToUpper(), - ) + username, err := h.usernameProducer.Generate(req.UsernameConfig) if err != nil { return dbplugin.NewUserResponse{}, err } diff --git a/plugins/database/hana/hana_test.go b/plugins/database/hana/hana_test.go index 6a4de72c2194..67c108883489 100644 --- a/plugins/database/hana/hana_test.go +++ b/plugins/database/hana/hana_test.go @@ -12,6 +12,7 @@ import ( "github.com/hashicorp/vault/sdk/database/dbplugin/v5" dbtesting "github.com/hashicorp/vault/sdk/database/dbplugin/v5/testing" + "github.com/stretchr/testify/require" ) func TestHANA_Initialize(t *testing.T) { @@ -288,6 +289,97 @@ func copyConfig(config map[string]interface{}) map[string]interface{} { return newConfig } +func TestHANA_DefaultUsernameTemplate(t *testing.T) { + if os.Getenv("HANA_URL") == "" || os.Getenv("VAULT_ACC") != "1" { + t.SkipNow() + } + connURL := os.Getenv("HANA_URL") + + connectionDetails := map[string]interface{}{ + "connection_url": connURL, + } + + initReq := dbplugin.InitializeRequest{ + Config: connectionDetails, + VerifyConnection: true, + } + + db := new() + dbtesting.AssertInitialize(t, db, initReq) + + usernameConfig := dbplugin.UsernameMetadata{ + DisplayName: "test", + RoleName: "test", + } + + const password = "SuperSecurePa55w0rd!" + resp := dbtesting.AssertNewUser(t, db, dbplugin.NewUserRequest{ + UsernameConfig: usernameConfig, + Password: password, + Statements: dbplugin.Statements{ + Commands: []string{testHANARole}, + }, + Expiration: time.Now().Add(5 * time.Minute), + }) + username := resp.Username + + if resp.Username == "" { + t.Fatalf("Missing username") + } + + testCredsExist(t, connURL, username, password) + + require.Regexp(t, `^V_TEST_TEST_[A-Z0-9]{20}_[0-9]{10}$`, resp.Username) + + defer dbtesting.AssertClose(t, db) +} + +func TestHANA_CustomUsernameTemplate(t *testing.T) { + if os.Getenv("HANA_URL") == "" || os.Getenv("VAULT_ACC") != "1" { + t.SkipNow() + } + connURL := os.Getenv("HANA_URL") + + connectionDetails := map[string]interface{}{ + "connection_url": connURL, + "username_template": "{{.DisplayName}}_{{random 10}}", + } + + initReq := dbplugin.InitializeRequest{ + Config: connectionDetails, + VerifyConnection: true, + } + + db := new() + dbtesting.AssertInitialize(t, db, initReq) + + usernameConfig := dbplugin.UsernameMetadata{ + DisplayName: "test", + RoleName: "test", + } + + const password = "SuperSecurePa55w0rd!" + resp := dbtesting.AssertNewUser(t, db, dbplugin.NewUserRequest{ + UsernameConfig: usernameConfig, + Password: password, + Statements: dbplugin.Statements{ + Commands: []string{testHANARole}, + }, + Expiration: time.Now().Add(5 * time.Minute), + }) + username := resp.Username + + if resp.Username == "" { + t.Fatalf("Missing username") + } + + testCredsExist(t, connURL, username, password) + + require.Regexp(t, `^TEST_[A-Z0-9]{10}$`, resp.Username) + + defer dbtesting.AssertClose(t, db) +} + const testHANARole = ` CREATE USER {{name}} PASSWORD "{{password}}" NO FORCE_FIRST_PASSWORD_CHANGE VALID UNTIL '{{expiration}}';` diff --git a/website/content/api-docs/secret/databases/hanadb.mdx b/website/content/api-docs/secret/databases/hanadb.mdx index d6764b92c9a8..031a30a0aa3f 100644 --- a/website/content/api-docs/secret/databases/hanadb.mdx +++ b/website/content/api-docs/secret/databases/hanadb.mdx @@ -44,6 +44,8 @@ has a number of parameters to further configure a connection. - `password` `(string: "")` - The root credential password used in the connection URL. +- `username_template` `(string)` - [Template](/docs/concepts/username-templating) describing how dynamic usernames are generated. + - `disable_escaping` `(boolean: false)` - Turns off the escaping of special characters inside of the username and password fields. See the [databases secrets engine docs](/docs/secrets/databases#disable-character-escaping) for more information. Defaults to `false`. From 5f13a3e0ea1cfeb27de36a17b93f5923fc78f319 Mon Sep 17 00:00:00 2001 From: Zlaticanin Date: Mon, 8 Aug 2022 14:06:56 -0500 Subject: [PATCH 2/4] adding changelog --- changelog/16631.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 changelog/16631.txt diff --git a/changelog/16631.txt b/changelog/16631.txt new file mode 100644 index 000000000000..73c46f65569a --- /dev/null +++ b/changelog/16631.txt @@ -0,0 +1,3 @@ +```release-note:feature +secrets/database/hana: Add ability to customize dynamic usernames +``` \ No newline at end of file From f641988f33c0ac20c28395a68bb6921e0d143106 Mon Sep 17 00:00:00 2001 From: Zlaticanin Date: Mon, 8 Aug 2022 14:43:56 -0500 Subject: [PATCH 3/4] update database capabilities doc --- website/content/docs/secrets/databases/index.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/content/docs/secrets/databases/index.mdx b/website/content/docs/secrets/databases/index.mdx index 2b2fbc2ab4d1..798e0814c7f3 100644 --- a/website/content/docs/secrets/databases/index.mdx +++ b/website/content/docs/secrets/databases/index.mdx @@ -138,7 +138,7 @@ and private key pair to authenticate. | [Cassandra](/docs/secrets/databases/cassandra) | Yes | Yes | Yes (1.6+) | Yes (1.7+) | password | | [Couchbase](/docs/secrets/databases/couchbase) | Yes | Yes | Yes | Yes (1.7+) | password | | [Elasticsearch](/docs/secrets/databases/elasticdb) | Yes | Yes | Yes (1.6+) | Yes (1.8+) | password | -| [HanaDB](/docs/secrets/databases/hanadb) | Yes (1.6+) | Yes | Yes (1.6+) | No | password | +| [HanaDB](/docs/secrets/databases/hanadb) | Yes (1.6+) | Yes | Yes (1.6+) | Yes (1.8+) | password | | [InfluxDB](/docs/secrets/databases/influxdb) | Yes | Yes | Yes (1.6+) | Yes (1.8+) | password | | [MongoDB](/docs/secrets/databases/mongodb) | Yes | Yes | Yes | Yes (1.7+) | password | | [MongoDB Atlas](/docs/secrets/databases/mongodbatlas) | No | Yes | Yes | Yes (1.8+) | password | From ec0c6dff4b57af10d0ee473d16d1ea1479cb0f59 Mon Sep 17 00:00:00 2001 From: Zlaticanin Date: Mon, 8 Aug 2022 15:33:44 -0500 Subject: [PATCH 4/4] update database capabilities doc --- website/content/docs/secrets/databases/index.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/content/docs/secrets/databases/index.mdx b/website/content/docs/secrets/databases/index.mdx index 798e0814c7f3..63eac385f03b 100644 --- a/website/content/docs/secrets/databases/index.mdx +++ b/website/content/docs/secrets/databases/index.mdx @@ -138,7 +138,7 @@ and private key pair to authenticate. | [Cassandra](/docs/secrets/databases/cassandra) | Yes | Yes | Yes (1.6+) | Yes (1.7+) | password | | [Couchbase](/docs/secrets/databases/couchbase) | Yes | Yes | Yes | Yes (1.7+) | password | | [Elasticsearch](/docs/secrets/databases/elasticdb) | Yes | Yes | Yes (1.6+) | Yes (1.8+) | password | -| [HanaDB](/docs/secrets/databases/hanadb) | Yes (1.6+) | Yes | Yes (1.6+) | Yes (1.8+) | password | +| [HanaDB](/docs/secrets/databases/hanadb) | Yes (1.6+) | Yes | Yes (1.6+) | Yes (1.12+) | password | | [InfluxDB](/docs/secrets/databases/influxdb) | Yes | Yes | Yes (1.6+) | Yes (1.8+) | password | | [MongoDB](/docs/secrets/databases/mongodb) | Yes | Yes | Yes | Yes (1.7+) | password | | [MongoDB Atlas](/docs/secrets/databases/mongodbatlas) | No | Yes | Yes | Yes (1.8+) | password |