mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-31 02:28:09 +00:00 
			
		
		
		
	DBPW - Update Cassandra to adhere to v5 Database interface (#10051)
This commit is contained in:
		| @@ -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}};` | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Michael Golowka
					Michael Golowka