open-vault/plugins/database/cassandra/cassandra_test.go
Michael Golowka 1888323243
DBPW - Copy newdbplugin package to dbplugin/v5 (#10151)
This is part 1 of 4 for renaming the `newdbplugin` package. This copies the existing package to the new location but keeps the current one in place so we can migrate the existing references over more easily.
2020-10-15 13:20:12 -06:00

221 lines
5.8 KiB
Go

package cassandra
import (
"reflect"
"regexp"
"strings"
"testing"
"time"
backoff "github.com/cenkalti/backoff/v3"
"github.com/gocql/gocql"
"github.com/hashicorp/vault/helper/testhelpers/cassandra"
dbplugin "github.com/hashicorp/vault/sdk/database/dbplugin/v5"
dbtesting "github.com/hashicorp/vault/sdk/database/dbplugin/v5/testing"
)
func getCassandra(t *testing.T, protocolVersion interface{}) (*Cassandra, func()) {
cleanup, connURL := cassandra.PrepareTestContainer(t, "latest")
pieces := strings.Split(connURL, ":")
db := new()
initReq := dbplugin.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",
"password": "cassandra",
"protocol_version": protocolVersion,
"connect_timeout": "20s",
}
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 {
t.Fatal("Database should be initialized")
}
return db, cleanup
}
func TestCassandra_Initialize(t *testing.T) {
db, cleanup := getCassandra(t, 4)
defer cleanup()
err := db.Close()
if err != nil {
t.Fatalf("err: %s", err)
}
db, cleanup = getCassandra(t, "4")
defer cleanup()
}
func TestCassandra_CreateUser(t *testing.T) {
db, cleanup := getCassandra(t, 4)
defer cleanup()
password := "myreallysecurepassword"
createReq := dbplugin.NewUserRequest{
UsernameConfig: dbplugin.UsernameMetadata{
DisplayName: "test",
RoleName: "test",
},
Statements: dbplugin.Statements{
Commands: []string{createUserStatements},
},
Password: password,
Expiration: time.Now().Add(1 * time.Minute),
}
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)
}
assertCreds(t, db.Hosts, db.Port, createResp.Username, password, 5*time.Second)
}
func TestMyCassandra_UpdateUserPassword(t *testing.T) {
db, cleanup := getCassandra(t, 4)
defer cleanup()
password := "myreallysecurepassword"
createReq := dbplugin.NewUserRequest{
UsernameConfig: dbplugin.UsernameMetadata{
DisplayName: "test",
RoleName: "test",
},
Statements: dbplugin.Statements{
Commands: []string{createUserStatements},
},
Password: password,
Expiration: time.Now().Add(1 * time.Minute),
}
createResp := dbtesting.AssertNewUser(t, db, createReq)
assertCreds(t, db.Hosts, db.Port, createResp.Username, password, 5*time.Second)
newPassword := "somenewpassword"
updateReq := dbplugin.UpdateUserRequest{
Username: createResp.Username,
Password: &dbplugin.ChangePassword{
NewPassword: newPassword,
Statements: dbplugin.Statements{},
},
Expiration: nil,
}
dbtesting.AssertUpdateUser(t, db, updateReq)
assertCreds(t, db.Hosts, db.Port, createResp.Username, newPassword, 5*time.Second)
}
func TestCassandra_DeleteUser(t *testing.T) {
db, cleanup := getCassandra(t, 4)
defer cleanup()
password := "myreallysecurepassword"
createReq := dbplugin.NewUserRequest{
UsernameConfig: dbplugin.UsernameMetadata{
DisplayName: "test",
RoleName: "test",
},
Statements: dbplugin.Statements{
Commands: []string{createUserStatements},
},
Password: password,
Expiration: time.Now().Add(1 * time.Minute),
}
createResp := dbtesting.AssertNewUser(t, db, createReq)
assertCreds(t, db.Hosts, db.Port, createResp.Username, password, 5*time.Second)
deleteReq := dbplugin.DeleteUserRequest{
Username: createResp.Username,
}
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("failed to connect after %s: %s", timeout, err)
}
}
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,
Password: password,
}
clusterConfig.ProtoVersion = 4
clusterConfig.Port = port
session, err := clusterConfig.CreateSession()
if err != nil {
return err
}
defer session.Close()
return nil
}
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}};`