mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-10-30 02:02:43 +00:00
Add mongodbatlas static roles support (#8987)
* Refactor PG container creation. * Rework rotation tests to use shorter sleeps. * Refactor rotation tests. * Add a static role rotation test for MongoDB Atlas.
This commit is contained in:
@@ -13,6 +13,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/go-test/deep"
|
||||
"github.com/hashicorp/vault-plugin-database-mongodbatlas"
|
||||
"github.com/hashicorp/vault/api"
|
||||
"github.com/hashicorp/vault/helper/namespace"
|
||||
"github.com/hashicorp/vault/helper/testhelpers/docker"
|
||||
@@ -104,6 +105,7 @@ func getCluster(t *testing.T) (*vault.TestCluster, logical.SystemView) {
|
||||
sys := vault.TestDynamicSystemView(cores[0].Core)
|
||||
vault.TestAddTestPlugin(t, cores[0].Core, "postgresql-database-plugin", consts.PluginTypeDatabase, "TestBackend_PluginMain_Postgres", []string{}, "")
|
||||
vault.TestAddTestPlugin(t, cores[0].Core, "mongodb-database-plugin", consts.PluginTypeDatabase, "TestBackend_PluginMain_Mongo", []string{}, "")
|
||||
vault.TestAddTestPlugin(t, cores[0].Core, "mongodbatlas-database-plugin", consts.PluginTypeDatabase, "TestBackend_PluginMain_MongoAtlas", []string{}, "")
|
||||
|
||||
return cluster, sys
|
||||
}
|
||||
@@ -149,6 +151,28 @@ func TestBackend_PluginMain_Mongo(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBackend_PluginMain_MongoAtlas(t *testing.T) {
|
||||
if os.Getenv(pluginutil.PluginUnwrapTokenEnv) == "" {
|
||||
return
|
||||
}
|
||||
|
||||
caPEM := os.Getenv(pluginutil.PluginCACertPEMEnv)
|
||||
if caPEM == "" {
|
||||
t.Fatal("CA cert not passed in")
|
||||
}
|
||||
|
||||
args := []string{"--ca-cert=" + caPEM}
|
||||
|
||||
apiClientMeta := &api.PluginAPIClientMeta{}
|
||||
flags := apiClientMeta.FlagSet()
|
||||
flags.Parse(args)
|
||||
|
||||
err := mongodbatlas.Run(apiClientMeta.GetTLSConfig())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBackend_RoleUpgrade(t *testing.T) {
|
||||
|
||||
storage := &logical.InmemStorage{}
|
||||
|
||||
@@ -3,18 +3,22 @@ package database
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"database/sql"
|
||||
|
||||
"github.com/Sectorbob/mlab-ns2/gae/ns/digest"
|
||||
"github.com/hashicorp/vault/helper/namespace"
|
||||
"github.com/hashicorp/vault/helper/testhelpers/mongodb"
|
||||
postgreshelper "github.com/hashicorp/vault/helper/testhelpers/postgresql"
|
||||
"github.com/hashicorp/vault/sdk/framework"
|
||||
"github.com/hashicorp/vault/sdk/helper/dbtxn"
|
||||
"github.com/hashicorp/vault/sdk/logical"
|
||||
"github.com/lib/pq"
|
||||
mongodbatlasapi "github.com/mongodb/go-client-mongodb-atlas/mongodbatlas"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
@@ -44,7 +48,7 @@ func TestBackend_StaticRole_Rotate_basic(t *testing.T) {
|
||||
}
|
||||
defer b.Cleanup(context.Background())
|
||||
|
||||
cleanup, connURL := preparePostgresTestContainer(t, config.StorageView, b)
|
||||
cleanup, connURL := postgreshelper.PrepareTestContainer(t, "")
|
||||
defer cleanup()
|
||||
|
||||
// create the database user
|
||||
@@ -192,7 +196,7 @@ func TestBackend_StaticRole_Rotate_NonStaticError(t *testing.T) {
|
||||
}
|
||||
defer b.Cleanup(context.Background())
|
||||
|
||||
cleanup, connURL := preparePostgresTestContainer(t, config.StorageView, b)
|
||||
cleanup, connURL := postgreshelper.PrepareTestContainer(t, "")
|
||||
defer cleanup()
|
||||
|
||||
// create the database user
|
||||
@@ -296,7 +300,7 @@ func TestBackend_StaticRole_Revoke_user(t *testing.T) {
|
||||
}
|
||||
defer b.Cleanup(context.Background())
|
||||
|
||||
cleanup, connURL := preparePostgresTestContainer(t, config.StorageView, b)
|
||||
cleanup, connURL := postgreshelper.PrepareTestContainer(t, "")
|
||||
defer cleanup()
|
||||
|
||||
// create the database user
|
||||
@@ -521,7 +525,7 @@ func TestBackend_Static_QueueWAL_discard_role_newer_rotation_date(t *testing.T)
|
||||
t.Fatal("could not convert to db backend")
|
||||
}
|
||||
|
||||
cleanup, connURL := preparePostgresTestContainer(t, config.StorageView, b)
|
||||
cleanup, connURL := postgreshelper.PrepareTestContainer(t, "")
|
||||
defer cleanup()
|
||||
|
||||
// create the database user
|
||||
@@ -685,143 +689,83 @@ func assertWALCount(t *testing.T, s logical.Storage, expected int, key string) {
|
||||
// End WAL testing
|
||||
//
|
||||
|
||||
type userCreator func(t *testing.T, username, password string)
|
||||
|
||||
func TestBackend_StaticRole_Rotations_PostgreSQL(t *testing.T) {
|
||||
cluster, sys := getCluster(t)
|
||||
defer cluster.Cleanup()
|
||||
|
||||
config := logical.TestBackendConfig()
|
||||
config.StorageView = &logical.InmemStorage{}
|
||||
config.System = sys
|
||||
|
||||
b, err := Factory(context.Background(), config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer b.Cleanup(context.Background())
|
||||
|
||||
bd := b.(*databaseBackend)
|
||||
if bd.credRotationQueue == nil {
|
||||
t.Fatal("database backend had no credential rotation queue")
|
||||
}
|
||||
|
||||
// Configure backend, add item and confirm length
|
||||
cleanup, connURL := preparePostgresTestContainer(t, config.StorageView, b)
|
||||
cleanup, connURL := postgreshelper.PrepareTestContainer(t, "latest")
|
||||
defer cleanup()
|
||||
testCases := []string{"65", "130", "5400"}
|
||||
// Create database users ahead
|
||||
for _, tc := range testCases {
|
||||
createTestPGUser(t, connURL, dbUser+tc, dbUserDefaultPassword, testRoleStaticCreate)
|
||||
}
|
||||
|
||||
// Configure a connection
|
||||
data := map[string]interface{}{
|
||||
"connection_url": connURL,
|
||||
"plugin_name": "postgresql-database-plugin",
|
||||
"verify_connection": false,
|
||||
"allowed_roles": []string{"*"},
|
||||
"name": "plugin-test",
|
||||
}
|
||||
req := &logical.Request{
|
||||
Operation: logical.UpdateOperation,
|
||||
Path: "config/plugin-test",
|
||||
Storage: config.StorageView,
|
||||
Data: data,
|
||||
}
|
||||
resp, err := b.HandleRequest(namespace.RootContext(nil), req)
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("err:%s resp:%#v\n", err, resp)
|
||||
}
|
||||
|
||||
// Create three static roles with different rotation periods
|
||||
for _, tc := range testCases {
|
||||
roleName := "plugin-static-role-" + tc
|
||||
data = map[string]interface{}{
|
||||
"name": roleName,
|
||||
"db_name": "plugin-test",
|
||||
"rotation_statements": testRoleStaticUpdate,
|
||||
"username": dbUser + tc,
|
||||
"rotation_period": tc,
|
||||
}
|
||||
|
||||
req = &logical.Request{
|
||||
Operation: logical.CreateOperation,
|
||||
Path: "static-roles/" + roleName,
|
||||
Storage: config.StorageView,
|
||||
Data: data,
|
||||
}
|
||||
|
||||
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("err:%s resp:%#v\n", err, resp)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify the queue has 3 items in it
|
||||
if bd.credRotationQueue.Len() != 3 {
|
||||
t.Fatalf("expected 3 items in the rotation queue, got: (%d)", bd.credRotationQueue.Len())
|
||||
}
|
||||
|
||||
// List the roles
|
||||
data = map[string]interface{}{}
|
||||
req = &logical.Request{
|
||||
Operation: logical.ListOperation,
|
||||
Path: "static-roles/",
|
||||
Storage: config.StorageView,
|
||||
Data: data,
|
||||
}
|
||||
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("err:%s resp:%#v\n", err, resp)
|
||||
}
|
||||
|
||||
keys := resp.Data["keys"].([]string)
|
||||
if len(keys) != 3 {
|
||||
t.Fatalf("expected 3 roles, got: (%d)", len(keys))
|
||||
}
|
||||
|
||||
// Capture initial passwords, before the periodic function is triggered
|
||||
pws := make(map[string][]string, 0)
|
||||
pws = capturePasswords(t, b, config, testCases, pws)
|
||||
|
||||
// Sleep to make sure the 65s role will be up for rotation by the time the
|
||||
// periodic function ticks
|
||||
time.Sleep(7 * time.Second)
|
||||
|
||||
// Sleep 75 to make sure the periodic func has time to actually run
|
||||
time.Sleep(75 * time.Second)
|
||||
pws = capturePasswords(t, b, config, testCases, pws)
|
||||
|
||||
// Sleep more, this should allow both sr65 and sr130 to rotate
|
||||
time.Sleep(140 * time.Second)
|
||||
pws = capturePasswords(t, b, config, testCases, pws)
|
||||
|
||||
// Verify all pws are as they should
|
||||
pass := true
|
||||
for k, v := range pws {
|
||||
switch {
|
||||
case k == "plugin-static-role-65":
|
||||
// expect all passwords to be different
|
||||
if v[0] == v[1] || v[1] == v[2] || v[0] == v[2] {
|
||||
pass = false
|
||||
}
|
||||
case k == "plugin-static-role-130":
|
||||
// expect the first two to be equal, but different from the third
|
||||
if v[0] != v[1] || v[0] == v[2] {
|
||||
pass = false
|
||||
}
|
||||
case k == "plugin-static-role-5400":
|
||||
// expect all passwords to be equal
|
||||
if v[0] != v[1] || v[1] != v[2] {
|
||||
pass = false
|
||||
}
|
||||
}
|
||||
}
|
||||
if !pass {
|
||||
t.Fatalf("password rotations did not match expected: %#v", pws)
|
||||
}
|
||||
uc := userCreator(func(t *testing.T, username, password string) {
|
||||
createTestPGUser(t, connURL, username, password, testRoleStaticCreate)
|
||||
})
|
||||
testBackend_StaticRole_Rotations(t, uc, map[string]interface{}{
|
||||
"connection_url": connURL,
|
||||
"plugin_name": "postgresql-database-plugin",
|
||||
})
|
||||
}
|
||||
|
||||
func TestBackend_StaticRole_Rotations_MongoDB(t *testing.T) {
|
||||
cleanup, connURL := mongodb.PrepareTestContainerWithDatabase(t, "latest", "vaulttestdb")
|
||||
defer cleanup()
|
||||
|
||||
uc := userCreator(func(t *testing.T, username, password string) {
|
||||
testCreateDBUser(t, connURL, "vaulttestdb", username, password)
|
||||
})
|
||||
testBackend_StaticRole_Rotations(t, uc, map[string]interface{}{
|
||||
"connection_url": connURL,
|
||||
"plugin_name": "mongodb-database-plugin",
|
||||
})
|
||||
}
|
||||
|
||||
func TestBackend_StaticRole_Rotations_MongoDBAtlas(t *testing.T) {
|
||||
// To get the project ID, connect to cloud.mongodb.com, go to the vault-test project and
|
||||
// look at Project Settings.
|
||||
projID := os.Getenv("VAULT_MONGODBATLAS_PROJECT_ID")
|
||||
// For the private and public key, go to Organization Access Manager on cloud.mongodb.com,
|
||||
// choose Create API Key, then create one using the defaults. Then go back to the vault-test
|
||||
// project and add the API key to it, with permissions "Project Owner".
|
||||
privKey := os.Getenv("VAULT_MONGODBATLAS_PRIVATE_KEY")
|
||||
pubKey := os.Getenv("VAULT_MONGODBATLAS_PUBLIC_KEY")
|
||||
if projID == "" {
|
||||
t.Logf("Skipping MongoDB Atlas test because VAULT_MONGODBATLAS_PROJECT_ID not set")
|
||||
t.SkipNow()
|
||||
}
|
||||
|
||||
transport := digest.NewTransport(pubKey, privKey)
|
||||
cl, err := transport.Client()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
api, err := mongodbatlasapi.New(cl)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
uc := userCreator(func(t *testing.T, username, password string) {
|
||||
// Delete the user in case it's still there from an earlier run, ignore
|
||||
// errors in case it's not.
|
||||
_, _ = api.DatabaseUsers.Delete(context.Background(), projID, username)
|
||||
|
||||
req := &mongodbatlasapi.DatabaseUser{
|
||||
Username: username,
|
||||
Password: password,
|
||||
DatabaseName: "admin",
|
||||
Roles: []mongodbatlasapi.Role{{RoleName: "atlasAdmin", DatabaseName: "admin"}},
|
||||
}
|
||||
_, _, err := api.DatabaseUsers.Create(context.Background(), projID, req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
testBackend_StaticRole_Rotations(t, uc, map[string]interface{}{
|
||||
"plugin_name": "mongodbatlas-database-plugin",
|
||||
"project_id": projID,
|
||||
"private_key": privKey,
|
||||
"public_key": pubKey,
|
||||
})
|
||||
}
|
||||
|
||||
func testBackend_StaticRole_Rotations(t *testing.T, createUser userCreator, opts map[string]interface{}) {
|
||||
cluster, sys := getCluster(t)
|
||||
defer cluster.Cleanup()
|
||||
|
||||
@@ -829,6 +773,7 @@ func TestBackend_StaticRole_Rotations_MongoDB(t *testing.T) {
|
||||
config.StorageView = &logical.InmemStorage{}
|
||||
config.System = sys
|
||||
|
||||
// Rotation ticker starts running in Factory call
|
||||
b, err := Factory(context.Background(), config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -841,27 +786,18 @@ func TestBackend_StaticRole_Rotations_MongoDB(t *testing.T) {
|
||||
t.Fatal("database backend had no credential rotation queue")
|
||||
}
|
||||
|
||||
// configure backend, add item and confirm length
|
||||
cleanup, connURL := mongodb.PrepareTestContainerWithDatabase(t, "latest", "vaulttestdb")
|
||||
defer cleanup()
|
||||
testCases := []string{"65", "130", "5400"}
|
||||
// Create database users ahead
|
||||
for _, tc := range testCases {
|
||||
testCreateDBUser(t, connURL, "vaulttestdb", "statictestMongo"+tc, "test")
|
||||
}
|
||||
|
||||
// Configure a connection
|
||||
data := map[string]interface{}{
|
||||
"connection_url": connURL,
|
||||
"plugin_name": "mongodb-database-plugin",
|
||||
"verify_connection": false,
|
||||
"allowed_roles": []string{"*"},
|
||||
"name": "plugin-mongo-test",
|
||||
}
|
||||
for k, v := range opts {
|
||||
data[k] = v
|
||||
}
|
||||
|
||||
req := &logical.Request{
|
||||
Operation: logical.UpdateOperation,
|
||||
Path: "config/plugin-mongo-test",
|
||||
Path: "config/plugin-test",
|
||||
Storage: config.StorageView,
|
||||
Data: data,
|
||||
}
|
||||
@@ -870,13 +806,19 @@ func TestBackend_StaticRole_Rotations_MongoDB(t *testing.T) {
|
||||
t.Fatalf("err:%s resp:%#v\n", err, resp)
|
||||
}
|
||||
|
||||
testCases := []string{"10", "20", "100"}
|
||||
// Create database users ahead
|
||||
for _, tc := range testCases {
|
||||
createUser(t, "statictest"+tc, "test")
|
||||
}
|
||||
|
||||
// create three static roles with different rotation periods
|
||||
for _, tc := range testCases {
|
||||
roleName := "plugin-static-role-" + tc
|
||||
data = map[string]interface{}{
|
||||
"name": roleName,
|
||||
"db_name": "plugin-mongo-test",
|
||||
"username": "statictestMongo" + tc,
|
||||
"db_name": "plugin-test",
|
||||
"username": "statictest" + tc,
|
||||
"rotation_period": tc,
|
||||
}
|
||||
|
||||
@@ -920,16 +862,12 @@ func TestBackend_StaticRole_Rotations_MongoDB(t *testing.T) {
|
||||
pws := make(map[string][]string, 0)
|
||||
pws = capturePasswords(t, b, config, testCases, pws)
|
||||
|
||||
// sleep to make sure the 65s role will be up for rotation by the time the
|
||||
// periodic function ticks
|
||||
time.Sleep(7 * time.Second)
|
||||
|
||||
// sleep 75 to make sure the periodic func has time to actually run
|
||||
time.Sleep(75 * time.Second)
|
||||
// sleep to make sure the periodic func has time to actually run
|
||||
time.Sleep(15 * time.Second)
|
||||
pws = capturePasswords(t, b, config, testCases, pws)
|
||||
|
||||
// sleep more, this should allow both sr65 and sr130 to rotate
|
||||
time.Sleep(140 * time.Second)
|
||||
// sleep more, this should allow both sr10 and sr20 to rotate
|
||||
time.Sleep(10 * time.Second)
|
||||
pws = capturePasswords(t, b, config, testCases, pws)
|
||||
|
||||
// verify all pws are as they should
|
||||
@@ -939,21 +877,23 @@ func TestBackend_StaticRole_Rotations_MongoDB(t *testing.T) {
|
||||
t.Fatalf("expected to find 3 passwords for (%s), only found (%d)", k, len(v))
|
||||
}
|
||||
switch {
|
||||
case k == "plugin-static-role-65":
|
||||
case k == "plugin-static-role-10":
|
||||
// expect all passwords to be different
|
||||
if v[0] == v[1] || v[1] == v[2] || v[0] == v[2] {
|
||||
pass = false
|
||||
}
|
||||
case k == "plugin-static-role-130":
|
||||
case k == "plugin-static-role-20":
|
||||
// expect the first two to be equal, but different from the third
|
||||
if v[0] != v[1] || v[0] == v[2] {
|
||||
pass = false
|
||||
}
|
||||
case k == "plugin-static-role-5400":
|
||||
case k == "plugin-static-role-100":
|
||||
// expect all passwords to be equal
|
||||
if v[0] != v[1] || v[1] != v[2] {
|
||||
pass = false
|
||||
}
|
||||
default:
|
||||
t.Fatalf("unexpected password key: %v", k)
|
||||
}
|
||||
}
|
||||
if !pass {
|
||||
@@ -1004,7 +944,7 @@ func TestBackend_StaticRole_LockRegression(t *testing.T) {
|
||||
}
|
||||
defer b.Cleanup(context.Background())
|
||||
|
||||
cleanup, connURL := preparePostgresTestContainer(t, config.StorageView, b)
|
||||
cleanup, connURL := postgreshelper.PrepareTestContainer(t, "")
|
||||
defer cleanup()
|
||||
|
||||
// Configure a connection
|
||||
@@ -1073,7 +1013,7 @@ func TestBackend_StaticRole_Rotate_Invalid_Role(t *testing.T) {
|
||||
}
|
||||
defer b.Cleanup(context.Background())
|
||||
|
||||
cleanup, connURL := preparePostgresTestContainer(t, config.StorageView, b)
|
||||
cleanup, connURL := postgreshelper.PrepareTestContainer(t, "")
|
||||
defer cleanup()
|
||||
|
||||
// create the database user
|
||||
|
||||
@@ -6,58 +6,17 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/vault/helper/testhelpers/docker"
|
||||
logicaltest "github.com/hashicorp/vault/helper/testhelpers/logical"
|
||||
postgreshelper "github.com/hashicorp/vault/helper/testhelpers/postgresql"
|
||||
"github.com/hashicorp/vault/sdk/logical"
|
||||
"github.com/lib/pq"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/ory/dockertest"
|
||||
)
|
||||
|
||||
func prepareTestContainer(t *testing.T) (cleanup func(), retURL string) {
|
||||
if os.Getenv("PG_URL") != "" {
|
||||
return func() {}, os.Getenv("PG_URL")
|
||||
}
|
||||
|
||||
pool, err := dockertest.NewPool("")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to connect to docker: %s", err)
|
||||
}
|
||||
|
||||
resource, err := pool.Run("postgres", "latest", []string{"POSTGRES_PASSWORD=secret", "POSTGRES_DB=database"})
|
||||
if err != nil {
|
||||
t.Fatalf("Could not start local PostgreSQL docker container: %s", err)
|
||||
}
|
||||
|
||||
cleanup = func() {
|
||||
docker.CleanupResource(t, pool, resource)
|
||||
}
|
||||
|
||||
retURL = fmt.Sprintf("postgres://postgres:secret@localhost:%s/database?sslmode=disable", resource.GetPort("5432/tcp"))
|
||||
|
||||
// exponential backoff-retry
|
||||
if err = pool.Retry(func() error {
|
||||
var err error
|
||||
var db *sql.DB
|
||||
db, err = sql.Open("postgres", retURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer db.Close()
|
||||
return db.Ping()
|
||||
}); err != nil {
|
||||
cleanup()
|
||||
t.Fatalf("Could not connect to PostgreSQL docker container: %s", err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func TestBackend_config_connection(t *testing.T) {
|
||||
var resp *logical.Response
|
||||
var err error
|
||||
@@ -107,7 +66,7 @@ func TestBackend_basic(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cleanup, connURL := prepareTestContainer(t)
|
||||
cleanup, connURL := postgreshelper.PrepareTestContainer(t, "")
|
||||
defer cleanup()
|
||||
|
||||
connData := map[string]interface{}{
|
||||
@@ -131,7 +90,7 @@ func TestBackend_roleCrud(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cleanup, connURL := prepareTestContainer(t)
|
||||
cleanup, connURL := postgreshelper.PrepareTestContainer(t, "")
|
||||
defer cleanup()
|
||||
|
||||
connData := map[string]interface{}{
|
||||
@@ -157,7 +116,7 @@ func TestBackend_BlockStatements(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cleanup, connURL := prepareTestContainer(t)
|
||||
cleanup, connURL := postgreshelper.PrepareTestContainer(t, "")
|
||||
defer cleanup()
|
||||
|
||||
connData := map[string]interface{}{
|
||||
@@ -187,7 +146,7 @@ func TestBackend_roleReadOnly(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cleanup, connURL := prepareTestContainer(t)
|
||||
cleanup, connURL := postgreshelper.PrepareTestContainer(t, "")
|
||||
defer cleanup()
|
||||
|
||||
connData := map[string]interface{}{
|
||||
@@ -218,7 +177,7 @@ func TestBackend_roleReadOnly_revocationSQL(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cleanup, connURL := prepareTestContainer(t)
|
||||
cleanup, connURL := postgreshelper.PrepareTestContainer(t, "")
|
||||
defer cleanup()
|
||||
|
||||
connData := map[string]interface{}{
|
||||
|
||||
4
go.mod
4
go.mod
@@ -15,6 +15,7 @@ require (
|
||||
github.com/DataDog/zstd v1.4.4 // indirect
|
||||
github.com/NYTimes/gziphandler v1.1.1
|
||||
github.com/SAP/go-hdb v0.14.1
|
||||
github.com/Sectorbob/mlab-ns2 v0.0.0-20171030222938-d3aa0c295a8a
|
||||
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 // indirect
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190620160927-9418d7b0cd0f
|
||||
github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190307165228-86c17b95fcd5
|
||||
@@ -81,7 +82,7 @@ require (
|
||||
github.com/hashicorp/vault-plugin-auth-kubernetes v0.6.1
|
||||
github.com/hashicorp/vault-plugin-auth-oci v0.5.4
|
||||
github.com/hashicorp/vault-plugin-database-elasticsearch v0.5.4
|
||||
github.com/hashicorp/vault-plugin-database-mongodbatlas v0.1.1
|
||||
github.com/hashicorp/vault-plugin-database-mongodbatlas v0.1.0-beta1.0.20200521152755-9cf156a44f9c
|
||||
github.com/hashicorp/vault-plugin-secrets-ad v0.6.4-beta1.0.20200518124111-3dceeb3ce90e
|
||||
github.com/hashicorp/vault-plugin-secrets-alicloud v0.5.5
|
||||
github.com/hashicorp/vault-plugin-secrets-azure v0.5.6
|
||||
@@ -111,6 +112,7 @@ require (
|
||||
github.com/mitchellh/gox v1.0.1
|
||||
github.com/mitchellh/mapstructure v1.2.2
|
||||
github.com/mitchellh/reflectwalk v1.0.1
|
||||
github.com/mongodb/go-client-mongodb-atlas v0.1.2
|
||||
github.com/natefinch/atomic v0.0.0-20150920032501-a62ce929ffcc
|
||||
github.com/ncw/swift v1.0.47
|
||||
github.com/nwaples/rardecode v1.0.0 // indirect
|
||||
|
||||
2
go.sum
2
go.sum
@@ -496,6 +496,8 @@ github.com/hashicorp/vault-plugin-auth-oci v0.5.4 h1:Hoauxh1V8Lusf7BRs+yXfoDTFQz
|
||||
github.com/hashicorp/vault-plugin-auth-oci v0.5.4/go.mod h1:j05O2b9fw2Q82NxDPhHMYVfHKvitUYGWfmqmpBdqmmc=
|
||||
github.com/hashicorp/vault-plugin-database-elasticsearch v0.5.4 h1:YE4qndazWmYGpVOoZI7nDGG+gwTZKzL1Ou4WZQ+Tdxk=
|
||||
github.com/hashicorp/vault-plugin-database-elasticsearch v0.5.4/go.mod h1:QjGrrxcRXv/4XkEZAlM0VMZEa3uxKAICFqDj27FP/48=
|
||||
github.com/hashicorp/vault-plugin-database-mongodbatlas v0.1.0-beta1.0.20200521152755-9cf156a44f9c h1:9pXwe7sEVhZ5C3U6egIrKaZBb5lD0FvLIjISEvpbQQA=
|
||||
github.com/hashicorp/vault-plugin-database-mongodbatlas v0.1.0-beta1.0.20200521152755-9cf156a44f9c/go.mod h1:HTXNzFr/SAVtJOs7jz0XxZ69jlKtaceEwp37l86UAQ0=
|
||||
github.com/hashicorp/vault-plugin-database-mongodbatlas v0.1.1 h1:fA6cFH8lIPH2M4KNTEzf1bpc6Tbyy5ZvoYP8H/TI9ts=
|
||||
github.com/hashicorp/vault-plugin-database-mongodbatlas v0.1.1/go.mod h1:MP3kfr0N+7miOTZFwKv952b9VkXM4S2Q6YtQCiNKWq8=
|
||||
github.com/hashicorp/vault-plugin-secrets-ad v0.6.4-beta1.0.20200518124111-3dceeb3ce90e h1:0GK1BNBfglD2sydZ4XXMjJElhY8bC2TDdc0vk1Q9zbA=
|
||||
|
||||
53
helper/testhelpers/postgresql/postgreshelper.go
Normal file
53
helper/testhelpers/postgresql/postgreshelper.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package postgresql
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/vault/helper/testhelpers/docker"
|
||||
"github.com/ory/dockertest"
|
||||
)
|
||||
|
||||
func PrepareTestContainer(t *testing.T, version string) (cleanup func(), retURL string) {
|
||||
if os.Getenv("PG_URL") != "" {
|
||||
return func() {}, os.Getenv("PG_URL")
|
||||
}
|
||||
if version == "" {
|
||||
version = "latest"
|
||||
}
|
||||
|
||||
pool, err := dockertest.NewPool("")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to connect to docker: %s", err)
|
||||
}
|
||||
|
||||
resource, err := pool.Run("postgres", "latest", []string{"POSTGRES_PASSWORD=secret", "POSTGRES_DB=database"})
|
||||
if err != nil {
|
||||
t.Fatalf("Could not start local PostgreSQL docker container: %s", err)
|
||||
}
|
||||
|
||||
cleanup = func() {
|
||||
docker.CleanupResource(t, pool, resource)
|
||||
}
|
||||
|
||||
retURL = fmt.Sprintf("postgres://postgres:secret@localhost:%s/database?sslmode=disable", resource.GetPort("5432/tcp"))
|
||||
|
||||
// exponential backoff-retry
|
||||
if err = pool.Retry(func() error {
|
||||
var err error
|
||||
var db *sql.DB
|
||||
db, err = sql.Open("postgres", retURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer db.Close()
|
||||
return db.Ping()
|
||||
}); err != nil {
|
||||
cleanup()
|
||||
t.Fatalf("Could not connect to PostgreSQL docker container: %s", err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
@@ -7,10 +7,9 @@ import (
|
||||
"time"
|
||||
|
||||
log "github.com/hashicorp/go-hclog"
|
||||
"github.com/hashicorp/vault/helper/testhelpers/docker"
|
||||
"github.com/hashicorp/vault/helper/testhelpers/postgresql"
|
||||
"github.com/hashicorp/vault/sdk/helper/logging"
|
||||
"github.com/hashicorp/vault/sdk/physical"
|
||||
"github.com/ory/dockertest"
|
||||
|
||||
_ "github.com/lib/pq"
|
||||
)
|
||||
@@ -19,11 +18,11 @@ func TestPostgreSQLBackend(t *testing.T) {
|
||||
logger := logging.NewVaultLogger(log.Debug)
|
||||
|
||||
// Use docker as pg backend if no url is provided via environment variables
|
||||
var cleanup func()
|
||||
connURL := os.Getenv("PGURL")
|
||||
if connURL == "" {
|
||||
cleanup, connURL = prepareTestContainer(t, logger)
|
||||
cleanup, u := postgresql.PrepareTestContainer(t, "11.1")
|
||||
defer cleanup()
|
||||
connURL = u
|
||||
}
|
||||
|
||||
table := os.Getenv("PGTABLE")
|
||||
@@ -361,47 +360,6 @@ func testPostgresSQLLockRenewal(t *testing.T, ha physical.HABackend) {
|
||||
newLock.Unlock()
|
||||
}
|
||||
|
||||
func prepareTestContainer(t *testing.T, logger log.Logger) (cleanup func(), retConnString string) {
|
||||
// If environment variable is set, use this connectionstring without starting docker container
|
||||
if os.Getenv("PGURL") != "" {
|
||||
return func() {}, os.Getenv("PGURL")
|
||||
}
|
||||
|
||||
pool, err := dockertest.NewPool("")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to connect to docker: %s", err)
|
||||
}
|
||||
// using 11.1 which is currently latest, use hard version for stability of tests
|
||||
resource, err := pool.Run("postgres", "11.1", []string{})
|
||||
if err != nil {
|
||||
t.Fatalf("Could not start docker Postgres: %s", err)
|
||||
}
|
||||
|
||||
retConnString = fmt.Sprintf("postgres://postgres@localhost:%v/postgres?sslmode=disable", resource.GetPort("5432/tcp"))
|
||||
|
||||
cleanup = func() {
|
||||
docker.CleanupResource(t, pool, resource)
|
||||
}
|
||||
|
||||
// Provide a test function to the pool to test if docker instance service is up.
|
||||
// We try to setup a pg backend as test for successful connect
|
||||
// exponential backoff-retry, because the dockerinstance may not be able to accept
|
||||
// connections yet, test by trying to setup a postgres backend, max-timeout is 60s
|
||||
if err := pool.Retry(func() error {
|
||||
var err error
|
||||
_, err = NewPostgreSQLBackend(map[string]string{
|
||||
"connection_url": retConnString,
|
||||
}, logger)
|
||||
return err
|
||||
|
||||
}); err != nil {
|
||||
cleanup()
|
||||
t.Fatalf("Could not connect to docker: %s", err)
|
||||
}
|
||||
|
||||
return cleanup, retConnString
|
||||
}
|
||||
|
||||
func setupDatabaseObjects(t *testing.T, logger log.Logger, pg *PostgreSQLBackend) {
|
||||
var err error
|
||||
// Setup tables and indexes if not exists.
|
||||
|
||||
@@ -4,64 +4,24 @@ import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/vault/helper/testhelpers/docker"
|
||||
"github.com/hashicorp/vault/helper/testhelpers/postgresql"
|
||||
"github.com/hashicorp/vault/sdk/database/dbplugin"
|
||||
"github.com/hashicorp/vault/sdk/helper/dbtxn"
|
||||
"github.com/lib/pq"
|
||||
"github.com/ory/dockertest"
|
||||
)
|
||||
|
||||
func preparePostgresTestContainer(t *testing.T) (cleanup func(), retURL string) {
|
||||
if os.Getenv("PG_URL") != "" {
|
||||
return func() {}, os.Getenv("PG_URL")
|
||||
}
|
||||
|
||||
pool, err := dockertest.NewPool("")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to connect to docker: %s", err)
|
||||
}
|
||||
|
||||
resource, err := pool.Run("postgres", "latest", []string{"POSTGRES_PASSWORD=secret", "POSTGRES_DB=database"})
|
||||
if err != nil {
|
||||
t.Fatalf("Could not start local PostgreSQL docker container: %s", err)
|
||||
}
|
||||
|
||||
cleanup = func() {
|
||||
docker.CleanupResource(t, pool, resource)
|
||||
}
|
||||
|
||||
retURL = fmt.Sprintf("postgres://postgres:secret@localhost:%s/database?sslmode=disable", resource.GetPort("5432/tcp"))
|
||||
|
||||
// exponential backoff-retry
|
||||
if err = pool.Retry(func() error {
|
||||
var err error
|
||||
var db *sql.DB
|
||||
db, err = sql.Open("postgres", retURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer db.Close()
|
||||
return db.Ping()
|
||||
}); err != nil {
|
||||
cleanup()
|
||||
t.Fatalf("Could not connect to PostgreSQL docker container: %s", err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func TestPostgreSQL_Initialize(t *testing.T) {
|
||||
cleanup, connURL := preparePostgresTestContainer(t)
|
||||
defer cleanup()
|
||||
func getPostgreSQL(t *testing.T, options map[string]interface{}) (*PostgreSQL, func()) {
|
||||
cleanup, connURL := postgresql.PrepareTestContainer(t, "latest")
|
||||
|
||||
connectionDetails := map[string]interface{}{
|
||||
"connection_url": connURL,
|
||||
"max_open_connections": 5,
|
||||
"connection_url": connURL,
|
||||
}
|
||||
for k, v := range options {
|
||||
connectionDetails[k] = v
|
||||
}
|
||||
|
||||
db := new()
|
||||
@@ -73,23 +33,29 @@ func TestPostgreSQL_Initialize(t *testing.T) {
|
||||
if !db.Initialized {
|
||||
t.Fatal("Database should be initialized")
|
||||
}
|
||||
return db, cleanup
|
||||
}
|
||||
|
||||
err = db.Close()
|
||||
if err != nil {
|
||||
func TestPostgreSQL_Initialize(t *testing.T) {
|
||||
db, cleanup := getPostgreSQL(t, map[string]interface{}{
|
||||
"max_open_connections": 5,
|
||||
})
|
||||
defer cleanup()
|
||||
|
||||
if err := db.Close(); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Test decoding a string value for max_open_connections
|
||||
connectionDetails = map[string]interface{}{
|
||||
"connection_url": connURL,
|
||||
func TestPostgreSQL_InitializeWithStringVals(t *testing.T) {
|
||||
db, cleanup := getPostgreSQL(t, map[string]interface{}{
|
||||
"max_open_connections": "5",
|
||||
}
|
||||
})
|
||||
defer cleanup()
|
||||
|
||||
_, err = db.Init(context.Background(), connectionDetails, true)
|
||||
if err != nil {
|
||||
if err := db.Close(); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestPostgreSQL_CreateUser_missingArgs(t *testing.T) {
|
||||
@@ -180,19 +146,9 @@ func TestPostgreSQL_CreateUser(t *testing.T) {
|
||||
}
|
||||
|
||||
// Shared test container for speed - there should not be any overlap between the tests
|
||||
cleanup, connURL := preparePostgresTestContainer(t)
|
||||
db, cleanup := getPostgreSQL(t, nil)
|
||||
defer cleanup()
|
||||
|
||||
connectionDetails := map[string]interface{}{
|
||||
"connection_url": connURL,
|
||||
}
|
||||
|
||||
db := new()
|
||||
_, err := db.Init(context.Background(), connectionDetails, true)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
for name, test := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
usernameConfig := dbplugin.UsernameConfig{
|
||||
@@ -218,14 +174,14 @@ func TestPostgreSQL_CreateUser(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
if err = testCredsExist(t, connURL, username, password); err != nil {
|
||||
if err = testCredsExist(t, db.ConnectionURL, username, password); err != nil {
|
||||
t.Fatalf("Could not connect with new credentials: %s", err)
|
||||
}
|
||||
|
||||
// Ensure that the role doesn't expire immediately
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
if err = testCredsExist(t, connURL, username, password); err != nil {
|
||||
if err = testCredsExist(t, db.ConnectionURL, username, password); err != nil {
|
||||
t.Fatalf("Could not connect with new credentials: %s", err)
|
||||
}
|
||||
})
|
||||
@@ -252,24 +208,9 @@ func TestPostgreSQL_RenewUser(t *testing.T) {
|
||||
}
|
||||
|
||||
// Shared test container for speed - there should not be any overlap between the tests
|
||||
cleanup, connURL := preparePostgresTestContainer(t)
|
||||
db, cleanup := getPostgreSQL(t, nil)
|
||||
defer cleanup()
|
||||
|
||||
connectionDetails := map[string]interface{}{
|
||||
"connection_url": connURL,
|
||||
}
|
||||
|
||||
db := new()
|
||||
|
||||
// Give a timeout just in case the test decides to be problematic
|
||||
initCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
_, err := db.Init(initCtx, connectionDetails, true)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
for name, test := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
statements := dbplugin.Statements{
|
||||
@@ -291,7 +232,7 @@ func TestPostgreSQL_RenewUser(t *testing.T) {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if err = testCredsExist(t, connURL, username, password); err != nil {
|
||||
if err = testCredsExist(t, db.ConnectionURL, username, password); err != nil {
|
||||
t.Fatalf("Could not connect with new credentials: %s", err)
|
||||
}
|
||||
|
||||
@@ -303,7 +244,7 @@ func TestPostgreSQL_RenewUser(t *testing.T) {
|
||||
// Sleep longer than the initial expiration time
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
if err = testCredsExist(t, connURL, username, password); err != nil {
|
||||
if err = testCredsExist(t, db.ConnectionURL, username, password); err != nil {
|
||||
t.Fatalf("Could not connect with new credentials: %s", err)
|
||||
}
|
||||
})
|
||||
@@ -331,9 +272,8 @@ func TestPostgreSQL_RotateRootCredentials(t *testing.T) {
|
||||
|
||||
for name, test := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
cleanup, connURL := preparePostgresTestContainer(t)
|
||||
cleanup, connURL := postgresql.PrepareTestContainer(t, "latest")
|
||||
defer cleanup()
|
||||
|
||||
connURL = strings.Replace(connURL, "postgres:secret", `{{username}}:{{password}}`, -1)
|
||||
|
||||
connectionDetails := map[string]interface{}{
|
||||
@@ -344,7 +284,6 @@ func TestPostgreSQL_RotateRootCredentials(t *testing.T) {
|
||||
}
|
||||
|
||||
db := new()
|
||||
|
||||
connProducer := db.SQLConnectionProducer
|
||||
|
||||
// Give a timeout just in case the test decides to be problematic
|
||||
@@ -400,24 +339,9 @@ func TestPostgreSQL_RevokeUser(t *testing.T) {
|
||||
}
|
||||
|
||||
// Shared test container for speed - there should not be any overlap between the tests
|
||||
cleanup, connURL := preparePostgresTestContainer(t)
|
||||
db, cleanup := getPostgreSQL(t, nil)
|
||||
defer cleanup()
|
||||
|
||||
connectionDetails := map[string]interface{}{
|
||||
"connection_url": connURL,
|
||||
}
|
||||
|
||||
db := new()
|
||||
|
||||
// Give a timeout just in case the test decides to be problematic
|
||||
initCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
_, err := db.Init(initCtx, connectionDetails, true)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
for name, test := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
statements := dbplugin.Statements{
|
||||
@@ -435,7 +359,7 @@ func TestPostgreSQL_RevokeUser(t *testing.T) {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if err = testCredsExist(t, connURL, username, password); err != nil {
|
||||
if err = testCredsExist(t, db.ConnectionURL, username, password); err != nil {
|
||||
t.Fatalf("Could not connect with new credentials: %s", err)
|
||||
}
|
||||
|
||||
@@ -445,7 +369,7 @@ func TestPostgreSQL_RevokeUser(t *testing.T) {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if err := testCredsExist(t, connURL, username, password); err == nil {
|
||||
if err := testCredsExist(t, db.ConnectionURL, username, password); err == nil {
|
||||
t.Fatal("Credentials were not revoked")
|
||||
}
|
||||
})
|
||||
@@ -530,29 +454,13 @@ func TestPostgresSQL_SetCredentials(t *testing.T) {
|
||||
|
||||
for name, test := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
// Shared test container for speed - there should not be any overlap between the tests
|
||||
cleanup, connURL := preparePostgresTestContainer(t)
|
||||
db, cleanup := getPostgreSQL(t, nil)
|
||||
defer cleanup()
|
||||
|
||||
// create the database user
|
||||
dbUser := "vaultstatictest"
|
||||
initPassword := "password"
|
||||
createTestPGUser(t, connURL, dbUser, initPassword, testRoleStaticCreate)
|
||||
|
||||
connectionDetails := map[string]interface{}{
|
||||
"connection_url": connURL,
|
||||
}
|
||||
|
||||
db := new()
|
||||
|
||||
// Give a timeout just in case the test decides to be problematic
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
_, err := db.Init(ctx, connectionDetails, true)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
createTestPGUser(t, db.ConnectionURL, dbUser, initPassword, testRoleStaticCreate)
|
||||
|
||||
statements := dbplugin.Statements{
|
||||
Rotation: test.rotationStmts,
|
||||
@@ -568,20 +476,20 @@ func TestPostgresSQL_SetCredentials(t *testing.T) {
|
||||
Password: password,
|
||||
}
|
||||
|
||||
if err := testCredsExist(t, connURL, dbUser, initPassword); err != nil {
|
||||
if err := testCredsExist(t, db.ConnectionURL, dbUser, initPassword); err != nil {
|
||||
t.Fatalf("Could not connect with initial credentials: %s", err)
|
||||
}
|
||||
|
||||
username, password, err := db.SetCredentials(ctx, statements, usernameConfig)
|
||||
username, password, err := db.SetCredentials(context.Background(), statements, usernameConfig)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if err := testCredsExist(t, connURL, username, password); err != nil {
|
||||
if err := testCredsExist(t, db.ConnectionURL, username, password); err != nil {
|
||||
t.Fatalf("Could not connect with new credentials: %s", err)
|
||||
}
|
||||
|
||||
if err := testCredsExist(t, connURL, username, initPassword); err == nil {
|
||||
if err := testCredsExist(t, db.ConnectionURL, username, initPassword); err == nil {
|
||||
t.Fatalf("Should not be able to connect with initial credentials")
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
log "github.com/hashicorp/go-hclog"
|
||||
@@ -15,11 +13,9 @@ import (
|
||||
"github.com/hashicorp/vault/builtin/logical/pki"
|
||||
"github.com/hashicorp/vault/builtin/logical/transit"
|
||||
"github.com/hashicorp/vault/helper/builtinplugins"
|
||||
"github.com/hashicorp/vault/helper/testhelpers/docker"
|
||||
"github.com/hashicorp/vault/http"
|
||||
"github.com/hashicorp/vault/sdk/logical"
|
||||
"github.com/hashicorp/vault/vault"
|
||||
"github.com/ory/dockertest"
|
||||
)
|
||||
|
||||
// testVaultServer creates a test vault cluster and returns a configured API
|
||||
@@ -83,40 +79,3 @@ func testVaultServerCoreConfig(t testing.TB, coreConfig *vault.CoreConfig) (*api
|
||||
|
||||
return client, unsealKeys, func() { defer cluster.Cleanup() }
|
||||
}
|
||||
|
||||
// testPostgresDB creates a testing postgres database in a Docker container,
|
||||
// returning the connection URL and the associated closer function.
|
||||
func testPostgresDB(t testing.TB) (string, func()) {
|
||||
pool, err := dockertest.NewPool("")
|
||||
if err != nil {
|
||||
t.Fatalf("postgresdb: failed to connect to docker: %s", err)
|
||||
}
|
||||
|
||||
resource, err := pool.Run("postgres", "latest", []string{
|
||||
"POSTGRES_PASSWORD=secret",
|
||||
"POSTGRES_DB=database",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("postgresdb: could not start container: %s", err)
|
||||
}
|
||||
|
||||
cleanup := func() {
|
||||
docker.CleanupResource(t, pool, resource)
|
||||
}
|
||||
|
||||
addr := fmt.Sprintf("postgres://postgres:secret@localhost:%s/database?sslmode=disable", resource.GetPort("5432/tcp"))
|
||||
|
||||
if err := pool.Retry(func() error {
|
||||
db, err := sql.Open("postgres", addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer db.Close()
|
||||
return db.Ping()
|
||||
}); err != nil {
|
||||
cleanup()
|
||||
t.Fatalf("postgresdb: could not connect: %s", err)
|
||||
}
|
||||
|
||||
return addr, cleanup
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/vault/api"
|
||||
postgreshelper "github.com/hashicorp/vault/helper/testhelpers/postgresql"
|
||||
)
|
||||
|
||||
func TestRenewer_Renew(t *testing.T) {
|
||||
@@ -89,8 +90,8 @@ func TestRenewer_Renew(t *testing.T) {
|
||||
t.Run("database", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
pgURL, pgDone := testPostgresDB(t)
|
||||
defer pgDone()
|
||||
cleanup, pgURL := postgreshelper.PrepareTestContainer(t, "")
|
||||
defer cleanup()
|
||||
|
||||
if err := client.Sys().Mount("database", &api.MountInput{
|
||||
Type: "database",
|
||||
|
||||
95
vendor/github.com/hashicorp/vault-plugin-database-mongodbatlas/README.md
generated
vendored
95
vendor/github.com/hashicorp/vault-plugin-database-mongodbatlas/README.md
generated
vendored
@@ -1,86 +1,25 @@
|
||||
# MongoDB Atlas Database Secrets Engine
|
||||
# HashiCorp Vault Database Secrets Engine - MongoDB Atlas plugin
|
||||
|
||||
This plugin provides unique, short-lived credentials for [MongoDB Atlas](https://www.mongodb.com/cloud/atlas).
|
||||
It is to be used with [Hashicorp Vault](https://www.github.com/hashicorp/vault).
|
||||
MongoDB Atlas is one of the supported plugins for the HashiCorp Vault Database Secrets Engine and allows for the programmatic generation of unique, ephemeral MongoDB [Database User](https://docs.atlas.mongodb.com/reference/api/database-users/) credentials in MongoDB Atlas Projects.
|
||||
|
||||
**Please note**: We take Vault's security and our users' trust very seriously. If you believe you have found a security issue in Vault, _please responsibly disclose_ by contacting us at [security@hashicorp.com](mailto:security@hashicorp.com).
|
||||
**The plugin is included in version 1.4 of Vault.**
|
||||
|
||||
## Support, Bugs and Feature Requests
|
||||
Support for the HashiCorp Vault Database Secrets Engine - MongoDB Atlas is provided under MongoDB Atlas support plans. Please submit support questions within the Atlas UI. Vault support is via HashiCorp.
|
||||
|
||||
Bugs should be filed under the Issues section of this repo.
|
||||
|
||||
Feature requests can be submitted in the Issues section or directly to MongoDB at https://feedback.mongodb.com/forums/924145-atlas - just select the Vault plugin as the category or vote for an already suggested feature.
|
||||
|
||||
## Quick Links
|
||||
|
||||
- [Database Secrets Engine for MongoDB Atlas](https://www.vaultproject.io/docs/secrets/databases/mongodbatlas.html)
|
||||
- [Database Secrets Engine for MongoDB Atlas - Docs](https://www.vaultproject.io/docs/secrets/databases/mongodbatlas)
|
||||
- [Database Secrets Engine for MongoDB Atlas - API Docs](https://www.vaultproject.io/api-docs/secret/databases/mongodbatlas/)
|
||||
- [MongoDB Atlas Website](https://www.mongodb.com/cloud/atlas)
|
||||
- [Vault Website](https://www.vaultproject.io)
|
||||
- [Vault Github](https://www.github.com/hashicorp/vault)
|
||||
|
||||
## Getting Started
|
||||
**Please note**: Hashicorp takes Vault's security and their users' trust very seriously, as does MongoDB.
|
||||
|
||||
This is a Vault plugin and is meant to work with Vault. This guide assumes you have already installed Vault
|
||||
and have a basic understanding of how Vault works.
|
||||
|
||||
Otherwise, first read this guide on how to [get started with Vault](https://www.vaultproject.io/intro/getting-started/install.html).
|
||||
|
||||
To learn specifically about how plugins work, see documentation on [Vault plugins](https://www.vaultproject.io/docs/internals/plugins.html).
|
||||
|
||||
## Installation
|
||||
|
||||
This plugin is bundled in Vault version 1.4.0 or later. It may also be built and mounted externally
|
||||
with earlier versions of Vault. For details on this process please see the documentation for Vault's
|
||||
[plugin system](https://www.vaultproject.io/docs/internals/plugins.html).
|
||||
|
||||
## Setup
|
||||
|
||||
1. Enable the database secrets engine if it is not already enabled:
|
||||
|
||||
```text
|
||||
$ vault secrets enable database
|
||||
Success! Enabled the database secrets engine at: database/
|
||||
```
|
||||
|
||||
The secrets engine will be enabled at the default path which is name of the engine. To
|
||||
enable the secrets engine at a different path use the `-path` argument.
|
||||
|
||||
1. Configure Vault with the proper plugin and connection information:
|
||||
|
||||
```text
|
||||
$ vault write database/config/my-mongodbatlas-database \
|
||||
plugin_name=mongodbatlas-database-plugin \
|
||||
allowed_roles="my-role" \
|
||||
public_key="a-public-key" \
|
||||
private_key="a-private-key!" \
|
||||
project_id="a-project-id"
|
||||
```
|
||||
|
||||
2. Configure a role that maps a name in Vault to a MongoDB Atlas command that executes and
|
||||
creates the Database User credential:
|
||||
|
||||
```text
|
||||
$ vault write database/roles/my-role \
|
||||
db_name=my-mongodbatlas-database \
|
||||
creation_statements='{ "database_name": "admin", "roles": [{"databaseName":"admin","roleName":"atlasAdmin"}]}' \
|
||||
default_ttl="1h" \
|
||||
max_ttl="24h"
|
||||
Success! Data written to: database/roles/my-role
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
After the secrets engine is configured and a user/machine has a Vault token with
|
||||
the proper permissions, it can generate credentials.
|
||||
|
||||
1. Generate a new credential by reading from the `/creds` endpoint with the name
|
||||
of the role:
|
||||
|
||||
```text
|
||||
$ vault read database/creds/my-role
|
||||
Key Value
|
||||
--- -----
|
||||
lease_id database/creds/my-role/2f6a614c-4aa2-7b19-24b9-ad944a8d4de6
|
||||
lease_duration 1h
|
||||
lease_renewable true
|
||||
password A1a-QwxApKgnfCp1AJYN
|
||||
username v-5WFTBKdwOTLOqWLgsjvH-1565815206
|
||||
```
|
||||
|
||||
|
||||
For more details on configuring and using the plugin, refer to the [Database Secrets Engine for MongoDB Atlas](https://www.vaultproject.io/docs/secrets/databases/mongodbatlas.html)
|
||||
documentation.
|
||||
If you believe you have found a security issue in Vault or with this plugin, _please responsibly disclose_ by
|
||||
contacting HashiCorp at [security@hashicorp.com](mailto:security@hashicorp.com) and contact MongoDB
|
||||
directly via [security@mongodb.com](mailto:security@mongodb.com) or
|
||||
[open a ticket](https://jira.mongodb.org/plugins/servlet/samlsso?redirectTo=%2Fbrowse%2FSECURITY) (link is external).
|
||||
|
||||
4
vendor/github.com/hashicorp/vault-plugin-database-mongodbatlas/go.mod
generated
vendored
4
vendor/github.com/hashicorp/vault-plugin-database-mongodbatlas/go.mod
generated
vendored
@@ -6,8 +6,8 @@ require (
|
||||
github.com/Sectorbob/mlab-ns2 v0.0.0-20171030222938-d3aa0c295a8a
|
||||
github.com/go-test/deep v1.0.2 // indirect
|
||||
github.com/hashicorp/go-version v1.2.0 // indirect
|
||||
github.com/hashicorp/vault/api v1.0.5-0.20200317185738-82f498082f02
|
||||
github.com/hashicorp/vault/sdk v0.1.14-0.20200317185738-82f498082f02
|
||||
github.com/hashicorp/vault/api v1.0.5-0.20200215224050-f6547fa8e820
|
||||
github.com/hashicorp/vault/sdk v0.1.14-0.20200215224050-f6547fa8e820
|
||||
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d // indirect
|
||||
github.com/mitchellh/mapstructure v1.1.2
|
||||
github.com/mongodb/go-client-mongodb-atlas v0.1.2
|
||||
|
||||
8
vendor/github.com/hashicorp/vault-plugin-database-mongodbatlas/go.sum
generated
vendored
8
vendor/github.com/hashicorp/vault-plugin-database-mongodbatlas/go.sum
generated
vendored
@@ -75,12 +75,12 @@ github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hashicorp/vault/api v1.0.5-0.20200317185738-82f498082f02 h1:OGEV0U0+lb8SP5aZA1m456Sr3MYxFel2awVr55QRri0=
|
||||
github.com/hashicorp/vault/api v1.0.5-0.20200317185738-82f498082f02/go.mod h1:3f12BMfgDGjTsTtIUj+ZKZwSobQpZtYGFIEehOv5z1o=
|
||||
github.com/hashicorp/vault/api v1.0.5-0.20200215224050-f6547fa8e820 h1:biZidYDDEWnuOI9mXnJre8lwHKhb5ym85aSXk3oz/dc=
|
||||
github.com/hashicorp/vault/api v1.0.5-0.20200215224050-f6547fa8e820/go.mod h1:3f12BMfgDGjTsTtIUj+ZKZwSobQpZtYGFIEehOv5z1o=
|
||||
github.com/hashicorp/vault/sdk v0.1.14-0.20200215195600-2ca765f0a500 h1:tiMX2ewq4ble+e2zENzBvaH2dMoFHe80NbnrF5Ir9Kk=
|
||||
github.com/hashicorp/vault/sdk v0.1.14-0.20200215195600-2ca765f0a500/go.mod h1:WX57W2PwkrOPQ6rVQk+dy5/htHIaB4aBM70EwKThu10=
|
||||
github.com/hashicorp/vault/sdk v0.1.14-0.20200317185738-82f498082f02 h1:vVrOAVfunVvkTkE9iF3Fe1+PGPLwGIp3nP4qgHGrHFs=
|
||||
github.com/hashicorp/vault/sdk v0.1.14-0.20200317185738-82f498082f02/go.mod h1:WX57W2PwkrOPQ6rVQk+dy5/htHIaB4aBM70EwKThu10=
|
||||
github.com/hashicorp/vault/sdk v0.1.14-0.20200215224050-f6547fa8e820 h1:TmDZ1sS6gU0hFeFlFuyJVUwRPEzifZIHCBeS2WF2uSc=
|
||||
github.com/hashicorp/vault/sdk v0.1.14-0.20200215224050-f6547fa8e820/go.mod h1:WX57W2PwkrOPQ6rVQk+dy5/htHIaB4aBM70EwKThu10=
|
||||
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
|
||||
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ=
|
||||
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
|
||||
|
||||
10
vendor/github.com/hashicorp/vault-plugin-database-mongodbatlas/mongodbatlas.go
generated
vendored
10
vendor/github.com/hashicorp/vault-plugin-database-mongodbatlas/mongodbatlas.go
generated
vendored
@@ -35,9 +35,9 @@ func new() *MongoDBAtlas {
|
||||
connProducer.Type = mongoDBAtlasTypeName
|
||||
|
||||
credsProducer := &credsutil.SQLCredentialsProducer{
|
||||
DisplayNameLen: 15,
|
||||
DisplayNameLen: credsutil.NoneLength,
|
||||
RoleNameLen: 15,
|
||||
UsernameLen: 100,
|
||||
UsernameLen: 20,
|
||||
Separator: "-",
|
||||
}
|
||||
|
||||
@@ -149,12 +149,6 @@ func (m *MongoDBAtlas) SetCredentials(ctx context.Context, statements dbplugin.S
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
statements = dbutil.StatementCompatibilityHelper(statements)
|
||||
|
||||
if len(statements.Creation) == 0 {
|
||||
return "", "", dbutil.ErrEmptyCreationStatement
|
||||
}
|
||||
|
||||
client, err := m.getConnection(ctx)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
|
||||
2
vendor/modules.txt
vendored
2
vendor/modules.txt
vendored
@@ -431,7 +431,7 @@ github.com/hashicorp/vault-plugin-auth-kubernetes
|
||||
github.com/hashicorp/vault-plugin-auth-oci
|
||||
# github.com/hashicorp/vault-plugin-database-elasticsearch v0.5.4
|
||||
github.com/hashicorp/vault-plugin-database-elasticsearch
|
||||
# github.com/hashicorp/vault-plugin-database-mongodbatlas v0.1.1
|
||||
# github.com/hashicorp/vault-plugin-database-mongodbatlas v0.1.0-beta1.0.20200521152755-9cf156a44f9c
|
||||
github.com/hashicorp/vault-plugin-database-mongodbatlas
|
||||
# github.com/hashicorp/vault-plugin-secrets-ad v0.6.4-beta1.0.20200518124111-3dceeb3ce90e
|
||||
github.com/hashicorp/vault-plugin-secrets-ad/plugin
|
||||
|
||||
Reference in New Issue
Block a user