Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hana - Add username customization #16631

Merged
merged 4 commits into from Aug 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions changelog/16631.txt
@@ -0,0 +1,3 @@
```release-note:feature
Zlaticanin marked this conversation as resolved.
Show resolved Hide resolved
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