package database import ( "context" "log" "strings" "testing" "time" "database/sql" "github.com/hashicorp/vault/helper/namespace" "github.com/hashicorp/vault/helper/testhelpers/mongodb" "github.com/hashicorp/vault/sdk/framework" "github.com/hashicorp/vault/sdk/helper/dbtxn" "github.com/hashicorp/vault/sdk/logical" "github.com/lib/pq" ) const dbUser = "vaultstatictest" const testMongoDBRole = `{ "db": "admin", "roles": [ { "role": "readWrite" } ] }` func TestBackend_StaticRole_Rotate_basic(t *testing.T) { cluster, sys := getCluster(t) defer cluster.Cleanup() config := logical.TestBackendConfig() config.StorageView = &logical.InmemStorage{} config.System = sys lb, err := Factory(context.Background(), config) if err != nil { t.Fatal(err) } b, ok := lb.(*databaseBackend) if !ok { t.Fatal("could not convert to db backend") } defer b.Cleanup(context.Background()) cleanup, connURL := preparePostgresTestContainer(t, config.StorageView, b) defer cleanup() // create the database user createTestPGUser(t, connURL, dbUser, "password", testRoleStaticCreate) verifyPgConn(t, dbUser, "password", connURL) // Configure a connection data := map[string]interface{}{ "connection_url": connURL, "plugin_name": "postgresql-database-plugin", "verify_connection": false, "allowed_roles": []string{"*"}, "name": "plugin-test", } req := &logical.Request{ Operation: logical.UpdateOperation, Path: "config/plugin-test", Storage: config.StorageView, Data: data, } resp, err := b.HandleRequest(namespace.RootContext(nil), req) if err != nil || (resp != nil && resp.IsError()) { t.Fatalf("err:%s resp:%#v\n", err, resp) } data = map[string]interface{}{ "name": "plugin-role-test", "db_name": "plugin-test", "rotation_statements": testRoleStaticUpdate, "username": dbUser, "rotation_period": "5400s", } req = &logical.Request{ Operation: logical.CreateOperation, Path: "static-roles/plugin-role-test", Storage: config.StorageView, Data: data, } resp, err = b.HandleRequest(namespace.RootContext(nil), req) if err != nil || (resp != nil && resp.IsError()) { t.Fatalf("err:%s resp:%#v\n", err, resp) } // Read the creds data = map[string]interface{}{} req = &logical.Request{ Operation: logical.ReadOperation, Path: "static-creds/plugin-role-test", Storage: config.StorageView, Data: data, } resp, err = b.HandleRequest(namespace.RootContext(nil), req) if err != nil || (resp != nil && resp.IsError()) { t.Fatalf("err:%s resp:%#v\n", err, resp) } username := resp.Data["username"].(string) password := resp.Data["password"].(string) if username == "" || password == "" { t.Fatalf("empty username (%s) or password (%s)", username, password) } // Verify username/password verifyPgConn(t, dbUser, password, connURL) // Re-read the creds, verifying they aren't changing on read data = map[string]interface{}{} req = &logical.Request{ Operation: logical.ReadOperation, Path: "static-creds/plugin-role-test", Storage: config.StorageView, Data: data, } resp, err = b.HandleRequest(namespace.RootContext(nil), req) if err != nil || (resp != nil && resp.IsError()) { t.Fatalf("err:%s resp:%#v\n", err, resp) } if username != resp.Data["username"].(string) || password != resp.Data["password"].(string) { t.Fatal("expected re-read username/password to match, but didn't") } // Trigger rotation data = map[string]interface{}{"name": "plugin-role-test"} req = &logical.Request{ Operation: logical.UpdateOperation, Path: "rotate-role/plugin-role-test", Storage: config.StorageView, Data: data, } resp, err = b.HandleRequest(namespace.RootContext(nil), req) if err != nil || (resp != nil && resp.IsError()) { t.Fatalf("err:%s resp:%#v\n", err, resp) } if resp != nil { t.Fatalf("Expected empty response from rotate-role: (%#v)", resp) } // Re-Read the creds data = map[string]interface{}{} req = &logical.Request{ Operation: logical.ReadOperation, Path: "static-creds/plugin-role-test", Storage: config.StorageView, Data: data, } resp, err = b.HandleRequest(namespace.RootContext(nil), req) if err != nil || (resp != nil && resp.IsError()) { t.Fatalf("err:%s resp:%#v\n", err, resp) } newPassword := resp.Data["password"].(string) if password == newPassword { t.Fatalf("expected passwords to differ, got (%s)", newPassword) } // Verify new username/password verifyPgConn(t, username, newPassword, connURL) } // Sanity check to make sure we don't allow an attempt of rotating credentials // for non-static accounts, which doesn't make sense anyway, but doesn't hurt to // verify we return an error func TestBackend_StaticRole_Rotate_NonStaticError(t *testing.T) { cluster, sys := getCluster(t) defer cluster.Cleanup() config := logical.TestBackendConfig() config.StorageView = &logical.InmemStorage{} config.System = sys lb, err := Factory(context.Background(), config) if err != nil { t.Fatal(err) } b, ok := lb.(*databaseBackend) if !ok { t.Fatal("could not convert to db backend") } defer b.Cleanup(context.Background()) cleanup, connURL := preparePostgresTestContainer(t, config.StorageView, b) defer cleanup() // create the database user createTestPGUser(t, connURL, dbUser, "password", testRoleStaticCreate) // Configure a connection data := map[string]interface{}{ "connection_url": connURL, "plugin_name": "postgresql-database-plugin", "verify_connection": false, "allowed_roles": []string{"*"}, "name": "plugin-test", } req := &logical.Request{ Operation: logical.UpdateOperation, Path: "config/plugin-test", Storage: config.StorageView, Data: data, } resp, err := b.HandleRequest(namespace.RootContext(nil), req) if err != nil || (resp != nil && resp.IsError()) { t.Fatalf("err:%s resp:%#v\n", err, resp) } data = map[string]interface{}{ "name": "plugin-role-test", "db_name": "plugin-test", "creation_statements": testRoleStaticCreate, "rotation_statements": testRoleStaticUpdate, "revocation_statements": defaultRevocationSQL, } req = &logical.Request{ Operation: logical.CreateOperation, Path: "roles/plugin-role-test", Storage: config.StorageView, Data: data, } resp, err = b.HandleRequest(namespace.RootContext(nil), req) if err != nil || (resp != nil && resp.IsError()) { t.Fatalf("err:%s resp:%#v\n", err, resp) } // Read the creds data = map[string]interface{}{} req = &logical.Request{ Operation: logical.ReadOperation, Path: "creds/plugin-role-test", Storage: config.StorageView, Data: data, } resp, err = b.HandleRequest(namespace.RootContext(nil), req) if err != nil || (resp != nil && resp.IsError()) { t.Fatalf("err:%s resp:%#v\n", err, resp) } username := resp.Data["username"].(string) password := resp.Data["password"].(string) if username == "" || password == "" { t.Fatalf("empty username (%s) or password (%s)", username, password) } // Verify username/password verifyPgConn(t, dbUser, "password", connURL) // Trigger rotation data = map[string]interface{}{"name": "plugin-role-test"} req = &logical.Request{ Operation: logical.UpdateOperation, Path: "rotate-role/plugin-role-test", Storage: config.StorageView, Data: data, } // expect resp to be an error resp, _ = b.HandleRequest(namespace.RootContext(nil), req) if !resp.IsError() { t.Fatalf("expected error rotating non-static role") } if resp.Error().Error() != "no static role found for role name" { t.Fatalf("wrong error message: %s", err) } } func TestBackend_StaticRole_Revoke_user(t *testing.T) { cluster, sys := getCluster(t) defer cluster.Cleanup() config := logical.TestBackendConfig() config.StorageView = &logical.InmemStorage{} config.System = sys lb, err := Factory(context.Background(), config) if err != nil { t.Fatal(err) } b, ok := lb.(*databaseBackend) if !ok { t.Fatal("could not convert to db backend") } defer b.Cleanup(context.Background()) cleanup, connURL := preparePostgresTestContainer(t, config.StorageView, b) defer cleanup() // create the database user createTestPGUser(t, connURL, dbUser, "password", testRoleStaticCreate) // Configure a connection data := map[string]interface{}{ "connection_url": connURL, "plugin_name": "postgresql-database-plugin", "verify_connection": false, "allowed_roles": []string{"*"}, "name": "plugin-test", } req := &logical.Request{ Operation: logical.UpdateOperation, Path: "config/plugin-test", Storage: config.StorageView, Data: data, } resp, err := b.HandleRequest(namespace.RootContext(nil), req) if err != nil || (resp != nil && resp.IsError()) { t.Fatalf("err:%s resp:%#v\n", err, resp) } testCases := map[string]struct { revoke *bool expectVerifyErr bool }{ // Default case: user does not specify, Vault leaves the database user // untouched, and the final connection check passes because the user still // exists "unset": {}, // Revoke on delete. The final connection check should fail because the user // no longer exists "revoke": { revoke: newBoolPtr(true), expectVerifyErr: true, }, // Revoke false, final connection check should still pass "persist": { revoke: newBoolPtr(false), }, } for k, tc := range testCases { t.Run(k, func(t *testing.T) { data = map[string]interface{}{ "name": "plugin-role-test", "db_name": "plugin-test", "rotation_statements": testRoleStaticUpdate, "username": dbUser, "rotation_period": "5400s", } if tc.revoke != nil { data["revoke_user_on_delete"] = *tc.revoke } req = &logical.Request{ Operation: logical.CreateOperation, Path: "static-roles/plugin-role-test", Storage: config.StorageView, Data: data, } resp, err = b.HandleRequest(namespace.RootContext(nil), req) if err != nil || (resp != nil && resp.IsError()) { t.Fatalf("err:%s resp:%#v\n", err, resp) } // Read the creds data = map[string]interface{}{} req = &logical.Request{ Operation: logical.ReadOperation, Path: "static-creds/plugin-role-test", Storage: config.StorageView, Data: data, } resp, err = b.HandleRequest(namespace.RootContext(nil), req) if err != nil || (resp != nil && resp.IsError()) { t.Fatalf("err:%s resp:%#v\n", err, resp) } username := resp.Data["username"].(string) password := resp.Data["password"].(string) if username == "" || password == "" { t.Fatalf("empty username (%s) or password (%s)", username, password) } // Verify username/password verifyPgConn(t, username, password, connURL) // delete the role, expect the default where the user is not destroyed // Read the creds req = &logical.Request{ Operation: logical.DeleteOperation, Path: "static-roles/plugin-role-test", Storage: config.StorageView, } resp, err = b.HandleRequest(namespace.RootContext(nil), req) if err != nil || (resp != nil && resp.IsError()) { t.Fatalf("err:%s resp:%#v\n", err, resp) } // Verify new username/password still work verifyPgConn(t, username, password, connURL) }) } } func createTestPGUser(t *testing.T, connURL string, username, password, query string) { t.Helper() log.Printf("[TRACE] Creating test user") conn, err := pq.ParseURL(connURL) if err != nil { t.Fatal(err) } db, err := sql.Open("postgres", conn) defer db.Close() if err != nil { t.Fatal(err) } // Start a transaction ctx := context.Background() tx, err := db.BeginTx(ctx, nil) if err != nil { t.Fatal(err) } defer func() { _ = tx.Rollback() }() m := map[string]string{ "name": username, "password": password, } if err := dbtxn.ExecuteTxQuery(ctx, tx, m, query); err != nil { t.Fatal(err) } // Commit the transaction if err := tx.Commit(); err != nil { t.Fatal(err) } } func verifyPgConn(t *testing.T, username, password, connURL string) { t.Helper() cURL := strings.Replace(connURL, "postgres:secret", username+":"+password, 1) db, err := sql.Open("postgres", cURL) if err != nil { t.Fatal(err) } if err := db.Ping(); err != nil { t.Fatal(err) } } // WAL testing // // First scenario, WAL contains a role name that does not exist. func TestBackend_Static_QueueWAL_discard_role_not_found(t *testing.T) { cluster, sys := getCluster(t) defer cluster.Cleanup() ctx := context.Background() config := logical.TestBackendConfig() config.StorageView = &logical.InmemStorage{} config.System = sys _, err := framework.PutWAL(ctx, config.StorageView, staticWALKey, &setCredentialsWAL{ RoleName: "doesnotexist", }) if err != nil { t.Fatalf("error with PutWAL: %s", err) } assertWALCount(t, config.StorageView, 1) b, err := Factory(ctx, config) if err != nil { t.Fatal(err) } defer b.Cleanup(ctx) time.Sleep(5 * time.Second) bd := b.(*databaseBackend) if bd.credRotationQueue == nil { t.Fatal("database backend had no credential rotation queue") } // Verify empty queue if bd.credRotationQueue.Len() != 0 { t.Fatalf("expected zero queue items, got: %d", bd.credRotationQueue.Len()) } assertWALCount(t, config.StorageView, 0) } // Second scenario, WAL contains a role name that does exist, but the role's // LastVaultRotation is greater than the WAL has func TestBackend_Static_QueueWAL_discard_role_newer_rotation_date(t *testing.T) { cluster, sys := getCluster(t) defer cluster.Cleanup() ctx := context.Background() config := logical.TestBackendConfig() config.StorageView = &logical.InmemStorage{} config.System = sys roleName := "test-discard-by-date" lb, err := Factory(context.Background(), config) if err != nil { t.Fatal(err) } b, ok := lb.(*databaseBackend) if !ok { t.Fatal("could not convert to db backend") } cleanup, connURL := preparePostgresTestContainer(t, config.StorageView, b) defer cleanup() // create the database user createTestPGUser(t, connURL, dbUser, "password", testRoleStaticCreate) // Configure a connection data := map[string]interface{}{ "connection_url": connURL, "plugin_name": "postgresql-database-plugin", "verify_connection": false, "allowed_roles": []string{"*"}, "name": "plugin-test", } req := &logical.Request{ Operation: logical.UpdateOperation, Path: "config/plugin-test", Storage: config.StorageView, Data: data, } resp, err := b.HandleRequest(namespace.RootContext(nil), req) if err != nil || (resp != nil && resp.IsError()) { t.Fatalf("err:%s resp:%#v\n", err, resp) } // Save Now() to make sure rotation time is after this, as well as the WAL // time roleTime := time.Now() // Create role data = map[string]interface{}{ "name": roleName, "db_name": "plugin-test", "rotation_statements": testRoleStaticUpdate, "username": dbUser, // Low value here, to make sure the backend rotates this password at least // once before we compare it to the WAL "rotation_period": "10s", } req = &logical.Request{ Operation: logical.CreateOperation, Path: "static-roles/" + roleName, Storage: config.StorageView, Data: data, } resp, err = b.HandleRequest(namespace.RootContext(nil), req) if err != nil || (resp != nil && resp.IsError()) { t.Fatalf("err:%s resp:%#v\n", err, resp) } // Allow the first rotation to occur, setting LastVaultRotation time.Sleep(time.Second * 12) // Cleanup the backend, then create a WAL for the role with a // LastVaultRotation of 1 hour ago, so that when we recreate the backend the // WAL will be read but discarded b.Cleanup(ctx) b = nil time.Sleep(time.Second * 3) // Make a fake WAL entry with an older time oldRotationTime := roleTime.Add(time.Hour * -1) walPassword := "somejunkpassword" _, err = framework.PutWAL(ctx, config.StorageView, staticWALKey, &setCredentialsWAL{ RoleName: roleName, NewPassword: walPassword, LastVaultRotation: oldRotationTime, Username: dbUser, }) if err != nil { t.Fatalf("error with PutWAL: %s", err) } assertWALCount(t, config.StorageView, 1) // Reload backend lb, err = Factory(context.Background(), config) if err != nil { t.Fatal(err) } b, ok = lb.(*databaseBackend) if !ok { t.Fatal("could not convert to db backend") } defer b.Cleanup(ctx) // Allow enough time for populateQueue to work after boot time.Sleep(time.Second * 12) // PopulateQueue should have processed the entry assertWALCount(t, config.StorageView, 0) // Read the role data = map[string]interface{}{} req = &logical.Request{ Operation: logical.ReadOperation, Path: "static-roles/" + roleName, Storage: config.StorageView, Data: data, } resp, err = b.HandleRequest(namespace.RootContext(nil), req) if err != nil || (resp != nil && resp.IsError()) { t.Fatalf("err:%s resp:%#v\n", err, resp) } lastVaultRotation := resp.Data["last_vault_rotation"].(time.Time) if !lastVaultRotation.After(oldRotationTime) { t.Fatal("last vault rotation time not greater than WAL time") } if !lastVaultRotation.After(roleTime) { t.Fatal("last vault rotation time not greater than role creation time") } // Grab password to verify it didn't change req = &logical.Request{ Operation: logical.ReadOperation, Path: "static-creds/" + roleName, Storage: config.StorageView, } resp, err = b.HandleRequest(namespace.RootContext(nil), req) if err != nil || (resp != nil && resp.IsError()) { t.Fatalf("err:%s resp:%#v\n", err, resp) } password := resp.Data["password"].(string) if password == walPassword { t.Fatalf("expected password to not be changed by WAL, but was") } } // Helper to assert the number of WAL entries is what we expect func assertWALCount(t *testing.T, s logical.Storage, expected int) { var count int ctx := context.Background() keys, err := framework.ListWAL(ctx, s) if err != nil { t.Fatal("error listing WALs") } // Loop through WAL keys and process any rotation ones for _, k := range keys { walEntry, _ := framework.GetWAL(ctx, s, k) if walEntry == nil { continue } if walEntry.Kind != staticWALKey { continue } count++ } if expected != count { t.Fatalf("WAL count mismatch, expected (%d), got (%d)", expected, count) } } // // End WAL testing // func TestBackend_StaticRole_Rotations_PostgreSQL(t *testing.T) { cluster, sys := getCluster(t) defer cluster.Cleanup() config := logical.TestBackendConfig() config.StorageView = &logical.InmemStorage{} config.System = sys b, err := Factory(context.Background(), config) if err != nil { t.Fatal(err) } defer b.Cleanup(context.Background()) bd := b.(*databaseBackend) if bd.credRotationQueue == nil { t.Fatal("database backend had no credential rotation queue") } // Configure backend, add item and confirm length cleanup, connURL := preparePostgresTestContainer(t, config.StorageView, b) defer cleanup() testCases := []string{"65", "130", "5400"} // Create database users ahead for _, tc := range testCases { createTestPGUser(t, connURL, dbUser+tc, "password", testRoleStaticCreate) } // Configure a connection data := map[string]interface{}{ "connection_url": connURL, "plugin_name": "postgresql-database-plugin", "verify_connection": false, "allowed_roles": []string{"*"}, "name": "plugin-test", } req := &logical.Request{ Operation: logical.UpdateOperation, Path: "config/plugin-test", Storage: config.StorageView, Data: data, } resp, err := b.HandleRequest(namespace.RootContext(nil), req) if err != nil || (resp != nil && resp.IsError()) { t.Fatalf("err:%s resp:%#v\n", err, resp) } // Create three static roles with different rotation periods for _, tc := range testCases { roleName := "plugin-static-role-" + tc data = map[string]interface{}{ "name": roleName, "db_name": "plugin-test", "rotation_statements": testRoleStaticUpdate, "username": dbUser + tc, "rotation_period": tc, } req = &logical.Request{ Operation: logical.CreateOperation, Path: "static-roles/" + roleName, Storage: config.StorageView, Data: data, } resp, err = b.HandleRequest(namespace.RootContext(nil), req) if err != nil || (resp != nil && resp.IsError()) { t.Fatalf("err:%s resp:%#v\n", err, resp) } } // Verify the queue has 3 items in it if bd.credRotationQueue.Len() != 3 { t.Fatalf("expected 3 items in the rotation queue, got: (%d)", bd.credRotationQueue.Len()) } // List the roles data = map[string]interface{}{} req = &logical.Request{ Operation: logical.ListOperation, Path: "static-roles/", Storage: config.StorageView, Data: data, } resp, err = b.HandleRequest(namespace.RootContext(nil), req) if err != nil || (resp != nil && resp.IsError()) { t.Fatalf("err:%s resp:%#v\n", err, resp) } keys := resp.Data["keys"].([]string) if len(keys) != 3 { t.Fatalf("expected 3 roles, got: (%d)", len(keys)) } // Capture initial passwords, before the periodic function is triggered pws := make(map[string][]string, 0) pws = capturePasswords(t, b, config, testCases, pws) // Sleep to make sure the 65s role will be up for rotation by the time the // periodic function ticks time.Sleep(7 * time.Second) // Sleep 75 to make sure the periodic func has time to actually run time.Sleep(75 * time.Second) pws = capturePasswords(t, b, config, testCases, pws) // Sleep more, this should allow both sr65 and sr130 to rotate time.Sleep(140 * time.Second) pws = capturePasswords(t, b, config, testCases, pws) // Verify all pws are as they should pass := true for k, v := range pws { switch { case k == "plugin-static-role-65": // expect all passwords to be different if v[0] == v[1] || v[1] == v[2] || v[0] == v[2] { pass = false } case k == "plugin-static-role-130": // expect the first two to be equal, but different from the third if v[0] != v[1] || v[0] == v[2] { pass = false } case k == "plugin-static-role-5400": // expect all passwords to be equal if v[0] != v[1] || v[1] != v[2] { pass = false } } } if !pass { t.Fatalf("password rotations did not match expected: %#v", pws) } } func TestBackend_StaticRole_Rotations_MongoDB(t *testing.T) { cluster, sys := getCluster(t) defer cluster.Cleanup() config := logical.TestBackendConfig() config.StorageView = &logical.InmemStorage{} config.System = sys b, err := Factory(context.Background(), config) if err != nil { t.Fatal(err) } defer b.Cleanup(context.Background()) // allow initQueue to finish bd := b.(*databaseBackend) if bd.credRotationQueue == nil { t.Fatal("database backend had no credential rotation queue") } // configure backend, add item and confirm length cleanup, connURL := mongodb.PrepareTestContainerWithDatabase(t, "latest", "vaulttestdb") defer cleanup() // Configure a connection data := map[string]interface{}{ "connection_url": connURL, "plugin_name": "mongodb-database-plugin", "verify_connection": false, "allowed_roles": []string{"*"}, "name": "plugin-mongo-test", } req := &logical.Request{ Operation: logical.UpdateOperation, Path: "config/plugin-mongo-test", Storage: config.StorageView, Data: data, } resp, err := b.HandleRequest(namespace.RootContext(nil), req) if err != nil || (resp != nil && resp.IsError()) { t.Fatalf("err:%s resp:%#v\n", err, resp) } // create three static roles with different rotation periods testCases := []string{"65", "130", "5400"} for _, tc := range testCases { roleName := "plugin-static-role-" + tc data = map[string]interface{}{ "name": roleName, "db_name": "plugin-mongo-test", "username": "statictestMongo" + tc, "rotation_period": tc, } req = &logical.Request{ Operation: logical.CreateOperation, Path: "static-roles/" + roleName, Storage: config.StorageView, Data: data, } resp, err = b.HandleRequest(namespace.RootContext(nil), req) if err != nil || (resp != nil && resp.IsError()) { t.Fatalf("err:%s resp:%#v\n", err, resp) } } // verify the queue has 3 items in it if bd.credRotationQueue.Len() != 3 { t.Fatalf("expected 3 items in the rotation queue, got: (%d)", bd.credRotationQueue.Len()) } // List the roles data = map[string]interface{}{} req = &logical.Request{ Operation: logical.ListOperation, Path: "static-roles/", Storage: config.StorageView, Data: data, } resp, err = b.HandleRequest(namespace.RootContext(nil), req) if err != nil || (resp != nil && resp.IsError()) { t.Fatalf("err:%s resp:%#v\n", err, resp) } keys := resp.Data["keys"].([]string) if len(keys) != 3 { t.Fatalf("expected 3 roles, got: (%d)", len(keys)) } // capture initial passwords, before the periodic function is triggered pws := make(map[string][]string, 0) pws = capturePasswords(t, b, config, testCases, pws) // sleep to make sure the 65s role will be up for rotation by the time the // periodic function ticks time.Sleep(7 * time.Second) // sleep 75 to make sure the periodic func has time to actually run time.Sleep(75 * time.Second) pws = capturePasswords(t, b, config, testCases, pws) // sleep more, this should allow both sr65 and sr130 to rotate time.Sleep(140 * time.Second) pws = capturePasswords(t, b, config, testCases, pws) // verify all pws are as they should pass := true for k, v := range pws { if len(v) < 3 { t.Fatalf("expected to find 3 passwords for (%s), only found (%d)", k, len(v)) } switch { case k == "plugin-static-role-65": // expect all passwords to be different if v[0] == v[1] || v[1] == v[2] || v[0] == v[2] { pass = false } case k == "plugin-static-role-130": // expect the first two to be equal, but different from the third if v[0] != v[1] || v[0] == v[2] { pass = false } case k == "plugin-static-role-5400": // expect all passwords to be equal if v[0] != v[1] || v[1] != v[2] { pass = false } } } if !pass { t.Fatalf("password rotations did not match expected: %#v", pws) } } // capturePasswords captures the current passwords at the time of calling, and // returns a map of username / passwords building off of the input map func capturePasswords(t *testing.T, b logical.Backend, config *logical.BackendConfig, testCases []string, pws map[string][]string) map[string][]string { new := make(map[string][]string, 0) for _, tc := range testCases { // Read the role roleName := "plugin-static-role-" + tc req := &logical.Request{ Operation: logical.ReadOperation, Path: "static-creds/" + roleName, Storage: config.StorageView, } resp, err := b.HandleRequest(namespace.RootContext(nil), req) if err != nil || (resp != nil && resp.IsError()) { t.Fatalf("err:%s resp:%#v\n", err, resp) } username := resp.Data["username"].(string) password := resp.Data["password"].(string) if username == "" || password == "" { t.Fatalf("expected both username/password for (%s), got (%s), (%s)", roleName, username, password) } new[roleName] = append(new[roleName], password) } for k, v := range new { pws[k] = append(pws[k], v...) } return pws } func newBoolPtr(b bool) *bool { v := b return &v }