DBPW - Update Cassandra to adhere to v5 Database interface (#10051)

This commit is contained in:
Michael Golowka
2020-10-12 14:46:17 -06:00
committed by GitHub
parent ee6391b691
commit 1eff3f7daa
3 changed files with 270 additions and 237 deletions

View File

@@ -1,22 +1,38 @@
package cassandra
import (
"context"
"reflect"
"regexp"
"strings"
"testing"
"time"
dbtesting "github.com/hashicorp/vault/sdk/database/newdbplugin/testing"
backoff "github.com/cenkalti/backoff/v3"
"github.com/gocql/gocql"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/vault/helper/testhelpers/cassandra"
"github.com/hashicorp/vault/sdk/database/dbplugin"
"github.com/hashicorp/vault/sdk/database/newdbplugin"
)
func getCassandra(t *testing.T, protocolVersion interface{}) (*Cassandra, func()) {
cleanup, connURL := cassandra.PrepareTestContainer(t, "latest")
pieces := strings.Split(connURL, ":")
connectionDetails := map[string]interface{}{
db := new()
initReq := newdbplugin.InitializeRequest{
Config: map[string]interface{}{
"hosts": connURL,
"port": pieces[1],
"username": "cassandra",
"password": "cassandra",
"protocol_version": protocolVersion,
"connect_timeout": "20s",
},
VerifyConnection: true,
}
expectedConfig := map[string]interface{}{
"hosts": connURL,
"port": pieces[1],
"username": "cassandra",
@@ -25,10 +41,9 @@ func getCassandra(t *testing.T, protocolVersion interface{}) (*Cassandra, func()
"connect_timeout": "20s",
}
db := new()
_, err := db.Init(context.Background(), connectionDetails, true)
if err != nil {
t.Fatalf("err: %s", err)
initResp := dbtesting.AssertInitialize(t, db, initReq)
if !reflect.DeepEqual(initResp.Config, expectedConfig) {
t.Fatalf("Initialize response config actual: %#v\nExpected: %#v", initResp.Config, expectedConfig)
}
if !db.Initialized {
@@ -54,109 +69,115 @@ func TestCassandra_CreateUser(t *testing.T) {
db, cleanup := getCassandra(t, 4)
defer cleanup()
statements := dbplugin.Statements{
Creation: []string{testCassandraRole},
password := "myreallysecurepassword"
createReq := newdbplugin.NewUserRequest{
UsernameConfig: newdbplugin.UsernameMetadata{
DisplayName: "test",
RoleName: "test",
},
Statements: newdbplugin.Statements{
Commands: []string{createUserStatements},
},
Password: password,
Expiration: time.Now().Add(1 * time.Minute),
}
usernameConfig := dbplugin.UsernameConfig{
DisplayName: "test",
RoleName: "test",
createResp := dbtesting.AssertNewUser(t, db, createReq)
expectedRegex := "^v_test_test_[a-zA-Z0-9]{20}_[0-9]{10}$"
re := regexp.MustCompile(expectedRegex)
if !re.MatchString(createResp.Username) {
t.Fatalf("Generated username %q did not match regexp %q", createResp.Username, expectedRegex)
}
username, password, err := db.CreateUser(context.Background(), statements, usernameConfig, time.Now().Add(time.Minute))
if err != nil {
t.Fatalf("err: %s", err)
}
if err := testCredsExist(db.Hosts, db.Port, username, password); err != nil {
t.Fatalf("Could not connect with new credentials: %s", err)
}
assertCreds(t, db.Hosts, db.Port, createResp.Username, password, 5*time.Second)
}
func TestMyCassandra_RenewUser(t *testing.T) {
func TestMyCassandra_UpdateUserPassword(t *testing.T) {
db, cleanup := getCassandra(t, 4)
defer cleanup()
statements := dbplugin.Statements{
Creation: []string{testCassandraRole},
password := "myreallysecurepassword"
createReq := newdbplugin.NewUserRequest{
UsernameConfig: newdbplugin.UsernameMetadata{
DisplayName: "test",
RoleName: "test",
},
Statements: newdbplugin.Statements{
Commands: []string{createUserStatements},
},
Password: password,
Expiration: time.Now().Add(1 * time.Minute),
}
usernameConfig := dbplugin.UsernameConfig{
DisplayName: "test",
RoleName: "test",
createResp := dbtesting.AssertNewUser(t, db, createReq)
assertCreds(t, db.Hosts, db.Port, createResp.Username, password, 5*time.Second)
newPassword := "somenewpassword"
updateReq := newdbplugin.UpdateUserRequest{
Username: createResp.Username,
Password: &newdbplugin.ChangePassword{
NewPassword: newPassword,
Statements: newdbplugin.Statements{},
},
Expiration: nil,
}
username, password, err := db.CreateUser(context.Background(), statements, usernameConfig, time.Now().Add(time.Minute))
if err != nil {
t.Fatalf("err: %s", err)
}
dbtesting.AssertUpdateUser(t, db, updateReq)
if err := testCredsExist(db.Hosts, db.Port, username, password); err != nil {
t.Fatalf("Could not connect with new credentials: %s", err)
}
err = db.RenewUser(context.Background(), statements, username, time.Now().Add(time.Minute))
if err != nil {
t.Fatalf("err: %s", err)
}
assertCreds(t, db.Hosts, db.Port, createResp.Username, newPassword, 5*time.Second)
}
func TestCassandra_RevokeUser(t *testing.T) {
func TestCassandra_DeleteUser(t *testing.T) {
db, cleanup := getCassandra(t, 4)
defer cleanup()
statements := dbplugin.Statements{
Creation: []string{testCassandraRole},
password := "myreallysecurepassword"
createReq := newdbplugin.NewUserRequest{
UsernameConfig: newdbplugin.UsernameMetadata{
DisplayName: "test",
RoleName: "test",
},
Statements: newdbplugin.Statements{
Commands: []string{createUserStatements},
},
Password: password,
Expiration: time.Now().Add(1 * time.Minute),
}
usernameConfig := dbplugin.UsernameConfig{
DisplayName: "test",
RoleName: "test",
createResp := dbtesting.AssertNewUser(t, db, createReq)
assertCreds(t, db.Hosts, db.Port, createResp.Username, password, 5*time.Second)
deleteReq := newdbplugin.DeleteUserRequest{
Username: createResp.Username,
}
username, password, err := db.CreateUser(context.Background(), statements, usernameConfig, time.Now().Add(time.Minute))
dbtesting.AssertDeleteUser(t, db, deleteReq)
assertNoCreds(t, db.Hosts, db.Port, createResp.Username, password, 5*time.Second)
}
func assertCreds(t testing.TB, address string, port int, username, password string, timeout time.Duration) {
t.Helper()
op := func() error {
return connect(t, address, port, username, password)
}
bo := backoff.NewExponentialBackOff()
bo.MaxElapsedTime = timeout
bo.InitialInterval = 500 * time.Millisecond
bo.MaxInterval = bo.InitialInterval
bo.RandomizationFactor = 0.0
err := backoff.Retry(op, bo)
if err != nil {
t.Fatalf("err: %s", err)
}
if err = testCredsExist(db.Hosts, db.Port, username, password); err != nil {
t.Fatalf("Could not connect with new credentials: %s", err)
}
// Test default revoke statements
err = db.RevokeUser(context.Background(), statements, username)
if err != nil {
t.Fatalf("err: %s", err)
}
if err = testCredsExist(db.Hosts, db.Port, username, password); err == nil {
t.Fatal("Credentials were not revoked")
t.Fatalf("failed to connect after %s: %s", timeout, err)
}
}
func TestCassandra_RotateRootCredentials(t *testing.T) {
db, cleanup := getCassandra(t, 4)
defer cleanup()
if !db.cassandraConnectionProducer.Initialized {
t.Fatal("Database should be initialized")
}
newConf, err := db.RotateRootCredentials(context.Background(), nil)
if err != nil {
t.Fatalf("err: %v", err)
}
if newConf["password"] == "cassandra" {
t.Fatal("password was not updated")
}
err = db.Close()
if err != nil {
t.Fatalf("err: %s", err)
}
}
func testCredsExist(address string, port int, username, password string) error {
func connect(t testing.TB, address string, port int, username, password string) error {
t.Helper()
clusterConfig := gocql.NewCluster(address)
clusterConfig.Authenticator = gocql.PasswordAuthenticator{
Username: username,
@@ -167,11 +188,34 @@ func testCredsExist(address string, port int, username, password string) error {
session, err := clusterConfig.CreateSession()
if err != nil {
return errwrap.Wrapf("error creating session: {{err}}", err)
return err
}
defer session.Close()
return nil
}
const testCassandraRole = `CREATE USER '{{username}}' WITH PASSWORD '{{password}}' NOSUPERUSER;
func assertNoCreds(t testing.TB, address string, port int, username, password string, timeout time.Duration) {
t.Helper()
op := func() error {
// "Invert" the error so the backoff logic sees a failure to connect as a success
err := connect(t, address, port, username, password)
if err != nil {
return nil
}
return err
}
bo := backoff.NewExponentialBackOff()
bo.MaxElapsedTime = timeout
bo.InitialInterval = 500 * time.Millisecond
bo.MaxInterval = bo.InitialInterval
bo.RandomizationFactor = 0.0
err := backoff.Retry(op, bo)
if err != nil {
t.Fatalf("successfully connected after %s when it shouldn't", timeout)
}
}
const createUserStatements = `CREATE USER '{{username}}' WITH PASSWORD '{{password}}' NOSUPERUSER;
GRANT ALL PERMISSIONS ON ALL KEYSPACES TO {{username}};`