mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-30 18:17:55 +00:00 
			
		
		
		
	 2a1753a469
			
		
	
	2a1753a469
	
	
	
		
			
			* Fix tests - Update MongoDB driver * increase timeout and disconnect client after ping * increase timeout * disconnect client after the ping
		
			
				
	
	
		
			312 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			312 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package mongodb
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"fmt"
 | |
| 	"io/ioutil"
 | |
| 	"net/url"
 | |
| 	"os"
 | |
| 	paths "path"
 | |
| 	"path/filepath"
 | |
| 	"reflect"
 | |
| 	"sort"
 | |
| 	"testing"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/hashicorp/vault/helper/testhelpers/certhelpers"
 | |
| 	dbplugin "github.com/hashicorp/vault/sdk/database/dbplugin/v5"
 | |
| 	"github.com/ory/dockertest"
 | |
| 	"go.mongodb.org/mongo-driver/mongo"
 | |
| 	"go.mongodb.org/mongo-driver/mongo/options"
 | |
| 	"go.mongodb.org/mongo-driver/mongo/readpref"
 | |
| )
 | |
| 
 | |
| func TestInit_clientTLS(t *testing.T) {
 | |
| 	t.Skip("Skipping this test because CircleCI can't mount the files we need without further investigation: " +
 | |
| 		"https://support.circleci.com/hc/en-us/articles/360007324514-How-can-I-mount-volumes-to-docker-containers-")
 | |
| 
 | |
| 	// Set up temp directory so we can mount it to the docker container
 | |
| 	confDir := makeTempDir(t)
 | |
| 	defer os.RemoveAll(confDir)
 | |
| 
 | |
| 	// Create certificates for Mongo authentication
 | |
| 	caCert := certhelpers.NewCert(t,
 | |
| 		certhelpers.CommonName("test certificate authority"),
 | |
| 		certhelpers.IsCA(true),
 | |
| 		certhelpers.SelfSign(),
 | |
| 	)
 | |
| 	serverCert := certhelpers.NewCert(t,
 | |
| 		certhelpers.CommonName("server"),
 | |
| 		certhelpers.DNS("localhost"),
 | |
| 		certhelpers.Parent(caCert),
 | |
| 	)
 | |
| 	clientCert := certhelpers.NewCert(t,
 | |
| 		certhelpers.CommonName("client"),
 | |
| 		certhelpers.DNS("client"),
 | |
| 		certhelpers.Parent(caCert),
 | |
| 	)
 | |
| 
 | |
| 	writeFile(t, paths.Join(confDir, "ca.pem"), caCert.CombinedPEM(), 0o644)
 | |
| 	writeFile(t, paths.Join(confDir, "server.pem"), serverCert.CombinedPEM(), 0o644)
 | |
| 	writeFile(t, paths.Join(confDir, "client.pem"), clientCert.CombinedPEM(), 0o644)
 | |
| 
 | |
| 	// //////////////////////////////////////////////////////
 | |
| 	// Set up Mongo config file
 | |
| 	rawConf := `
 | |
| net:
 | |
|    tls:
 | |
|       mode: preferTLS
 | |
|       certificateKeyFile: /etc/mongo/server.pem
 | |
|       CAFile: /etc/mongo/ca.pem
 | |
|       allowInvalidHostnames: true`
 | |
| 
 | |
| 	writeFile(t, paths.Join(confDir, "mongod.conf"), []byte(rawConf), 0o644)
 | |
| 
 | |
| 	// //////////////////////////////////////////////////////
 | |
| 	// Start Mongo container
 | |
| 	retURL, cleanup := startMongoWithTLS(t, "latest", confDir)
 | |
| 	defer cleanup()
 | |
| 
 | |
| 	// //////////////////////////////////////////////////////
 | |
| 	// Set up x509 user
 | |
| 	mClient := connect(t, retURL)
 | |
| 
 | |
| 	setUpX509User(t, mClient, clientCert)
 | |
| 
 | |
| 	// //////////////////////////////////////////////////////
 | |
| 	// Test
 | |
| 	mongo := new()
 | |
| 
 | |
| 	initReq := dbplugin.InitializeRequest{
 | |
| 		Config: map[string]interface{}{
 | |
| 			"connection_url":      retURL,
 | |
| 			"allowed_roles":       "*",
 | |
| 			"tls_certificate_key": clientCert.CombinedPEM(),
 | |
| 			"tls_ca":              caCert.Pem,
 | |
| 		},
 | |
| 		VerifyConnection: true,
 | |
| 	}
 | |
| 
 | |
| 	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
 | |
| 	defer cancel()
 | |
| 
 | |
| 	_, err := mongo.Initialize(ctx, initReq)
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("Unable to initialize mongo engine: %s", err)
 | |
| 	}
 | |
