Skip to content

Commit

Permalink
Hana - Add username customization (#16631)
Browse files Browse the repository at this point in the history
* implement username customization feature

* adding changelog

* update database capabilities doc

* update database capabilities doc

Co-authored-by: Zlaticanin <milena@hashicorp.com>
  • Loading branch information
Zlaticanin and MilenaHC committed Aug 8, 2022
1 parent a736c12 commit e4968ca
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 11 deletions.
3 changes: 3 additions & 0 deletions changelog/16631.txt
@@ -0,0 +1,3 @@
```release-note:feature
secrets/database/hana: Add ability to customize dynamic usernames
```
36 changes: 26 additions & 10 deletions plugins/database/hana/hana.go
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand Down
92 changes: 92 additions & 0 deletions plugins/database/hana/hana_test.go
Expand Up @@ -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) {
Expand Down Expand Up @@ -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}}';`

Expand Down
2 changes: 2 additions & 0 deletions website/content/api-docs/secret/databases/hanadb.mdx
Expand Up @@ -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`.
Expand Down
2 changes: 1 addition & 1 deletion website/content/docs/secrets/databases/index.mdx
Expand Up @@ -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.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 |
Expand Down

0 comments on commit e4968ca

Please sign in to comment.