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:
Michael Golowka
2020-05-15 11:24:10 -06:00
committed by GitHub
parent 1a1d3a1b9e
commit 2190cccfa3
3 changed files with 174 additions and 14 deletions

View File

@@ -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()
}