| 
 | |
| 	// Initialization complete. The connection was established, but we need to ensure
 | |
| 	// that we're connected as the right user
 | |
| 	whoamiCmd := map[string]interface{}{
 | |
| 		"connectionStatus": 1,
 | |
| 	}
 | |
| 
 | |
| 	client, err := mongo.Connection(ctx)
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("Unable to make connection to Mongo: %s", err)
 | |
| 	}
 | |
| 	result := client.Database("test").RunCommand(ctx, whoamiCmd)
 | |
| 	if result.Err() != nil {
 | |
| 		t.Fatalf("Unable to connect to Mongo: %s", err)
 | |
| 	}
 | |
| 
 | |
| 	expected := connStatus{
 | |
| 		AuthInfo: authInfo{
 | |
| 			AuthenticatedUsers: []user{
 | |
| 				{
 | |
| 					User: fmt.Sprintf("CN=%s", clientCert.Template.Subject.CommonName),
 | |
| 					DB:   "$external",
 | |
| 				},
 | |
| 			},
 | |
| 			AuthenticatedUserRoles: []role{
 | |
| 				{
 | |
| 					Role: "readWrite",
 | |
| 					DB:   "test",
 | |
| 				},
 | |
| 				{
 | |
| 					Role: "userAdminAnyDatabase",
 | |
| 					DB:   "admin",
 | |
| 				},
 | |
| 			},
 | |
| 		},
 | |
| 		Ok: 1,
 | |
| 	}
 | |
| 	// Sort the AuthenticatedUserRoles because Mongo doesn't return them in the same order every time
 | |
| 	// Thanks Mongo! /tableflip
 | |
| 	sort.Sort(expected.AuthInfo.AuthenticatedUserRoles)
 | |
| 
 | |
| 	actual := connStatus{}
 | |
| 	err = result.Decode(&actual)
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("Unable to decode connection status: %s", err)
 | |
| 	}
 | |
| 
 | |
| 	sort.Sort(actual.AuthInfo.AuthenticatedUserRoles)
 | |
| 
 | |
| 	if !reflect.DeepEqual(actual, expected) {
 | |
| 		t.Fatalf("Actual:%#v\nExpected:\n%#v", actual, expected)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func makeTempDir(t *testing.T) (confDir string) {
 | |
| 	confDir, err := ioutil.TempDir(".", "mongodb-test-data")
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("Unable to make temp directory: %s", err)
 | |
| 	}
 | |
| 	// Convert the directory to an absolute path because docker needs it when mounting
 | |
| 	confDir, err = filepath.Abs(filepath.Clean(confDir))
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("Unable to determine where temp directory is on absolute path: %s", err)
 | |
| 	}
 | |
| 	return confDir
 | |
| }
 | |
| 
 | |
| func startMongoWithTLS(t *testing.T, version string, confDir string) (retURL string, cleanup func()) {
 | |
| 	if os.Getenv("MONGODB_URL") != "" {
 | |
| 		return os.Getenv("MONGODB_URL"), func() {}
 | |
| 	}
 | |
| 
 | |
| 	pool, err := dockertest.NewPool("")
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("Failed to connect to docker: %s", err)
 | |
| 	}
 | |
| 	pool.MaxWait = 30 * time.Second
 | |
| 
 | |
| 	containerName := "mongo-unit-test"
 | |
| 
 | |
| 	// Remove previously running container if it is still running because cleanup failed
 | |
| 	err = pool.RemoveContainerByName(containerName)
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("Unable to remove old running containers: %s", err)
 | |
| 	}
 | |
| 
 | |
