// 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;`