blob: 6a3c1dbe07d4b94e655975e8588f097cdd9f76b5 [file] [log] [blame]
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hana
import (
"context"
"database/sql"
"fmt"
"os"
"reflect"
"strings"
"testing"
"time"
"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) {
if os.Getenv("HANA_URL") == "" || os.Getenv("VAULT_ACC") != "1" {
t.SkipNow()
}
connURL := os.Getenv("HANA_URL")
connectionDetails := map[string]interface{}{
"connection_url": connURL,
}
expectedConfig := copyConfig(connectionDetails)
initReq := dbplugin.InitializeRequest{
Config: connectionDetails,
VerifyConnection: true,
}
db := new()
initResp := dbtesting.AssertInitialize(t, db, initReq)
defer dbtesting.AssertClose(t, db)
if !reflect.DeepEqual(initResp.Config, expectedConfig) {
t.Fatalf("Actual config: %#v\nExpected config: %#v", initResp.Config, expectedConfig)
}
}
// this test will leave a lingering user on the system
func TestHANA_NewUser(t *testing.T) {
if os.Getenv("HANA_URL") == "" || os.Getenv("VAULT_ACC") != "1" {
t.SkipNow()
}
connURL := os.Getenv("HANA_URL")
type testCase struct {
commands []string
expectErr bool
assertUser func(t testing.TB, connURL, username, password string)
}
tests := map[string]testCase{
"no creation statements": {
commands: []string{},
expectErr: true,
assertUser: assertCredsDoNotExist,
},
"with creation statements": {
commands: []string{testHANARole},
expectErr: false,
assertUser: assertCredsExist,
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
connectionDetails := map[string]interface{}{
"connection_url": connURL,
}
initReq := dbplugin.InitializeRequest{
Config: connectionDetails,
VerifyConnection: true,
}
db := new()
dbtesting.AssertInitialize(t, db, initReq)
defer dbtesting.AssertClose(t, db)
req := dbplugin.NewUserRequest{
UsernameConfig: dbplugin.UsernameMetadata{
DisplayName: "test-test",
RoleName: "test-test",
},
Statements: dbplugin.Statements{
Commands: test.commands,
},
Password: "AG4qagho_dsvZ",
Expiration: time.Now().Add(1 * time.Second),
}
createResp, err := db.NewUser(context.Background(), req)
if test.expectErr && err == nil {
t.Fatalf("err expected, received nil")
}
if !test.expectErr && err != nil {
t.Fatalf("no error expected, got: %s", err)
}
test.assertUser(t, connURL, createResp.Username, req.Password)
})
}
}
func TestHANA_UpdateUser(t *testing.T) {
if os.Getenv("HANA_URL") == "" || os.Getenv("VAULT_ACC") != "1" {
t.SkipNow()
}
connURL := os.Getenv("HANA_URL")
type testCase struct {
commands []string
expectErrOnLogin bool
expectedErrMsg string
}
tests := map[string]testCase{
"no update statements": {
commands: []string{},
expectErrOnLogin: true,
expectedErrMsg: "user is forced to change password",
},
"with custom update statements": {
commands: []string{testHANAUpdate},
expectErrOnLogin: false,
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
connectionDetails := map[string]interface{}{
"connection_url": connURL,
}
initReq := dbplugin.InitializeRequest{
Config: connectionDetails,
VerifyConnection: true,
}
db := new()
dbtesting.AssertInitialize(t, db, initReq)
defer dbtesting.AssertClose(t, db)
password := "this_is_Thirty_2_characters_wow_"
newReq := dbplugin.NewUserRequest{
UsernameConfig: dbplugin.UsernameMetadata{
DisplayName: "test-test",
RoleName: "test-test",
},
Password: password,
Statements: dbplugin.Statements{
Commands: []string{testHANARole},
},
Expiration: time.Now().Add(time.Hour),
}
userResp := dbtesting.AssertNewUser(t, db, newReq)
assertCredsExist(t, connURL, userResp.Username, password)
req := dbplugin.UpdateUserRequest{
Username: userResp.Username,
Password: &dbplugin.ChangePassword{
NewPassword: "this_is_ALSO_Thirty_2_characters_",
Statements: dbplugin.Statements{
Commands: test.commands,
},
},
}
dbtesting.AssertUpdateUser(t, db, req)
err := testCredsExist(t, connURL, userResp.Username, req.Password.NewPassword)
if test.expectErrOnLogin {
if err == nil {
t.Fatalf("Able to login with new creds when expecting an issue")
} else if test.expectedErrMsg != "" && !strings.Contains(err.Error(), test.expectedErrMsg) {
t.Fatalf("Expected error message to contain %q, received: %s", test.expectedErrMsg, err)
}
}
if !test.expectErrOnLogin && err != nil {
t.Fatalf("Unable to login: %s", err)
}
})
}
}
func TestHANA_DeleteUser(t *testing.T) {
if os.Getenv("HANA_URL") == "" || os.Getenv("VAULT_ACC") != "1" {
t.SkipNow()
}
connURL := os.Getenv("HANA_URL")
type testCase struct {
commands []string
}
tests := map[string]testCase{
"no update statements": {
commands: []string{},
},
"with custom update statements": {
commands: []string{testHANADrop},
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
connectionDetails := map[string]interface{}{
"connection_url": connURL,
}
initReq := dbplugin.InitializeRequest{
Config: connectionDetails,
VerifyConnection: true,
}
db := new()
dbtesting.AssertInitialize(t, db, initReq)
defer dbtesting.AssertClose(t, db)
password := "this_is_Thirty_2_characters_wow_"
newReq := dbplugin.NewUserRequest{
UsernameConfig: dbplugin.UsernameMetadata{
DisplayName: "test-test",
RoleName: "test-test",
},
Password: password,
Statements: dbplugin.Statements{
Commands: []string{testHANARole},
},
Expiration: time.Now().Add(time.Hour),
}
userResp := dbtesting.AssertNewUser(t, db, newReq)
assertCredsExist(t, connURL, userResp.Username, password)
req := dbplugin.DeleteUserRequest{
Username: userResp.Username,
Statements: dbplugin.Statements{
Commands: test.commands,
},
}
dbtesting.AssertDeleteUser(t, db, req)
assertCredsDoNotExist(t, connURL, userResp.Username, password)
})
}
}
func testCredsExist(t testing.TB, connURL, username, password string) error {
// Log in with the new creds
parts := strings.Split(connURL, "@")
connURL = fmt.Sprintf("hdb://%s:%s@%s", username, password, parts[1])
db, err := sql.Open("hdb", connURL)
if err != nil {
return err
}
defer db.Close()
return db.Ping()
}
func assertCredsExist(t testing.TB, connURL, username, password string) {
t.Helper()
err := testCredsExist(t, connURL, username, password)
if err != nil {
t.Fatalf("Unable to log in as %q: %s", username, err)
}
}
func assertCredsDoNotExist(t testing.TB, connURL, username, password string) {
t.Helper()
err := testCredsExist(t, connURL, username, password)
if err == nil {
t.Fatalf("Able to log in when we should not be able to")
}
}
func copyConfig(config map[string]interface{}) map[string]interface{} {
newConfig := map[string]interface{}{}
for k, v := range config {
newConfig[k] = v
}
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}}';`
const testHANADrop = `
DROP USER {{name}} CASCADE;`
const testHANAUpdate = `
ALTER USER {{name}} PASSWORD "{{password}}" NO FORCE_FIRST_PASSWORD_CHANGE;`