| 	runOpts := &dockertest.RunOptions{
 | |
| 		Name:       containerName,
 | |
| 		Repository: "mongo",
 | |
| 		Tag:        version,
 | |
| 		Cmd:        []string{"mongod", "--config", "/etc/mongo/mongod.conf"},
 | |
| 		// Mount the directory from local filesystem into the container
 | |
| 		Mounts: []string{
 | |
| 			fmt.Sprintf("%s:/etc/mongo", confDir),
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	resource, err := pool.RunWithOptions(runOpts)
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("Could not start local mongo docker container: %s", err)
 | |
| 	}
 | |
| 	resource.Expire(30)
 | |
| 
 | |
| 	cleanup = func() {
 | |
| 		err := pool.Purge(resource)
 | |
| 		if err != nil {
 | |
| 			t.Fatalf("Failed to cleanup local container: %s", err)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	uri := url.URL{
 | |
| 		Scheme: "mongodb",
 | |
| 		Host:   fmt.Sprintf("localhost:%s", resource.GetPort("27017/tcp")),
 | |
| 	}
 | |
| 	retURL = uri.String()
 | |
| 
 | |
| 	// exponential backoff-retry
 | |
| 	err = pool.Retry(func() error {
 | |
| 		var err error
 | |
| 		ctx, _ := context.WithTimeout(context.Background(), 1*time.Minute)
 | |
| 		client, err := mongo.Connect(ctx, options.Client().ApplyURI(retURL))
 | |
| 		if err = client.Disconnect(ctx); err != nil {
 | |
| 			t.Fatal()
 | |
| 		}
 | |
| 		return client.Ping(ctx, readpref.Primary())
 | |
| 	})
 | |
| 	if err != nil {
 | |
| 		cleanup()
 | |
| 		t.Fatalf("Could not connect to mongo docker container: %s", err)
 | |
| 	}
 | |
| 
 | |
| 	return retURL, cleanup
 | |
| }
 | |
| 
 | |
| func connect(t *testing.T, uri string) (client *mongo.Client) {
 | |
| 	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
 | |
| 	defer cancel()
 | |
| 
 | |
| 	client, err := mongo.Connect(ctx, options.Client().ApplyURI(uri))
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("Unable to make connection to Mongo: %s", err)
 | |
| 	}
 | |
| 
 | |
| 	err = client.Ping(ctx, readpref.Primary())
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("Failed to ping Mongo server: %s", err)
 | |
| 	}
 | |
| 
 | |
| 	return client
 | |
| }
 | |
| 
 | |
| func setUpX509User(t *testing.T, client *mongo.Client, cert certhelpers.Certificate) {
 | |
| 	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
 | |
| 	defer cancel()
 | |
| 
 | |
| 	username := fmt.Sprintf("CN=%s", cert.Template.Subject.CommonName)
 | |
| 
 | |
| 	cmd := &createUserCommand{
 | |
| 		Username: username,
 | |
| 		Roles: []interface{}{
 | |
| 			mongodbRole{
 | |
| 				Role: "readWrite",
 | |
| 				DB:   "test",
 | |
| 			},
 | |
| 			mongodbRole{
 | |
| 				Role: "userAdminAnyDatabase",
 | |
| 				DB:   "admin",
 | |
| 			},
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	result := client.Database("$external").RunCommand(ctx, cmd)
 | |
| 	err := result.Err()
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("Failed to create x509 user in database: %s", err)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| type connStatus struct {
 | |
| 	AuthInfo authInfo `bson:"authInfo"`
 | |
| 	Ok       int      `bson:"ok"`
 | |
| }
 | |
| 
 | |
| type authInfo struct {
 | |
| 	AuthenticatedUsers     []user `bson:"authenticatedUsers"`
 | |
| 	AuthenticatedUserRoles roles  `bson:"authenticatedUserRoles"`
 | |
| }
 | |
| 
 | |
| type user struct {
 | |
| 	User string `bson:"user"`
 | |
| 	DB   string `bson:"db"`
 | |
| }
 | |
| 
 | |
| type role struct {
 | |
| 	Role string `bson:"role"`
 | |
| 	DB   string `bson:"db"`
 | |
| }
 | |
| 
 | |
| type roles []role
 | |
| 
 | |
| func (r roles) Len() int           { return len(r) }
 | |
| func (r roles) Less(i, j int) bool { return r[i].Role < r[j].Role }
 | |
| func (r roles) Swap(i, j int)      { r[i], r[j] = r[j], r[i] }
 | |
| 
 | |
| // ////////////////////////////////////////////////////////////////////////////
 | |
| // Writing to file
 | |
| // ////////////////////////////////////////////////////////////////////////////
 | |
| func writeFile(t *testing.T, filename string, data []byte, perms os.FileMode) {
 | |
| 	t.Helper()
 | |
| 
 | |
| 	err := ioutil.WriteFile(filename, data, perms)
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("Unable to write to file [%s]: %s", filename, err)
 | |
| 	}
 | |
| }
 |