From 580e497f9d4ae95f2171cbc2a664ec86360d20bf Mon Sep 17 00:00:00 2001 From: Calvin Leung Huang Date: Mon, 8 May 2017 14:30:18 -0400 Subject: [PATCH 01/18] WIP on mongodb plugin --- .../mongodb/mongodb-database-plugin/main.go | 21 +++ plugins/database/mongodb/mongodb.go | 57 +++++++ plugins/helper/database/connutil/cassandra.go | 2 +- plugins/helper/database/connutil/mongodb.go | 159 ++++++++++++++++++ plugins/helper/database/credsutil/mongodb.go | 36 ++++ 5 files changed, 274 insertions(+), 1 deletion(-) create mode 100644 plugins/database/mongodb/mongodb-database-plugin/main.go create mode 100644 plugins/database/mongodb/mongodb.go create mode 100644 plugins/helper/database/connutil/mongodb.go create mode 100644 plugins/helper/database/credsutil/mongodb.go diff --git a/plugins/database/mongodb/mongodb-database-plugin/main.go b/plugins/database/mongodb/mongodb-database-plugin/main.go new file mode 100644 index 000000000000..cb62e2b0df2a --- /dev/null +++ b/plugins/database/mongodb/mongodb-database-plugin/main.go @@ -0,0 +1,21 @@ +package main + +import ( + "log" + "os" + + "github.com/hashicorp/vault-plugins/database/mongodb" + "github.com/hashicorp/vault/helper/pluginutil" +) + +func main() { + apiClientMeta := &pluginutil.APIClientMeta{} + flags := apiClientMeta.FlagSet() + flags.Parse(os.Args) + + err := mongodb.Run(apiClientMeta.GetTLSConfig()) + if err != nil { + log.Println(err) + os.Exit(1) + } +} diff --git a/plugins/database/mongodb/mongodb.go b/plugins/database/mongodb/mongodb.go new file mode 100644 index 000000000000..3a629e2bf8c5 --- /dev/null +++ b/plugins/database/mongodb/mongodb.go @@ -0,0 +1,57 @@ +package mongodb + +import ( + "github.com/hashicorp/vault/api" + "github.com/hashicorp/vault/plugins" + "github.com/hashicorp/vault/plugins/helper/database/connutil" + "github.com/hashicorp/vault/plugins/helper/database/credsutil" + "gopkg.in/mgo.v2" +) + +const mongoDBTypeName = "mongodb" + +// MongoDB is an implementation of Database interface +type MongoDB struct { + connutil.ConnectionProducer + credsutil.CredentialsProducer +} + +// New returns a new MongoDB instance +func New() (interface{}, error) { + connProducer := &connutil.MongoDBConnectionProducer{} + connProducer.Type = mongoDBTypeName + + credsProducer := &credsutil.MongoDBCredentialsProducer{} + + dbType := &MongoDB{ + ConnectionProducer: connProducer, + CredentialsProducer: credsProducer, + } + return dbType, nil +} + +// Run instantiates a MongoDB object, and runs the RPC server for the plugin +func Run(apiTLSConfig *api.TLSConfig) error { + dbType, err := New() + if err != nil { + return err + } + + plugins.Serve(dbType.(*MongoDB), apiTLSConfig) + + return nil +} + +// Type returns the TypeName for this backend +func (m *MongoDB) Type() (string, error) { + return mongoDBTypeName, nil +} + +func (m *MongoDB) getConnection() (*mgo.Session, error) { + session, err := m.Connection() + if err != nil { + return nil, err + } + + return session.(*mgo.Session), nil +} diff --git a/plugins/helper/database/connutil/cassandra.go b/plugins/helper/database/connutil/cassandra.go index 869c39e3b6dc..ea34cbbae403 100644 --- a/plugins/helper/database/connutil/cassandra.go +++ b/plugins/helper/database/connutil/cassandra.go @@ -102,7 +102,7 @@ func (c *CassandraConnectionProducer) Initialize(conf map[string]interface{}, ve if verifyConnection { if _, err := c.Connection(); err != nil { - return fmt.Errorf("error Initalizing Connection: %s", err) + return fmt.Errorf("error initalizing connection: %s", err) } } return nil diff --git a/plugins/helper/database/connutil/mongodb.go b/plugins/helper/database/connutil/mongodb.go new file mode 100644 index 000000000000..f9178fd65f17 --- /dev/null +++ b/plugins/helper/database/connutil/mongodb.go @@ -0,0 +1,159 @@ +package connutil + +import ( + "crypto/tls" + "errors" + "fmt" + "net" + "net/url" + "strconv" + "strings" + "sync" + "time" + + "github.com/mitchellh/mapstructure" + + "gopkg.in/mgo.v2" +) + +// MongoDBConnectionProducer implements ConnectionProducer and provides an +// interface for databases to make connections. +type MongoDBConnectionProducer struct { + URI string `json:"uri" structs:"uri" mapstructure:"uri"` + + Initialized bool + Type string + session *mgo.Session + sync.Mutex +} + +// Initialize parses connection configuration. +func (c *MongoDBConnectionProducer) Initialize(conf map[string]interface{}, verifyConnection bool) error { + c.Lock() + defer c.Unlock() + + err := mapstructure.Decode(conf, c) + if err != nil { + return err + } + + if len(c.URI) == 0 { + return fmt.Errorf("uri cannot be empty") + } + + c.Initialized = true + + if verifyConnection { + if _, err := c.Connection(); err != nil { + return fmt.Errorf("error initializing connection: %s", err) + } + } + return nil +} + +// Connection creates a database connection. +func (c *MongoDBConnectionProducer) Connection() (interface{}, error) { + if !c.Initialized { + return nil, errNotInitialized + } + + if c.session != nil { + return c.session, nil + } + + dialInfo, err := parseMongoURI(c.URI) + if err != nil { + return nil, err + } + + c.session, err = mgo.DialWithInfo(dialInfo) + if err != nil { + return nil, err + } + c.session.SetSyncTimeout(1 * time.Minute) + c.session.SetSocketTimeout(1 * time.Minute) + + return nil, nil +} + +// Close terminates the database connection. +func (c *MongoDBConnectionProducer) Close() error { + c.Lock() + defer c.Unlock() + + if c.session != nil { + c.session.Close() + } + + c.session = nil + + return nil +} + +func parseMongoURI(rawURI string) (*mgo.DialInfo, error) { + uri, err := url.Parse(rawURI) + if err != nil { + return nil, err + } + + info := mgo.DialInfo{ + Addrs: strings.Split(uri.Host, ","), + Database: strings.TrimPrefix(uri.Path, "/"), + Timeout: 10 * time.Second, + } + + if uri.User != nil { + info.Username = uri.User.Username() + info.Password, _ = uri.User.Password() + } + + query := uri.Query() + for key, values := range query { + var value string + if len(values) > 0 { + value = values[0] + } + + switch key { + case "authSource": + info.Source = value + case "authMechanism": + info.Mechanism = value + case "gssapiServiceName": + info.Service = value + case "replicaSet": + info.ReplicaSetName = value + case "maxPoolSize": + poolLimit, err := strconv.Atoi(value) + if err != nil { + return nil, errors.New("bad value for maxPoolSize: " + value) + } + info.PoolLimit = poolLimit + case "ssl": + // Unfortunately, mgo doesn't support the ssl parameter in its MongoDB URI parsing logic, so we have to handle that + // ourselves. See https://github.com/go-mgo/mgo/issues/84 + ssl, err := strconv.ParseBool(value) + if err != nil { + return nil, errors.New("bad value for ssl: " + value) + } + if ssl { + info.DialServer = func(addr *mgo.ServerAddr) (net.Conn, error) { + return tls.Dial("tcp", addr.String(), &tls.Config{}) + } + } + case "connect": + if value == "direct" { + info.Direct = true + break + } + if value == "replicaSet" { + break + } + fallthrough + default: + return nil, errors.New("unsupported connection URL option: " + key + "=" + value) + } + } + + return &info, nil +} diff --git a/plugins/helper/database/credsutil/mongodb.go b/plugins/helper/database/credsutil/mongodb.go new file mode 100644 index 000000000000..597efc3aee17 --- /dev/null +++ b/plugins/helper/database/credsutil/mongodb.go @@ -0,0 +1,36 @@ +package credsutil + +import ( + "fmt" + "time" + + uuid "github.com/hashicorp/go-uuid" +) + +// MongoDBCredentialsProducer implements CredentialsProducer and provides an +// interface for databases to generate user information. +type MongoDBCredentialsProducer struct{} + +func (cp *MongoDBCredentialsProducer) GenerateUsername(displayName string) (string, error) { + userUUID, err := uuid.GenerateUUID() + if err != nil { + return "", err + } + + username := fmt.Sprintf("vault-%s-%s", displayName, userUUID) + + return username, nil +} + +func (cp *MongoDBCredentialsProducer) GeneratePassword() (string, error) { + password, err := uuid.GenerateUUID() + if err != nil { + return "", err + } + + return password, nil +} + +func (cp *MongoDBCredentialsProducer) GenerateExpiration(ttl time.Time) (string, error) { + return "", nil +} From 059d1d9d1b04ab7b96fa9421f3874d13ec721115 Mon Sep 17 00:00:00 2001 From: Calvin Leung Huang Date: Mon, 8 May 2017 17:18:22 -0400 Subject: [PATCH 02/18] Add mongodb plugin --- plugins/database/mongodb/mongodb.go | 100 ++++++++++++++++++++++++++++ plugins/database/mongodb/util.go | 40 +++++++++++ 2 files changed, 140 insertions(+) create mode 100644 plugins/database/mongodb/util.go diff --git a/plugins/database/mongodb/mongodb.go b/plugins/database/mongodb/mongodb.go index 3a629e2bf8c5..aa1bd34b6a1e 100644 --- a/plugins/database/mongodb/mongodb.go +++ b/plugins/database/mongodb/mongodb.go @@ -1,7 +1,15 @@ package mongodb import ( + "time" + + "encoding/json" + + "fmt" + + "github.com/hashicorp/vault-plugins/helper/database/dbutil" "github.com/hashicorp/vault/api" + "github.com/hashicorp/vault/builtin/logical/database/dbplugin" "github.com/hashicorp/vault/plugins" "github.com/hashicorp/vault/plugins/helper/database/connutil" "github.com/hashicorp/vault/plugins/helper/database/credsutil" @@ -55,3 +63,95 @@ func (m *MongoDB) getConnection() (*mgo.Session, error) { return session.(*mgo.Session), nil } + +// CreateUser generates the username/password on the underlying secret backend as instructed by +// the CreationStatement provided. The creation statement JSON that has a db value, and an array +// of roles that accept an optional db value. This array will be normalized the format specified +// in the mongoDB docs: https://docs.mongodb.com/manual/reference/command/createUser/#dbcmd.createUser +func (m *MongoDB) CreateUser(statements dbplugin.Statements, usernamePrefix string, expiration time.Time) (username string, password string, err error) { + // Grab the lock + m.Lock() + defer m.Unlock() + + session, err := m.getConnection() + if err != nil { + return "", "", err + } + if statements.CreationStatements == "" { + return "", "", dbutil.ErrEmptyCreationStatement + } + + username, err = m.GenerateUsername(usernamePrefix) + if err != nil { + return "", "", err + } + + password, err = m.GeneratePassword() + if err != nil { + return "", "", err + } + + // Unmarshal statements.CreationStatements into mongodbRoles + var mongoCS mongoDBStatement + err = json.Unmarshal([]byte(statements.CreationStatements), &mongoCS) + if err != nil { + return "", "", err + } + + // Check for db string + if mongoCS.DB == "" { + return "", "", fmt.Errorf("db value is required in creation statement") + } + + if len(mongoCS.Roles) == 0 { + return "", "", fmt.Errorf("roles array is required in creation statement") + } + + createUserCmd := createUserCommand{ + Username: username, + Password: password, + Roles: mongoCS.Roles.toStandardRolesArray(), + } + + err = session.DB(mongoCS.DB).Run(createUserCmd, nil) + if err != nil { + return "", "", err + } + + return username, password, nil +} + +// RenewUser is not supported on mongoDB, so this is a no-op. +func (m *MongoDB) RenewUser(statements dbplugin.Statements, username string, expiration time.Time) error { + // NOOP + return nil +} + +// RevokeUser drops the specified user from the authentication databse. If none is provided +// in the revocation statement, the default "admin" authentication database will be assumed. +func (m *MongoDB) RevokeUser(statements dbplugin.Statements, username string) error { + session, err := m.getConnection() + if err != nil { + return err + } + + // Unmarshal statements.RevocationStatements into mongodbRoles + var mongoCS mongoDBStatement + err = json.Unmarshal([]byte(statements.RevocationStatements), &mongoCS) + if err != nil { + return err + } + + db := mongoCS.DB + // If db is not specified, use the default authenticationDatabase "admin" + if db == "" { + db = "admin" + } + + err = session.DB(db).RemoveUser(username) + if err != nil && err != mgo.ErrNotFound { + return err + } + + return nil +} diff --git a/plugins/database/mongodb/util.go b/plugins/database/mongodb/util.go new file mode 100644 index 000000000000..cc00368682d7 --- /dev/null +++ b/plugins/database/mongodb/util.go @@ -0,0 +1,40 @@ +package mongodb + +type createUserCommand struct { + Username string `bson:"createUser"` + Password string `bson:"pwd"` + Roles []interface{} `bson:"roles"` +} +type mongodbRole struct { + Role string `json:"role" bson:"role"` + DB string `json:"db" bson:"db"` +} + +type mongodbRoles []mongodbRole + +type mongoDBStatement struct { + DB string `json:"db"` + Roles mongodbRoles `json:"roles"` +} + +func (roles mongodbRoles) toStandardRolesArray() []interface{} { + // Convert array of role documents like: + // + // [ { "role": "readWrite" }, { "role": "readWrite", "db": "test" } ] + // + // into a "standard" MongoDB roles array containing both strings and role documents: + // + // [ "readWrite", { "role": "readWrite", "db": "test" } ] + // + // MongoDB's createUser command accepts the latter. + // + var standardRolesArray []interface{} + for _, role := range roles { + if role.DB == "" { + standardRolesArray = append(standardRolesArray, role.Role) + } else { + standardRolesArray = append(standardRolesArray, role) + } + } + return standardRolesArray +} From 981a41adc2a10522a36d7ee2a8a44e075c2937a2 Mon Sep 17 00:00:00 2001 From: Calvin Leung Huang Date: Tue, 9 May 2017 11:20:55 -0400 Subject: [PATCH 03/18] Add tests --- plugins/database/mongodb/mongodb.go | 10 +- plugins/database/mongodb/mongodb_test.go | 184 +++++++++++++++++++++++ 2 files changed, 192 insertions(+), 2 deletions(-) create mode 100644 plugins/database/mongodb/mongodb_test.go diff --git a/plugins/database/mongodb/mongodb.go b/plugins/database/mongodb/mongodb.go index aa1bd34b6a1e..0e7f833c1fe4 100644 --- a/plugins/database/mongodb/mongodb.go +++ b/plugins/database/mongodb/mongodb.go @@ -135,9 +135,15 @@ func (m *MongoDB) RevokeUser(statements dbplugin.Statements, username string) er return err } - // Unmarshal statements.RevocationStatements into mongodbRoles + // If no revocation statements provided, pass in empty JSON + revocationStatement := statements.RevocationStatements + if revocationStatement == "" { + revocationStatement = `{}` + } + + // Unmarshal revocation statements into mongodbRoles var mongoCS mongoDBStatement - err = json.Unmarshal([]byte(statements.RevocationStatements), &mongoCS) + err = json.Unmarshal([]byte(revocationStatement), &mongoCS) if err != nil { return err } diff --git a/plugins/database/mongodb/mongodb_test.go b/plugins/database/mongodb/mongodb_test.go new file mode 100644 index 000000000000..982874c9f7c1 --- /dev/null +++ b/plugins/database/mongodb/mongodb_test.go @@ -0,0 +1,184 @@ +package mongodb + +import ( + "fmt" + "os" + "testing" + "time" + + mgo "gopkg.in/mgo.v2" + + "strings" + + "github.com/calvn/dockertest" + "github.com/hashicorp/vault/builtin/logical/database/dbplugin" + "github.com/hashicorp/vault/plugins/helper/database/connutil" +) + +const testMongoDBRole = `{ "db": "admin", "roles": [ { "role": "readWrite" } ] }` + +func prepareMongoDBTestContainer(t *testing.T) (cleanup func(), retURI string) { + if os.Getenv("MONGODB_URI") != "" { + return func() {}, os.Getenv("MONGODB_URI") + } + + pool, err := dockertest.NewPool("") + if err != nil { + t.Fatalf("Failed to connect to docker: %s", err) + } + + resource, err := pool.Run("mongo", "latest", []string{}) + if err != nil { + t.Fatalf("Could not start local mongo docker container: %s", err) + } + + cleanup = func() { + err := pool.Purge(resource) + if err != nil { + t.Fatalf("Failed to cleanup local container: %s", err) + } + } + + retURI = fmt.Sprintf("mongodb://127.0.0.1:%s", resource.GetPort("27017/tcp")) + + // exponential backoff-retry + if err = pool.Retry(func() error { + var err error + dialInfo, err := connutil.ParseMongoURI(retURI) + if err != nil { + return err + } + + session, err := mgo.DialWithInfo(dialInfo) + if err != nil { + return err + } + session.SetSyncTimeout(1 * time.Minute) + session.SetSocketTimeout(1 * time.Minute) + return session.Ping() + }); err != nil { + t.Fatalf("Could not connect to mongo docker container: %s", err) + } + + return +} + +func TestMongoDB_Initialize(t *testing.T) { + cleanup, connURI := prepareMongoDBTestContainer(t) + defer cleanup() + + connectionDetails := map[string]interface{}{ + "uri": connURI, + } + + dbRaw, err := New() + if err != nil { + t.Fatalf("err: %s", err) + } + db := dbRaw.(*MongoDB) + connProducer := db.ConnectionProducer.(*connutil.MongoDBConnectionProducer) + + err = db.Initialize(connectionDetails, true) + if err != nil { + t.Fatalf("err: %s", err) + } + + if !connProducer.Initialized { + t.Fatal("Database should be initialized") + } + + err = db.Close() + if err != nil { + t.Fatalf("err: %s", err) + } +} + +func TestMongoDB_CreateUser(t *testing.T) { + cleanup, connURI := prepareMongoDBTestContainer(t) + defer cleanup() + + connectionDetails := map[string]interface{}{ + "uri": connURI, + } + + dbRaw, err := New() + if err != nil { + t.Fatalf("err: %s", err) + } + db := dbRaw.(*MongoDB) + err = db.Initialize(connectionDetails, true) + if err != nil { + t.Fatalf("err: %s", err) + } + + statements := dbplugin.Statements{ + CreationStatements: testMongoDBRole, + } + + username, password, err := db.CreateUser(statements, "test", time.Now().Add(time.Minute)) + if err != nil { + t.Fatalf("err: %s", err) + } + + if err := testCredsExist(t, connURI, username, password); err != nil { + t.Fatalf("Could not connect with new credentials: %s", err) + } +} + +func TestMongoDB_RevokeUser(t *testing.T) { + cleanup, connURI := prepareMongoDBTestContainer(t) + defer cleanup() + + connectionDetails := map[string]interface{}{ + "uri": connURI, + } + + dbRaw, err := New() + if err != nil { + t.Fatalf("err: %s", err) + } + db := dbRaw.(*MongoDB) + err = db.Initialize(connectionDetails, true) + if err != nil { + t.Fatalf("err: %s", err) + } + + statements := dbplugin.Statements{ + CreationStatements: testMongoDBRole, + } + + username, password, err := db.CreateUser(statements, "test", time.Now().Add(time.Minute)) + if err != nil { + t.Fatalf("err: %s", err) + } + + if err := testCredsExist(t, connURI, username, password); err != nil { + t.Fatalf("Could not connect with new credentials: %s", err) + } + + // Test default revocation statememt + err = db.RevokeUser(statements, username) + if err != nil { + t.Fatalf("err: %s", err) + } + + if err = testCredsExist(t, connURI, username, password); err == nil { + t.Fatal("Credentials were not revoked") + } +} + +func testCredsExist(t testing.TB, connURI, username, password string) error { + connURI = strings.Replace(connURI, "mongodb://", fmt.Sprintf("mongodb://%s:%s@", username, password), 1) + dialInfo, err := connutil.ParseMongoURI(connURI) + if err != nil { + return err + } + + session, err := mgo.DialWithInfo(dialInfo) + if err != nil { + return err + } + session.SetSyncTimeout(1 * time.Minute) + session.SetSocketTimeout(1 * time.Minute) + return session.Ping() +} From b468e7400fa96cbc8265db3cc37aab53d423f55e Mon Sep 17 00:00:00 2001 From: Calvin Leung Huang Date: Tue, 9 May 2017 11:32:55 -0400 Subject: [PATCH 04/18] Update mongodb.CreateUser() comment --- plugins/database/mongodb/mongodb.go | 10 +++++++--- plugins/helper/database/connutil/mongodb.go | 4 ++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/plugins/database/mongodb/mongodb.go b/plugins/database/mongodb/mongodb.go index 0e7f833c1fe4..b77836a2edfd 100644 --- a/plugins/database/mongodb/mongodb.go +++ b/plugins/database/mongodb/mongodb.go @@ -65,9 +65,13 @@ func (m *MongoDB) getConnection() (*mgo.Session, error) { } // CreateUser generates the username/password on the underlying secret backend as instructed by -// the CreationStatement provided. The creation statement JSON that has a db value, and an array -// of roles that accept an optional db value. This array will be normalized the format specified -// in the mongoDB docs: https://docs.mongodb.com/manual/reference/command/createUser/#dbcmd.createUser +// the CreationStatement provided. The creation statement is a JSON blob that has a db value, +// and an array of roles that accepts a role, and an optional db value pair. This array will +// be normalized the format specified in the mongoDB docs: +// https://docs.mongodb.com/manual/reference/command/createUser/#dbcmd.createUser +// +// JSON Example: +// { "db": "admin", "roles": [{ "role": "readWrite" }, {"role": "read", "db": "foo"}] } func (m *MongoDB) CreateUser(statements dbplugin.Statements, usernamePrefix string, expiration time.Time) (username string, password string, err error) { // Grab the lock m.Lock() diff --git a/plugins/helper/database/connutil/mongodb.go b/plugins/helper/database/connutil/mongodb.go index f9178fd65f17..1279f53b100a 100644 --- a/plugins/helper/database/connutil/mongodb.go +++ b/plugins/helper/database/connutil/mongodb.go @@ -61,7 +61,7 @@ func (c *MongoDBConnectionProducer) Connection() (interface{}, error) { return c.session, nil } - dialInfo, err := parseMongoURI(c.URI) + dialInfo, err := ParseMongoURI(c.URI) if err != nil { return nil, err } @@ -90,7 +90,7 @@ func (c *MongoDBConnectionProducer) Close() error { return nil } -func parseMongoURI(rawURI string) (*mgo.DialInfo, error) { +func ParseMongoURI(rawURI string) (*mgo.DialInfo, error) { uri, err := url.Parse(rawURI) if err != nil { return nil, err From 814e6918680688075cb583322fbaec64054205bf Mon Sep 17 00:00:00 2001 From: Calvin Leung Huang Date: Tue, 9 May 2017 12:03:27 -0400 Subject: [PATCH 05/18] Update docs --- website/source/api/secret/databases/cassandra.html.md | 2 +- website/source/api/secret/databases/mssql.html.md | 2 +- website/source/api/secret/databases/postgresql.html.md | 2 +- website/source/layouts/api.erb | 5 ++++- website/source/layouts/docs.erb | 5 ++++- 5 files changed, 11 insertions(+), 5 deletions(-) diff --git a/website/source/api/secret/databases/cassandra.html.md b/website/source/api/secret/databases/cassandra.html.md index 5e2b5a83603b..72a2e18845f7 100644 --- a/website/source/api/secret/databases/cassandra.html.md +++ b/website/source/api/secret/databases/cassandra.html.md @@ -1,7 +1,7 @@ --- layout: "api" page_title: "Cassandra Database Plugin - HTTP API" -sidebar_current: "docs-http-secret-databases-cassandra-maria" +sidebar_current: "docs-http-secret-databases-cassandra" description: |- The Cassandra plugin for Vault's Database backend generates database credentials to access Cassandra servers. --- diff --git a/website/source/api/secret/databases/mssql.html.md b/website/source/api/secret/databases/mssql.html.md index d4b120e8d62c..75b44b60d205 100644 --- a/website/source/api/secret/databases/mssql.html.md +++ b/website/source/api/secret/databases/mssql.html.md @@ -1,7 +1,7 @@ --- layout: "api" page_title: "MSSQL Database Plugin - HTTP API" -sidebar_current: "docs-http-secret-databases-mssql-maria" +sidebar_current: "docs-http-secret-databases-mssql" description: |- The MSSQL plugin for Vault's Database backend generates database credentials to access MSSQL servers. --- diff --git a/website/source/api/secret/databases/postgresql.html.md b/website/source/api/secret/databases/postgresql.html.md index a1aaeee1c65a..27844bda96cc 100644 --- a/website/source/api/secret/databases/postgresql.html.md +++ b/website/source/api/secret/databases/postgresql.html.md @@ -1,7 +1,7 @@ --- layout: "api" page_title: "PostgreSQL Database Plugin - HTTP API" -sidebar_current: "docs-http-secret-databases-postgresql-maria" +sidebar_current: "docs-http-secret-databases-postgresql" description: |- The PostgreSQL plugin for Vault's Database backend generates database credentials to access PostgreSQL servers. --- diff --git a/website/source/layouts/api.erb b/website/source/layouts/api.erb index 799e461d0c2f..016aa4265c4a 100644 --- a/website/source/layouts/api.erb +++ b/website/source/layouts/api.erb @@ -36,6 +36,9 @@ > Cassandra + > + MongoDB + > MSSQL @@ -52,7 +55,7 @@ Generic > - MongoDB + MongoDB (Deprecated) > MSSQL (Deprecated) diff --git a/website/source/layouts/docs.erb b/website/source/layouts/docs.erb index ac6d96cbb80c..5c8a8bac69bc 100644 --- a/website/source/layouts/docs.erb +++ b/website/source/layouts/docs.erb @@ -225,6 +225,9 @@ > Cassandra + > + MongoDB + > MSSQL @@ -245,7 +248,7 @@ > - MongoDB + MongoDB (Deprecated) > From 7c0bad8bbff689050ffd75a940fc34c56505ace3 Mon Sep 17 00:00:00 2001 From: Calvin Leung Huang Date: Tue, 9 May 2017 12:04:08 -0400 Subject: [PATCH 06/18] Add missing docs --- .../api/secret/databases/mongodb.html.md | 46 +++++++++++++++ .../docs/secrets/databases/mongodb.html.md | 58 +++++++++++++++++++ 2 files changed, 104 insertions(+) create mode 100644 website/source/api/secret/databases/mongodb.html.md create mode 100644 website/source/docs/secrets/databases/mongodb.html.md diff --git a/website/source/api/secret/databases/mongodb.html.md b/website/source/api/secret/databases/mongodb.html.md new file mode 100644 index 000000000000..1e33513906fd --- /dev/null +++ b/website/source/api/secret/databases/mongodb.html.md @@ -0,0 +1,46 @@ +--- +layout: "api" +page_title: "MongoDB Database Plugin - HTTP API" +sidebar_current: "docs-http-secret-databases-mongodb" +description: |- + The MongoDB plugin for Vault's Database backend generates database credentials to access MongoDB servers. +--- + +# Cassandra Database Plugin HTTP API + +The MongoDB Database Plugin is one of the supported plugins for the Database +backend. This plugin generates database credentials dynamically based on +configured roles for the MongoDB database. + +## Configure Connection + +In addition to the parameters defined by the [Database +Backend](/api/secret/databases/index.html#configure-connection), this plugin +has a number of parameters to further configure a connection. + +| Method | Path | Produces | +| :------- | :--------------------------- | :--------------------- | +| `POST` | `/database/config/:name` | `204 (empty body)` | + +### Parameters +- `url` `(string: )` – Specifies the MongoDB standard connection string (URI). + +### Sample Payload + +```json +{ + "plugin_name": "mongodb-database-plugin", + "allowed_roles": "readonly", + "uri": "mongodb://admin:Password!@mongodb.acme.com:27017/admin?ssl=true" +} +``` + +### Sample Request + +``` +$ curl \ + --header "X-Vault-Token: ..." \ + --request POST \ + --data @payload.json \ + https://vault.rocks/v1/database/config/mongodb +``` diff --git a/website/source/docs/secrets/databases/mongodb.html.md b/website/source/docs/secrets/databases/mongodb.html.md new file mode 100644 index 000000000000..869c8dd43153 --- /dev/null +++ b/website/source/docs/secrets/databases/mongodb.html.md @@ -0,0 +1,58 @@ +--- +layout: "docs" +page_title: "MongoDB Database Plugin" +sidebar_current: "docs-secrets-databases-mongodb" +description: |- + The MongoDB plugin for Vault's Database backend generates database credentials to access MongoDB. +--- + +# Cassandra Database Plugin + +Name: `mongodb-database-plugin` + +The MongoDB Database Plugin is one of the supported plugins for the Database +backend. This plugin generates database credentials dynamically based on +configured roles for the MongoDB database. + +See the [Database Backend](/docs/secrets/databases/index.html) docs for more +information about setting up the Database Backend. + +## Quick Start + +After the Database Backend is mounted you can configure a MongoDB connection +by specifying this plugin as the `"plugin_name"` argument. Here is an example +MongoDB configuration: + +``` +$ vault write database/config/mongodb \ + plugin_name=mongodb-database-plugin \ + allowed_roles="readonly" \ + uri="mongodb://admin:Password!@mongodb.acme.com:27017/admin?ssl=true" + +The following warnings were returned from the Vault server: +* Read access to this endpoint should be controlled via ACLs as it will return the connection details as is, including passwords, if any. +``` + +Once the MongoDB connection is configured we can add a role: + +``` +$ vault write database/roles/readonly \ + db_name=mongodb \ + creation_statements='{ "db": "admin", "roles": [{ "role": "readWrite" }, {"role": "read", "db": "foo"}] }' \ + default_ttl="1h" \ + max_ttl="24h" + +Success! Data written to: database/roles/readonly +``` + +This role can be used to retrieve a new set of credentials by querying the +"database/creds/readonly" endpoint. + +## API + +The full list of configurable options can be seen in the [MongoDB database +plugin API](/api/secret/databases/mongodb.html) page. + +For more information on the Database secret backend's HTTP API please see the [Database secret +backend API](/api/secret/databases/index.html) page. + From 5bbc74ec99d3a78909c3f7a12faf4cc0002870c3 Mon Sep 17 00:00:00 2001 From: Calvin Leung Huang Date: Tue, 9 May 2017 12:11:53 -0400 Subject: [PATCH 07/18] Fix mongodb docs --- website/source/api/secret/databases/mongodb.html.md | 2 +- website/source/docs/secrets/databases/mongodb.html.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/website/source/api/secret/databases/mongodb.html.md b/website/source/api/secret/databases/mongodb.html.md index 1e33513906fd..c9f197947b90 100644 --- a/website/source/api/secret/databases/mongodb.html.md +++ b/website/source/api/secret/databases/mongodb.html.md @@ -6,7 +6,7 @@ description: |- The MongoDB plugin for Vault's Database backend generates database credentials to access MongoDB servers. --- -# Cassandra Database Plugin HTTP API +# MongoDB Database Plugin HTTP API The MongoDB Database Plugin is one of the supported plugins for the Database backend. This plugin generates database credentials dynamically based on diff --git a/website/source/docs/secrets/databases/mongodb.html.md b/website/source/docs/secrets/databases/mongodb.html.md index 869c8dd43153..2b5e92ad76db 100644 --- a/website/source/docs/secrets/databases/mongodb.html.md +++ b/website/source/docs/secrets/databases/mongodb.html.md @@ -6,7 +6,7 @@ description: |- The MongoDB plugin for Vault's Database backend generates database credentials to access MongoDB. --- -# Cassandra Database Plugin +# MongoDB Database Plugin Name: `mongodb-database-plugin` From adfa25e2c995aa5269c144428d6d6d5fb676932b Mon Sep 17 00:00:00 2001 From: Calvin Leung Huang Date: Tue, 9 May 2017 12:15:14 -0400 Subject: [PATCH 08/18] Minor comment and test updates --- plugins/database/mongodb/mongodb.go | 2 +- plugins/database/mongodb/mongodb_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/database/mongodb/mongodb.go b/plugins/database/mongodb/mongodb.go index b77836a2edfd..581ef1413091 100644 --- a/plugins/database/mongodb/mongodb.go +++ b/plugins/database/mongodb/mongodb.go @@ -125,7 +125,7 @@ func (m *MongoDB) CreateUser(statements dbplugin.Statements, usernamePrefix stri return username, password, nil } -// RenewUser is not supported on mongoDB, so this is a no-op. +// RenewUser is not supported on MongoDB, so this is a no-op. func (m *MongoDB) RenewUser(statements dbplugin.Statements, username string, expiration time.Time) error { // NOOP return nil diff --git a/plugins/database/mongodb/mongodb_test.go b/plugins/database/mongodb/mongodb_test.go index 982874c9f7c1..4c54ae2cfe2d 100644 --- a/plugins/database/mongodb/mongodb_test.go +++ b/plugins/database/mongodb/mongodb_test.go @@ -39,7 +39,7 @@ func prepareMongoDBTestContainer(t *testing.T) (cleanup func(), retURI string) { } } - retURI = fmt.Sprintf("mongodb://127.0.0.1:%s", resource.GetPort("27017/tcp")) + retURI = fmt.Sprintf("mongodb://localhost:%s", resource.GetPort("27017/tcp")) // exponential backoff-retry if err = pool.Retry(func() error { From 510729eed75890814795982b1df2a81d4c0550f1 Mon Sep 17 00:00:00 2001 From: Calvin Leung Huang Date: Tue, 9 May 2017 12:23:40 -0400 Subject: [PATCH 09/18] Fix imports --- plugins/database/mongodb/mongodb-database-plugin/main.go | 2 +- plugins/database/mongodb/mongodb.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/database/mongodb/mongodb-database-plugin/main.go b/plugins/database/mongodb/mongodb-database-plugin/main.go index cb62e2b0df2a..30db69a12e94 100644 --- a/plugins/database/mongodb/mongodb-database-plugin/main.go +++ b/plugins/database/mongodb/mongodb-database-plugin/main.go @@ -4,8 +4,8 @@ import ( "log" "os" - "github.com/hashicorp/vault-plugins/database/mongodb" "github.com/hashicorp/vault/helper/pluginutil" + "github.com/hashicorp/vault/plugins/database/mongodb" ) func main() { diff --git a/plugins/database/mongodb/mongodb.go b/plugins/database/mongodb/mongodb.go index 581ef1413091..296b9e66743d 100644 --- a/plugins/database/mongodb/mongodb.go +++ b/plugins/database/mongodb/mongodb.go @@ -7,12 +7,12 @@ import ( "fmt" - "github.com/hashicorp/vault-plugins/helper/database/dbutil" "github.com/hashicorp/vault/api" "github.com/hashicorp/vault/builtin/logical/database/dbplugin" "github.com/hashicorp/vault/plugins" "github.com/hashicorp/vault/plugins/helper/database/connutil" "github.com/hashicorp/vault/plugins/helper/database/credsutil" + "github.com/hashicorp/vault/plugins/helper/database/dbutil" "gopkg.in/mgo.v2" ) From 592c0cf69cf43f239073c6187430d87435b18cb3 Mon Sep 17 00:00:00 2001 From: Calvin Leung Huang Date: Tue, 9 May 2017 12:55:45 -0400 Subject: [PATCH 10/18] Fix dockertest import --- plugins/database/mongodb/mongodb_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/database/mongodb/mongodb_test.go b/plugins/database/mongodb/mongodb_test.go index 4c54ae2cfe2d..ec34db8743e3 100644 --- a/plugins/database/mongodb/mongodb_test.go +++ b/plugins/database/mongodb/mongodb_test.go @@ -10,9 +10,9 @@ import ( "strings" - "github.com/calvn/dockertest" "github.com/hashicorp/vault/builtin/logical/database/dbplugin" "github.com/hashicorp/vault/plugins/helper/database/connutil" + dockertest "gopkg.in/ory-am/dockertest.v3" ) const testMongoDBRole = `{ "db": "admin", "roles": [ { "role": "readWrite" } ] }` From 539b19e487fa9b5c0c79681571c4cede085be75c Mon Sep 17 00:00:00 2001 From: Calvin Leung Huang Date: Tue, 9 May 2017 13:31:46 -0400 Subject: [PATCH 11/18] Set c.Initialized at the end, check for empty CreationStmts first on CreateUser --- plugins/database/mongodb/mongodb.go | 7 ++++--- plugins/helper/database/connutil/cassandra.go | 4 +++- plugins/helper/database/connutil/mongodb.go | 5 +++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/plugins/database/mongodb/mongodb.go b/plugins/database/mongodb/mongodb.go index 296b9e66743d..ae67648ea3e3 100644 --- a/plugins/database/mongodb/mongodb.go +++ b/plugins/database/mongodb/mongodb.go @@ -77,13 +77,14 @@ func (m *MongoDB) CreateUser(statements dbplugin.Statements, usernamePrefix stri m.Lock() defer m.Unlock() + if statements.CreationStatements == "" { + return "", "", dbutil.ErrEmptyCreationStatement + } + session, err := m.getConnection() if err != nil { return "", "", err } - if statements.CreationStatements == "" { - return "", "", dbutil.ErrEmptyCreationStatement - } username, err = m.GenerateUsername(usernamePrefix) if err != nil { diff --git a/plugins/helper/database/connutil/cassandra.go b/plugins/helper/database/connutil/cassandra.go index ea34cbbae403..dc1195caf77b 100644 --- a/plugins/helper/database/connutil/cassandra.go +++ b/plugins/helper/database/connutil/cassandra.go @@ -49,7 +49,6 @@ func (c *CassandraConnectionProducer) Initialize(conf map[string]interface{}, ve if err != nil { return err } - c.Initialized = true if c.ConnectTimeoutRaw == nil { c.ConnectTimeoutRaw = "0s" @@ -105,6 +104,9 @@ func (c *CassandraConnectionProducer) Initialize(conf map[string]interface{}, ve return fmt.Errorf("error initalizing connection: %s", err) } } + + c.Initialized = true + return nil } diff --git a/plugins/helper/database/connutil/mongodb.go b/plugins/helper/database/connutil/mongodb.go index 1279f53b100a..567a699a9e33 100644 --- a/plugins/helper/database/connutil/mongodb.go +++ b/plugins/helper/database/connutil/mongodb.go @@ -41,13 +41,14 @@ func (c *MongoDBConnectionProducer) Initialize(conf map[string]interface{}, veri return fmt.Errorf("uri cannot be empty") } - c.Initialized = true - if verifyConnection { if _, err := c.Connection(); err != nil { return fmt.Errorf("error initializing connection: %s", err) } } + + c.Initialized = true + return nil } From 36467650efa1007bc78990b739f1273877021eb6 Mon Sep 17 00:00:00 2001 From: Calvin Leung Huang Date: Tue, 9 May 2017 14:21:07 -0400 Subject: [PATCH 12/18] Remove Initialized check on Connection() --- plugins/database/mongodb/mongodb_test.go | 34 +++++++++---------- plugins/helper/database/connutil/cassandra.go | 4 --- plugins/helper/database/connutil/mongodb.go | 28 +++++++-------- 3 files changed, 29 insertions(+), 37 deletions(-) diff --git a/plugins/database/mongodb/mongodb_test.go b/plugins/database/mongodb/mongodb_test.go index ec34db8743e3..41d16318f536 100644 --- a/plugins/database/mongodb/mongodb_test.go +++ b/plugins/database/mongodb/mongodb_test.go @@ -17,9 +17,9 @@ import ( const testMongoDBRole = `{ "db": "admin", "roles": [ { "role": "readWrite" } ] }` -func prepareMongoDBTestContainer(t *testing.T) (cleanup func(), retURI string) { - if os.Getenv("MONGODB_URI") != "" { - return func() {}, os.Getenv("MONGODB_URI") +func prepareMongoDBTestContainer(t *testing.T) (cleanup func(), retURL string) { + if os.Getenv("MONGODB_URL") != "" { + return func() {}, os.Getenv("MONGODB_URL") } pool, err := dockertest.NewPool("") @@ -39,12 +39,12 @@ func prepareMongoDBTestContainer(t *testing.T) (cleanup func(), retURI string) { } } - retURI = fmt.Sprintf("mongodb://localhost:%s", resource.GetPort("27017/tcp")) + retURL = fmt.Sprintf("mongodb://localhost:%s", resource.GetPort("27017/tcp")) // exponential backoff-retry if err = pool.Retry(func() error { var err error - dialInfo, err := connutil.ParseMongoURI(retURI) + dialInfo, err := connutil.ParseMongoURL(retURL) if err != nil { return err } @@ -64,11 +64,11 @@ func prepareMongoDBTestContainer(t *testing.T) (cleanup func(), retURI string) { } func TestMongoDB_Initialize(t *testing.T) { - cleanup, connURI := prepareMongoDBTestContainer(t) + cleanup, connURL := prepareMongoDBTestContainer(t) defer cleanup() connectionDetails := map[string]interface{}{ - "uri": connURI, + "connection_url": connURL, } dbRaw, err := New() @@ -94,11 +94,11 @@ func TestMongoDB_Initialize(t *testing.T) { } func TestMongoDB_CreateUser(t *testing.T) { - cleanup, connURI := prepareMongoDBTestContainer(t) + cleanup, connURL := prepareMongoDBTestContainer(t) defer cleanup() connectionDetails := map[string]interface{}{ - "uri": connURI, + "connection_url": connURL, } dbRaw, err := New() @@ -120,17 +120,17 @@ func TestMongoDB_CreateUser(t *testing.T) { t.Fatalf("err: %s", err) } - if err := testCredsExist(t, connURI, username, password); err != nil { + if err := testCredsExist(t, connURL, username, password); err != nil { t.Fatalf("Could not connect with new credentials: %s", err) } } func TestMongoDB_RevokeUser(t *testing.T) { - cleanup, connURI := prepareMongoDBTestContainer(t) + cleanup, connURL := prepareMongoDBTestContainer(t) defer cleanup() connectionDetails := map[string]interface{}{ - "uri": connURI, + "connection_url": connURL, } dbRaw, err := New() @@ -152,7 +152,7 @@ func TestMongoDB_RevokeUser(t *testing.T) { t.Fatalf("err: %s", err) } - if err := testCredsExist(t, connURI, username, password); err != nil { + if err := testCredsExist(t, connURL, username, password); err != nil { t.Fatalf("Could not connect with new credentials: %s", err) } @@ -162,14 +162,14 @@ func TestMongoDB_RevokeUser(t *testing.T) { t.Fatalf("err: %s", err) } - if err = testCredsExist(t, connURI, username, password); err == nil { + if err = testCredsExist(t, connURL, username, password); err == nil { t.Fatal("Credentials were not revoked") } } -func testCredsExist(t testing.TB, connURI, username, password string) error { - connURI = strings.Replace(connURI, "mongodb://", fmt.Sprintf("mongodb://%s:%s@", username, password), 1) - dialInfo, err := connutil.ParseMongoURI(connURI) +func testCredsExist(t testing.TB, connURL, username, password string) error { + connURL = strings.Replace(connURL, "mongodb://", fmt.Sprintf("mongodb://%s:%s@", username, password), 1) + dialInfo, err := connutil.ParseMongoURL(connURL) if err != nil { return err } diff --git a/plugins/helper/database/connutil/cassandra.go b/plugins/helper/database/connutil/cassandra.go index dc1195caf77b..3849df7e6f6a 100644 --- a/plugins/helper/database/connutil/cassandra.go +++ b/plugins/helper/database/connutil/cassandra.go @@ -111,10 +111,6 @@ func (c *CassandraConnectionProducer) Initialize(conf map[string]interface{}, ve } func (c *CassandraConnectionProducer) Connection() (interface{}, error) { - if !c.Initialized { - return nil, errNotInitialized - } - // If we already have a DB, return it if c.session != nil { return c.session, nil diff --git a/plugins/helper/database/connutil/mongodb.go b/plugins/helper/database/connutil/mongodb.go index 567a699a9e33..8f9a140de1cc 100644 --- a/plugins/helper/database/connutil/mongodb.go +++ b/plugins/helper/database/connutil/mongodb.go @@ -19,7 +19,7 @@ import ( // MongoDBConnectionProducer implements ConnectionProducer and provides an // interface for databases to make connections. type MongoDBConnectionProducer struct { - URI string `json:"uri" structs:"uri" mapstructure:"uri"` + ConnectionURL string `json:"connection_url" structs:"connection_url" mapstructure:"connection_url"` Initialized bool Type string @@ -37,8 +37,8 @@ func (c *MongoDBConnectionProducer) Initialize(conf map[string]interface{}, veri return err } - if len(c.URI) == 0 { - return fmt.Errorf("uri cannot be empty") + if len(c.ConnectionURL) == 0 { + return fmt.Errorf("connection_url cannot be empty") } if verifyConnection { @@ -54,15 +54,11 @@ func (c *MongoDBConnectionProducer) Initialize(conf map[string]interface{}, veri // Connection creates a database connection. func (c *MongoDBConnectionProducer) Connection() (interface{}, error) { - if !c.Initialized { - return nil, errNotInitialized - } - if c.session != nil { return c.session, nil } - dialInfo, err := ParseMongoURI(c.URI) + dialInfo, err := ParseMongoURL(c.ConnectionURL) if err != nil { return nil, err } @@ -91,24 +87,24 @@ func (c *MongoDBConnectionProducer) Close() error { return nil } -func ParseMongoURI(rawURI string) (*mgo.DialInfo, error) { - uri, err := url.Parse(rawURI) +func ParseMongoURL(rawURL string) (*mgo.DialInfo, error) { + url, err := url.Parse(rawURL) if err != nil { return nil, err } info := mgo.DialInfo{ - Addrs: strings.Split(uri.Host, ","), - Database: strings.TrimPrefix(uri.Path, "/"), + Addrs: strings.Split(url.Host, ","), + Database: strings.TrimPrefix(url.Path, "/"), Timeout: 10 * time.Second, } - if uri.User != nil { - info.Username = uri.User.Username() - info.Password, _ = uri.User.Password() + if url.User != nil { + info.Username = url.User.Username() + info.Password, _ = url.User.Password() } - query := uri.Query() + query := url.Query() for key, values := range query { var value string if len(values) > 0 { From f5c66f70e46e0591d6f796e1c12d875f72cdfaa1 Mon Sep 17 00:00:00 2001 From: Calvin Leung Huang Date: Tue, 9 May 2017 14:44:13 -0400 Subject: [PATCH 13/18] Add back Initialized check --- plugins/helper/database/connutil/cassandra.go | 12 +++++++++--- plugins/helper/database/connutil/mongodb.go | 16 +++++++++++++--- plugins/helper/database/connutil/sql.go | 14 ++++++++++---- 3 files changed, 32 insertions(+), 10 deletions(-) diff --git a/plugins/helper/database/connutil/cassandra.go b/plugins/helper/database/connutil/cassandra.go index 3849df7e6f6a..497d97d6d603 100644 --- a/plugins/helper/database/connutil/cassandra.go +++ b/plugins/helper/database/connutil/cassandra.go @@ -99,18 +99,24 @@ func (c *CassandraConnectionProducer) Initialize(conf map[string]interface{}, ve c.TLS = true } + // Set initialized to true at this point since all fields are set, + // and the connection can be established at a later time. + c.Initialized = true + if verifyConnection { if _, err := c.Connection(); err != nil { - return fmt.Errorf("error initalizing connection: %s", err) + return fmt.Errorf("error verifying connection: %s", err) } } - c.Initialized = true - return nil } func (c *CassandraConnectionProducer) Connection() (interface{}, error) { + if !c.Initialized { + return nil, errNotInitialized + } + // If we already have a DB, return it if c.session != nil { return c.session, nil diff --git a/plugins/helper/database/connutil/mongodb.go b/plugins/helper/database/connutil/mongodb.go index 8f9a140de1cc..52a0888b4fb4 100644 --- a/plugins/helper/database/connutil/mongodb.go +++ b/plugins/helper/database/connutil/mongodb.go @@ -41,19 +41,29 @@ func (c *MongoDBConnectionProducer) Initialize(conf map[string]interface{}, veri return fmt.Errorf("connection_url cannot be empty") } + // Set initialized to true at this point since all fields are set, + // and the connection can be established at a later time. + c.Initialized = true + if verifyConnection { if _, err := c.Connection(); err != nil { - return fmt.Errorf("error initializing connection: %s", err) + return fmt.Errorf("error verifying connection: %s", err) } - } - c.Initialized = true + if err := c.session.Ping(); err != nil { + return fmt.Errorf("error verifying connection: %s", err) + } + } return nil } // Connection creates a database connection. func (c *MongoDBConnectionProducer) Connection() (interface{}, error) { + if !c.Initialized { + return nil, errNotInitialized + } + if c.session != nil { return c.session, nil } diff --git a/plugins/helper/database/connutil/sql.go b/plugins/helper/database/connutil/sql.go index 04269798f190..e50ee436d6fa 100644 --- a/plugins/helper/database/connutil/sql.go +++ b/plugins/helper/database/connutil/sql.go @@ -61,22 +61,28 @@ func (c *SQLConnectionProducer) Initialize(conf map[string]interface{}, verifyCo return fmt.Errorf("invalid max_connection_lifetime: %s", err) } + // Set initialized to true at this point since all fields are set, + // and the connection can be established at a later time. + c.Initialized = true + if verifyConnection { if _, err := c.Connection(); err != nil { - return fmt.Errorf("error initalizing connection: %s", err) + return fmt.Errorf("error verifying connection: %s", err) } if err := c.db.Ping(); err != nil { - return fmt.Errorf("error initalizing connection: %s", err) + return fmt.Errorf("error verifying connection: %s", err) } } - c.Initialized = true - return nil } func (c *SQLConnectionProducer) Connection() (interface{}, error) { + if !c.Initialized { + return nil, errNotInitialized + } + // If we already have a DB, test it and return if c.db != nil { if err := c.db.Ping(); err == nil { From d0e2404bc8ffaacf16868d9354db76181ab46bbb Mon Sep 17 00:00:00 2001 From: Calvin Leung Huang Date: Tue, 9 May 2017 14:46:58 -0400 Subject: [PATCH 14/18] Update docs --- website/source/api/secret/databases/mongodb.html.md | 4 ++-- website/source/docs/secrets/databases/mongodb.html.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/website/source/api/secret/databases/mongodb.html.md b/website/source/api/secret/databases/mongodb.html.md index c9f197947b90..65aeea1b69b3 100644 --- a/website/source/api/secret/databases/mongodb.html.md +++ b/website/source/api/secret/databases/mongodb.html.md @@ -23,7 +23,7 @@ has a number of parameters to further configure a connection. | `POST` | `/database/config/:name` | `204 (empty body)` | ### Parameters -- `url` `(string: )` – Specifies the MongoDB standard connection string (URI). +- `connection_url` `(string: )` – Specifies the MongoDB standard connection string (URI). ### Sample Payload @@ -31,7 +31,7 @@ has a number of parameters to further configure a connection. { "plugin_name": "mongodb-database-plugin", "allowed_roles": "readonly", - "uri": "mongodb://admin:Password!@mongodb.acme.com:27017/admin?ssl=true" + "connection_url": "mongodb://admin:Password!@mongodb.acme.com:27017/admin?ssl=true" } ``` diff --git a/website/source/docs/secrets/databases/mongodb.html.md b/website/source/docs/secrets/databases/mongodb.html.md index 2b5e92ad76db..d285c50620d0 100644 --- a/website/source/docs/secrets/databases/mongodb.html.md +++ b/website/source/docs/secrets/databases/mongodb.html.md @@ -27,7 +27,7 @@ MongoDB configuration: $ vault write database/config/mongodb \ plugin_name=mongodb-database-plugin \ allowed_roles="readonly" \ - uri="mongodb://admin:Password!@mongodb.acme.com:27017/admin?ssl=true" + connection_url="mongodb://admin:Password!@mongodb.acme.com:27017/admin?ssl=true" The following warnings were returned from the Vault server: * Read access to this endpoint should be controlled via ACLs as it will return the connection details as is, including passwords, if any. From 5426b78cf4c4bb22d9ef4d51d78b8b55b3f6a91d Mon Sep 17 00:00:00 2001 From: Calvin Leung Huang Date: Tue, 9 May 2017 18:39:09 -0400 Subject: [PATCH 15/18] Move connProducer and credsProducer into pkg for mongodb and cassandra --- plugins/database/cassandra/cassandra.go | 4 ++-- plugins/database/cassandra/cassandra_test.go | 3 +-- .../cassandra/connection_producer.go} | 17 +++++++++-------- .../cassandra/credentials_producer.go} | 12 ++++++------ .../cassandra/test-fixtures/cassandra.yaml | 8 ++++---- .../mongodb/connection_producer.go} | 15 ++++++++------- .../mongodb/credentials_producer.go} | 12 ++++++------ plugins/database/mongodb/mongodb.go | 4 ++-- plugins/database/mongodb/mongodb_test.go | 7 +++---- plugins/helper/database/connutil/connutil.go | 2 +- plugins/helper/database/connutil/sql.go | 2 +- 11 files changed, 43 insertions(+), 43 deletions(-) rename plugins/{helper/database/connutil/cassandra.go => database/cassandra/connection_producer.go} (92%) rename plugins/{helper/database/credsutil/cassandra.go => database/cassandra/credentials_producer.go} (65%) rename plugins/{helper/database/connutil/mongodb.go => database/mongodb/connection_producer.go} (89%) rename plugins/{helper/database/credsutil/mongodb.go => database/mongodb/credentials_producer.go} (60%) diff --git a/plugins/database/cassandra/cassandra.go b/plugins/database/cassandra/cassandra.go index 60e445ff6bae..cafb2545cfb1 100644 --- a/plugins/database/cassandra/cassandra.go +++ b/plugins/database/cassandra/cassandra.go @@ -29,10 +29,10 @@ type Cassandra struct { // New returns a new Cassandra instance func New() (interface{}, error) { - connProducer := &connutil.CassandraConnectionProducer{} + connProducer := &cassandraConnectionProducer{} connProducer.Type = cassandraTypeName - credsProducer := &credsutil.CassandraCredentialsProducer{} + credsProducer := &cassandraCredentialsProducer{} dbType := &Cassandra{ ConnectionProducer: connProducer, diff --git a/plugins/database/cassandra/cassandra_test.go b/plugins/database/cassandra/cassandra_test.go index 9e98ec48f558..eaa5dacd9d31 100644 --- a/plugins/database/cassandra/cassandra_test.go +++ b/plugins/database/cassandra/cassandra_test.go @@ -10,7 +10,6 @@ import ( "github.com/gocql/gocql" "github.com/hashicorp/vault/builtin/logical/database/dbplugin" - "github.com/hashicorp/vault/plugins/helper/database/connutil" dockertest "gopkg.in/ory-am/dockertest.v3" ) @@ -82,7 +81,7 @@ func TestCassandra_Initialize(t *testing.T) { dbRaw, _ := New() db := dbRaw.(*Cassandra) - connProducer := db.ConnectionProducer.(*connutil.CassandraConnectionProducer) + connProducer := db.ConnectionProducer.(*cassandraConnectionProducer) err := db.Initialize(connectionDetails, true) if err != nil { diff --git a/plugins/helper/database/connutil/cassandra.go b/plugins/database/cassandra/connection_producer.go similarity index 92% rename from plugins/helper/database/connutil/cassandra.go rename to plugins/database/cassandra/connection_producer.go index 497d97d6d603..0b484d1c993f 100644 --- a/plugins/helper/database/connutil/cassandra.go +++ b/plugins/database/cassandra/connection_producer.go @@ -1,4 +1,4 @@ -package connutil +package cassandra import ( "crypto/tls" @@ -13,11 +13,12 @@ import ( "github.com/hashicorp/vault/helper/certutil" "github.com/hashicorp/vault/helper/parseutil" "github.com/hashicorp/vault/helper/tlsutil" + "github.com/hashicorp/vault/plugins/helper/database/connutil" ) -// CassandraConnectionProducer implements ConnectionProducer and provides an +// cassandraConnectionProducer implements ConnectionProducer and provides an // interface for cassandra databases to make connections. -type CassandraConnectionProducer struct { +type cassandraConnectionProducer struct { Hosts string `json:"hosts" structs:"hosts" mapstructure:"hosts"` Username string `json:"username" structs:"username" mapstructure:"username"` Password string `json:"password" structs:"password" mapstructure:"password"` @@ -41,7 +42,7 @@ type CassandraConnectionProducer struct { sync.Mutex } -func (c *CassandraConnectionProducer) Initialize(conf map[string]interface{}, verifyConnection bool) error { +func (c *cassandraConnectionProducer) Initialize(conf map[string]interface{}, verifyConnection bool) error { c.Lock() defer c.Unlock() @@ -112,9 +113,9 @@ func (c *CassandraConnectionProducer) Initialize(conf map[string]interface{}, ve return nil } -func (c *CassandraConnectionProducer) Connection() (interface{}, error) { +func (c *cassandraConnectionProducer) Connection() (interface{}, error) { if !c.Initialized { - return nil, errNotInitialized + return nil, connutil.ErrNotInitialized } // If we already have a DB, return it @@ -133,7 +134,7 @@ func (c *CassandraConnectionProducer) Connection() (interface{}, error) { return session, nil } -func (c *CassandraConnectionProducer) Close() error { +func (c *cassandraConnectionProducer) Close() error { // Grab the write lock c.Lock() defer c.Unlock() @@ -147,7 +148,7 @@ func (c *CassandraConnectionProducer) Close() error { return nil } -func (c *CassandraConnectionProducer) createSession() (*gocql.Session, error) { +func (c *cassandraConnectionProducer) createSession() (*gocql.Session, error) { clusterConfig := gocql.NewCluster(strings.Split(c.Hosts, ",")...) clusterConfig.Authenticator = gocql.PasswordAuthenticator{ Username: c.Username, diff --git a/plugins/helper/database/credsutil/cassandra.go b/plugins/database/cassandra/credentials_producer.go similarity index 65% rename from plugins/helper/database/credsutil/cassandra.go rename to plugins/database/cassandra/credentials_producer.go index 7ab5630b5809..8e9c3a509adf 100644 --- a/plugins/helper/database/credsutil/cassandra.go +++ b/plugins/database/cassandra/credentials_producer.go @@ -1,4 +1,4 @@ -package credsutil +package cassandra import ( "fmt" @@ -8,11 +8,11 @@ import ( uuid "github.com/hashicorp/go-uuid" ) -// CassandraCredentialsProducer implements CredentialsProducer and provides an +// cassandraCredentialsProducer implements CredentialsProducer and provides an // interface for cassandra databases to generate user information. -type CassandraCredentialsProducer struct{} +type cassandraCredentialsProducer struct{} -func (ccp *CassandraCredentialsProducer) GenerateUsername(displayName string) (string, error) { +func (ccp *cassandraCredentialsProducer) GenerateUsername(displayName string) (string, error) { userUUID, err := uuid.GenerateUUID() if err != nil { return "", err @@ -23,7 +23,7 @@ func (ccp *CassandraCredentialsProducer) GenerateUsername(displayName string) (s return username, nil } -func (ccp *CassandraCredentialsProducer) GeneratePassword() (string, error) { +func (ccp *cassandraCredentialsProducer) GeneratePassword() (string, error) { password, err := uuid.GenerateUUID() if err != nil { return "", err @@ -32,6 +32,6 @@ func (ccp *CassandraCredentialsProducer) GeneratePassword() (string, error) { return password, nil } -func (ccp *CassandraCredentialsProducer) GenerateExpiration(ttl time.Time) (string, error) { +func (ccp *cassandraCredentialsProducer) GenerateExpiration(ttl time.Time) (string, error) { return "", nil } diff --git a/plugins/database/cassandra/test-fixtures/cassandra.yaml b/plugins/database/cassandra/test-fixtures/cassandra.yaml index 54f47d34ac62..a6ef938c4131 100644 --- a/plugins/database/cassandra/test-fixtures/cassandra.yaml +++ b/plugins/database/cassandra/test-fixtures/cassandra.yaml @@ -421,7 +421,7 @@ seed_provider: parameters: # seeds is actually a comma-delimited list of addresses. # Ex: ",," - - seeds: "172.17.0.2" + - seeds: "172.17.0.4" # For workloads with more data than can fit in memory, Cassandra's # bottleneck will be reads that need to fetch data from @@ -572,7 +572,7 @@ ssl_storage_port: 7001 # # Setting listen_address to 0.0.0.0 is always wrong. # -listen_address: 172.17.0.2 +listen_address: 172.17.0.4 # Set listen_address OR listen_interface, not both. Interfaces must correspond # to a single address, IP aliasing is not supported. @@ -586,7 +586,7 @@ listen_address: 172.17.0.2 # Address to broadcast to other Cassandra nodes # Leaving this blank will set it to the same value as listen_address -broadcast_address: 172.17.0.2 +broadcast_address: 172.17.0.4 # When using multiple physical network interfaces, set this # to true to listen on broadcast_address in addition to @@ -668,7 +668,7 @@ rpc_port: 9160 # be set to 0.0.0.0. If left blank, this will be set to the value of # rpc_address. If rpc_address is set to 0.0.0.0, broadcast_rpc_address must # be set. -broadcast_rpc_address: 172.17.0.2 +broadcast_rpc_address: 172.17.0.4 # enable or disable keepalive on rpc/native connections rpc_keepalive: true diff --git a/plugins/helper/database/connutil/mongodb.go b/plugins/database/mongodb/connection_producer.go similarity index 89% rename from plugins/helper/database/connutil/mongodb.go rename to plugins/database/mongodb/connection_producer.go index 52a0888b4fb4..1ea6284e3594 100644 --- a/plugins/helper/database/connutil/mongodb.go +++ b/plugins/database/mongodb/connection_producer.go @@ -1,4 +1,4 @@ -package connutil +package mongodb import ( "crypto/tls" @@ -11,14 +11,15 @@ import ( "sync" "time" + "github.com/hashicorp/vault/plugins/helper/database/connutil" "github.com/mitchellh/mapstructure" "gopkg.in/mgo.v2" ) -// MongoDBConnectionProducer implements ConnectionProducer and provides an +// mongoDBConnectionProducer implements ConnectionProducer and provides an // interface for databases to make connections. -type MongoDBConnectionProducer struct { +type mongoDBConnectionProducer struct { ConnectionURL string `json:"connection_url" structs:"connection_url" mapstructure:"connection_url"` Initialized bool @@ -28,7 +29,7 @@ type MongoDBConnectionProducer struct { } // Initialize parses connection configuration. -func (c *MongoDBConnectionProducer) Initialize(conf map[string]interface{}, verifyConnection bool) error { +func (c *mongoDBConnectionProducer) Initialize(conf map[string]interface{}, verifyConnection bool) error { c.Lock() defer c.Unlock() @@ -59,9 +60,9 @@ func (c *MongoDBConnectionProducer) Initialize(conf map[string]interface{}, veri } // Connection creates a database connection. -func (c *MongoDBConnectionProducer) Connection() (interface{}, error) { +func (c *mongoDBConnectionProducer) Connection() (interface{}, error) { if !c.Initialized { - return nil, errNotInitialized + return nil, connutil.ErrNotInitialized } if c.session != nil { @@ -84,7 +85,7 @@ func (c *MongoDBConnectionProducer) Connection() (interface{}, error) { } // Close terminates the database connection. -func (c *MongoDBConnectionProducer) Close() error { +func (c *mongoDBConnectionProducer) Close() error { c.Lock() defer c.Unlock() diff --git a/plugins/helper/database/credsutil/mongodb.go b/plugins/database/mongodb/credentials_producer.go similarity index 60% rename from plugins/helper/database/credsutil/mongodb.go rename to plugins/database/mongodb/credentials_producer.go index 597efc3aee17..80dc2c3d39d1 100644 --- a/plugins/helper/database/credsutil/mongodb.go +++ b/plugins/database/mongodb/credentials_producer.go @@ -1,4 +1,4 @@ -package credsutil +package mongodb import ( "fmt" @@ -7,11 +7,11 @@ import ( uuid "github.com/hashicorp/go-uuid" ) -// MongoDBCredentialsProducer implements CredentialsProducer and provides an +// mongoDBCredentialsProducer implements CredentialsProducer and provides an // interface for databases to generate user information. -type MongoDBCredentialsProducer struct{} +type mongoDBCredentialsProducer struct{} -func (cp *MongoDBCredentialsProducer) GenerateUsername(displayName string) (string, error) { +func (cp *mongoDBCredentialsProducer) GenerateUsername(displayName string) (string, error) { userUUID, err := uuid.GenerateUUID() if err != nil { return "", err @@ -22,7 +22,7 @@ func (cp *MongoDBCredentialsProducer) GenerateUsername(displayName string) (stri return username, nil } -func (cp *MongoDBCredentialsProducer) GeneratePassword() (string, error) { +func (cp *mongoDBCredentialsProducer) GeneratePassword() (string, error) { password, err := uuid.GenerateUUID() if err != nil { return "", err @@ -31,6 +31,6 @@ func (cp *MongoDBCredentialsProducer) GeneratePassword() (string, error) { return password, nil } -func (cp *MongoDBCredentialsProducer) GenerateExpiration(ttl time.Time) (string, error) { +func (cp *mongoDBCredentialsProducer) GenerateExpiration(ttl time.Time) (string, error) { return "", nil } diff --git a/plugins/database/mongodb/mongodb.go b/plugins/database/mongodb/mongodb.go index ae67648ea3e3..a6929187bdc7 100644 --- a/plugins/database/mongodb/mongodb.go +++ b/plugins/database/mongodb/mongodb.go @@ -26,10 +26,10 @@ type MongoDB struct { // New returns a new MongoDB instance func New() (interface{}, error) { - connProducer := &connutil.MongoDBConnectionProducer{} + connProducer := &mongoDBConnectionProducer{} connProducer.Type = mongoDBTypeName - credsProducer := &credsutil.MongoDBCredentialsProducer{} + credsProducer := &mongoDBCredentialsProducer{} dbType := &MongoDB{ ConnectionProducer: connProducer, diff --git a/plugins/database/mongodb/mongodb_test.go b/plugins/database/mongodb/mongodb_test.go index 41d16318f536..c319841499bf 100644 --- a/plugins/database/mongodb/mongodb_test.go +++ b/plugins/database/mongodb/mongodb_test.go @@ -11,7 +11,6 @@ import ( "strings" "github.com/hashicorp/vault/builtin/logical/database/dbplugin" - "github.com/hashicorp/vault/plugins/helper/database/connutil" dockertest "gopkg.in/ory-am/dockertest.v3" ) @@ -44,7 +43,7 @@ func prepareMongoDBTestContainer(t *testing.T) (cleanup func(), retURL string) { // exponential backoff-retry if err = pool.Retry(func() error { var err error - dialInfo, err := connutil.ParseMongoURL(retURL) + dialInfo, err := ParseMongoURL(retURL) if err != nil { return err } @@ -76,7 +75,7 @@ func TestMongoDB_Initialize(t *testing.T) { t.Fatalf("err: %s", err) } db := dbRaw.(*MongoDB) - connProducer := db.ConnectionProducer.(*connutil.MongoDBConnectionProducer) + connProducer := db.ConnectionProducer.(*mongoDBConnectionProducer) err = db.Initialize(connectionDetails, true) if err != nil { @@ -169,7 +168,7 @@ func TestMongoDB_RevokeUser(t *testing.T) { func testCredsExist(t testing.TB, connURL, username, password string) error { connURL = strings.Replace(connURL, "mongodb://", fmt.Sprintf("mongodb://%s:%s@", username, password), 1) - dialInfo, err := connutil.ParseMongoURL(connURL) + dialInfo, err := ParseMongoURL(connURL) if err != nil { return err } diff --git a/plugins/helper/database/connutil/connutil.go b/plugins/helper/database/connutil/connutil.go index c43691c6164d..d36d5719d6a8 100644 --- a/plugins/helper/database/connutil/connutil.go +++ b/plugins/helper/database/connutil/connutil.go @@ -6,7 +6,7 @@ import ( ) var ( - errNotInitialized = errors.New("connection has not been initalized") + ErrNotInitialized = errors.New("connection has not been initalized") ) // ConnectionProducer can be used as an embeded interface in the Database diff --git a/plugins/helper/database/connutil/sql.go b/plugins/helper/database/connutil/sql.go index e50ee436d6fa..4cb99744298f 100644 --- a/plugins/helper/database/connutil/sql.go +++ b/plugins/helper/database/connutil/sql.go @@ -80,7 +80,7 @@ func (c *SQLConnectionProducer) Initialize(conf map[string]interface{}, verifyCo func (c *SQLConnectionProducer) Connection() (interface{}, error) { if !c.Initialized { - return nil, errNotInitialized + return nil, ErrNotInitialized } // If we already have a DB, test it and return From 3d180a7121200d6973ce5aaf3ca88a07c82ad92a Mon Sep 17 00:00:00 2001 From: Calvin Leung Huang Date: Tue, 9 May 2017 23:01:51 -0400 Subject: [PATCH 16/18] Chage parseMongoURL to be a private func --- plugins/database/mongodb/connection_producer.go | 4 ++-- plugins/database/mongodb/mongodb_test.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/database/mongodb/connection_producer.go b/plugins/database/mongodb/connection_producer.go index 1ea6284e3594..5fcfdecbdf2b 100644 --- a/plugins/database/mongodb/connection_producer.go +++ b/plugins/database/mongodb/connection_producer.go @@ -69,7 +69,7 @@ func (c *mongoDBConnectionProducer) Connection() (interface{}, error) { return c.session, nil } - dialInfo, err := ParseMongoURL(c.ConnectionURL) + dialInfo, err := parseMongoURL(c.ConnectionURL) if err != nil { return nil, err } @@ -98,7 +98,7 @@ func (c *mongoDBConnectionProducer) Close() error { return nil } -func ParseMongoURL(rawURL string) (*mgo.DialInfo, error) { +func parseMongoURL(rawURL string) (*mgo.DialInfo, error) { url, err := url.Parse(rawURL) if err != nil { return nil, err diff --git a/plugins/database/mongodb/mongodb_test.go b/plugins/database/mongodb/mongodb_test.go index c319841499bf..1fa14aa37fc4 100644 --- a/plugins/database/mongodb/mongodb_test.go +++ b/plugins/database/mongodb/mongodb_test.go @@ -43,7 +43,7 @@ func prepareMongoDBTestContainer(t *testing.T) (cleanup func(), retURL string) { // exponential backoff-retry if err = pool.Retry(func() error { var err error - dialInfo, err := ParseMongoURL(retURL) + dialInfo, err := parseMongoURL(retURL) if err != nil { return err } @@ -168,7 +168,7 @@ func TestMongoDB_RevokeUser(t *testing.T) { func testCredsExist(t testing.TB, connURL, username, password string) error { connURL = strings.Replace(connURL, "mongodb://", fmt.Sprintf("mongodb://%s:%s@", username, password), 1) - dialInfo, err := ParseMongoURL(connURL) + dialInfo, err := parseMongoURL(connURL) if err != nil { return err } From 445b94fd1b530a476252857e44e3f2ed34d34cbf Mon Sep 17 00:00:00 2001 From: Calvin Leung Huang Date: Thu, 11 May 2017 14:28:07 -0400 Subject: [PATCH 17/18] Default to admin if no db is provided in creation_statements --- plugins/database/mongodb/mongodb.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/database/mongodb/mongodb.go b/plugins/database/mongodb/mongodb.go index a6929187bdc7..5d7aa09b1c71 100644 --- a/plugins/database/mongodb/mongodb.go +++ b/plugins/database/mongodb/mongodb.go @@ -103,9 +103,9 @@ func (m *MongoDB) CreateUser(statements dbplugin.Statements, usernamePrefix stri return "", "", err } - // Check for db string + // Default to "admin" if no db provided if mongoCS.DB == "" { - return "", "", fmt.Errorf("db value is required in creation statement") + mongoCS.DB = "admin" } if len(mongoCS.Roles) == 0 { From 65b6cf7483703d21cca8a86e355db66d1cf76877 Mon Sep 17 00:00:00 2001 From: Calvin Leung Huang Date: Thu, 11 May 2017 16:50:46 -0400 Subject: [PATCH 18/18] Update comments and docs --- plugins/database/mongodb/util.go | 19 ++++----- .../api/secret/databases/mongodb.html.md | 41 +++++++++++++++++++ 2 files changed, 50 insertions(+), 10 deletions(-) diff --git a/plugins/database/mongodb/util.go b/plugins/database/mongodb/util.go index cc00368682d7..9004a3c710c4 100644 --- a/plugins/database/mongodb/util.go +++ b/plugins/database/mongodb/util.go @@ -17,17 +17,16 @@ type mongoDBStatement struct { Roles mongodbRoles `json:"roles"` } +// Convert array of role documents like: +// +// [ { "role": "readWrite" }, { "role": "readWrite", "db": "test" } ] +// +// into a "standard" MongoDB roles array containing both strings and role documents: +// +// [ "readWrite", { "role": "readWrite", "db": "test" } ] +// +// MongoDB's createUser command accepts the latter. func (roles mongodbRoles) toStandardRolesArray() []interface{} { - // Convert array of role documents like: - // - // [ { "role": "readWrite" }, { "role": "readWrite", "db": "test" } ] - // - // into a "standard" MongoDB roles array containing both strings and role documents: - // - // [ "readWrite", { "role": "readWrite", "db": "test" } ] - // - // MongoDB's createUser command accepts the latter. - // var standardRolesArray []interface{} for _, role := range roles { if role.DB == "" { diff --git a/website/source/api/secret/databases/mongodb.html.md b/website/source/api/secret/databases/mongodb.html.md index 65aeea1b69b3..48a8ae2c401d 100644 --- a/website/source/api/secret/databases/mongodb.html.md +++ b/website/source/api/secret/databases/mongodb.html.md @@ -44,3 +44,44 @@ $ curl \ --data @payload.json \ https://vault.rocks/v1/database/config/mongodb ``` + +## Statements + +Statements are configured during role creation and are used by the plugin to +determine what is sent to the datatabse on user creation, renewing, and +revocation. For more information on configuring roles see the [Role +API](/api/secret/databases/index.html#create-role) in the Database Backend docs. + +### Parameters + +The following are the statements used by this plugin. If not mentioned in this +list the plugin does not support that statement type. + +- `creation_statements` `(string: )` – Specifies the database + statements executed to create and configure a user. Must be a + serialized JSON object, or a base64-encoded serialized JSON object. + The object can optionally contain a "db" string for session connection, + and must contain a "roles" array. This array contains objects that holds + a "role", and an optional "db" value, and is similar to the BSON document that + is accepted by MongoDB's `roles` field. Vault will transform this array into + such format. For more information regarding the `roles` field, refer to + [MongoDB's documentation](https://docs.mongodb.com/manual/reference/method/db.createUser/). + +- `revocation_statements` `(string: "")` – Specifies the database statements to + be executed to revoke a user. Must be a serialized JSON object, or a base64-encoded + serialized JSON object. The object can optionally contain a "db" string. If no + "db" value is provided, it defaults to the "admin" database. + +### Sample Creation Statement + +```json +{ + "db": "admin", + "roles": [ + { + "role": "read", + "db": "foo", + } + ] +} +``` \ No newline at end of file