mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-31 18:48:08 +00:00 
			
		
		
		
	Enable root user credential rotation in MongoDB (#8540)
* Enable root user credential rotation in MongoDB This takes its logic from the SetCredentials function with some changes (ex: it's generating a password rather than taking one as a parameter). This will error if the username isn't specified in the config. Since Mongo defaults to unauthorized, this seemed like an easy check to make to prevent strange behaviors when it tries to rotate the "" user.
This commit is contained in:
		| @@ -5,6 +5,7 @@ import ( | ||||
| 	"crypto/tls" | ||||
| 	"crypto/x509" | ||||
| 	"fmt" | ||||
| 	"net/url" | ||||
| 	"reflect" | ||||
| 	"strings" | ||||
| 	"testing" | ||||
| @@ -333,3 +334,130 @@ func appendToCertPool(t *testing.T, pool *x509.CertPool, caPem []byte) *x509.Cer | ||||
| 	} | ||||
| 	return pool | ||||
| } | ||||
|  | ||||
| func TestMongoDB_RotateRootCredentials(t *testing.T) { | ||||
| 	cleanup, connURL := mongodb.PrepareTestContainer(t, "latest") | ||||
| 	defer cleanup() | ||||
|  | ||||
| 	// Test to ensure that we can't rotate the root creds if no username has been specified | ||||
| 	testCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second) | ||||
| 	defer cancel() | ||||
|  | ||||
| 	db := new() | ||||
| 	connDetailsWithoutUsername := map[string]interface{}{ | ||||
| 		"connection_url": connURL, | ||||
| 	} | ||||
| 	_, err := db.Init(testCtx, connDetailsWithoutUsername, true) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("err: %s", err) | ||||
| 	} | ||||
|  | ||||
| 	// Rotate credentials should fail because no username is specified | ||||
| 	cfg, err := db.RotateRootCredentials(testCtx, nil) | ||||
| 	if err == nil { | ||||
| 		t.Fatalf("successfully rotated root credentials when no username was present") | ||||
| 	} | ||||
| 	if !reflect.DeepEqual(cfg, connDetailsWithoutUsername) { | ||||
| 		t.Fatalf("expected connection details: %#v but were %#v", connDetailsWithoutUsername, cfg) | ||||
| 	} | ||||
|  | ||||
| 	db.Close() | ||||
|  | ||||
| 	// Reset the database object with new connection details | ||||
| 	username := "vault-test-admin" | ||||
| 	initialPassword := "myreallysecurepassword" | ||||
|  | ||||
| 	db = new() | ||||
| 	connDetailsWithUsername := map[string]interface{}{ | ||||
| 		"connection_url": connURL, | ||||
| 		"username":       username, | ||||
| 		"password":       initialPassword, | ||||
| 	} | ||||
| 	_, err = db.Init(testCtx, connDetailsWithUsername, true) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("err: %s", err) | ||||
| 	} | ||||
|  | ||||
| 	// Create root user | ||||
| 	createUser(t, connURL, username, initialPassword) | ||||
| 	initialURL := setUserPassOnURL(t, connURL, username, initialPassword) | ||||
|  | ||||
| 	// Ensure the initial root user can connect | ||||
| 	err = assertConnection(testCtx, initialURL) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("%s", err) | ||||
| 	} | ||||
|  | ||||
| 	// Rotate credentials | ||||
| 	newCfg, err := db.RotateRootCredentials(testCtx, nil) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("unexpected err rotating root credentials: %s", err) | ||||
| 	} | ||||
|  | ||||
| 	// Ensure the initial root user can no longer connect | ||||
| 	err = assertConnection(testCtx, initialURL) | ||||
| 	if err == nil { | ||||
| 		t.Fatalf("connection with initial credentials succeeded when it shouldn't have") | ||||
| 	} | ||||
|  | ||||
| 	// Ensure the new password can connect | ||||
| 	newURL := setUserPassOnURL(t, connURL, username, newCfg["password"].(string)) | ||||
| 	err = assertConnection(testCtx, newURL) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("unexpected error pinging client with new credentials: %s", err) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func createUser(t *testing.T, connURL, username, password string) { | ||||
| 	t.Helper() | ||||
|  | ||||
| 	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) | ||||
| 	defer cancel() | ||||
|  | ||||
| 	client, err := createClient(ctx, connURL, nil) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Unable to make initial connection: %s", err) | ||||
| 	} | ||||
|  | ||||
| 	createUserCmd := createUserCommand{ | ||||
| 		Username: username, | ||||
| 		Password: password, | ||||
| 		Roles: []interface{}{ | ||||
| 			"userAdminAnyDatabase", | ||||
| 			"dbAdminAnyDatabase", | ||||
| 			"readWriteAnyDatabase", | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	result := client.Database("admin").RunCommand(ctx, createUserCmd, nil) | ||||
| 	err = result.Err() | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Unable to create admin user: %s", err) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func assertConnection(testCtx context.Context, connURL string) error { | ||||
| 	// Connect as initial root user and ensure the connection is successful | ||||
| 	client, err := createClient(testCtx, connURL, nil) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("unable to create client connection with initial root user: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	err = client.Ping(testCtx, nil) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("failed to ping server with initial root user: %w", err) | ||||
| 	} | ||||
| 	client.Disconnect(testCtx) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func setUserPassOnURL(t *testing.T, connURL, username, password string) string { | ||||
| 	t.Helper() | ||||
| 	uri, err := url.Parse(connURL) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("unable to parse connection URL: %s", err) | ||||
| 	} | ||||
|  | ||||
| 	uri.User = url.UserPassword(username, password) | ||||
| 	return uri.String() | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Michael Golowka
					Michael Golowka