Mongodb plugin (#2698)

* WIP on mongodb plugin

* Add mongodb plugin

* Add tests

* Update mongodb.CreateUser() comment

* Update docs

* Add missing docs

* Fix mongodb docs

* Minor comment and test updates

* Fix imports

* Fix dockertest import

* Set c.Initialized at the end, check for empty CreationStmts first on CreateUser

* Remove Initialized check on Connection()

* Add back Initialized check

* Update docs

* Move connProducer and credsProducer into pkg for  mongodb and cassandra

* Chage parseMongoURL to be a private func

* Default to admin if no db is provided in creation_statements

* Update comments and docs
This commit is contained in:
Calvin Leung Huang
2017-05-11 17:38:54 -04:00
committed by GitHub
parent b203d51068
commit a4c652cbb3
20 changed files with 809 additions and 34 deletions

View File

@@ -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,

View File

@@ -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"
)
@@ -85,7 +84,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 {

View File

@@ -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()
@@ -49,7 +50,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"
@@ -100,17 +100,22 @@ 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)
}
}
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
@@ -129,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()
@@ -143,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,

View File

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

View File

@@ -421,7 +421,7 @@ seed_provider:
parameters:
# seeds is actually a comma-delimited list of addresses.
# Ex: "<ip1>,<ip2>,<ip3>"
- 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

View File

@@ -0,0 +1,167 @@
package mongodb
import (
"crypto/tls"
"errors"
"fmt"
"net"
"net/url"
"strconv"
"strings"
"sync"
"time"
"github.com/hashicorp/vault/plugins/helper/database/connutil"
"github.com/mitchellh/mapstructure"
"gopkg.in/mgo.v2"
)
// mongoDBConnectionProducer implements ConnectionProducer and provides an
// interface for databases to make connections.
type mongoDBConnectionProducer struct {
ConnectionURL string `json:"connection_url" structs:"connection_url" mapstructure:"connection_url"`
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.ConnectionURL) == 0 {
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 verifying connection: %s", err)
}
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, connutil.ErrNotInitialized
}
if c.session != nil {
return c.session, nil
}
dialInfo, err := parseMongoURL(c.ConnectionURL)
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 parseMongoURL(rawURL string) (*mgo.DialInfo, error) {
url, err := url.Parse(rawURL)
if err != nil {
return nil, err
}
info := mgo.DialInfo{
Addrs: strings.Split(url.Host, ","),
Database: strings.TrimPrefix(url.Path, "/"),
Timeout: 10 * time.Second,
}
if url.User != nil {
info.Username = url.User.Username()
info.Password, _ = url.User.Password()
}
query := url.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
}

View File

@@ -0,0 +1,36 @@
package mongodb
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
}

View File

@@ -0,0 +1,21 @@
package main
import (
"log"
"os"
"github.com/hashicorp/vault/helper/pluginutil"
"github.com/hashicorp/vault/plugins/database/mongodb"
)
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)
}
}

View File

@@ -0,0 +1,168 @@
package mongodb
import (
"time"
"encoding/json"
"fmt"
"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"
)
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 := &mongoDBConnectionProducer{}
connProducer.Type = mongoDBTypeName
credsProducer := &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
}
// CreateUser generates the username/password on the underlying secret backend as instructed by
// 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()
defer m.Unlock()
if statements.CreationStatements == "" {
return "", "", dbutil.ErrEmptyCreationStatement
}
session, err := m.getConnection()
if err != nil {
return "", "", err
}
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
}
// Default to "admin" if no db provided
if mongoCS.DB == "" {
mongoCS.DB = "admin"
}
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
}
// 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(revocationStatement), &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
}

View File

@@ -0,0 +1,183 @@
package mongodb
import (
"fmt"
"os"
"testing"
"time"
mgo "gopkg.in/mgo.v2"
"strings"
"github.com/hashicorp/vault/builtin/logical/database/dbplugin"
dockertest "gopkg.in/ory-am/dockertest.v3"
)
const testMongoDBRole = `{ "db": "admin", "roles": [ { "role": "readWrite" } ] }`
func prepareMongoDBTestContainer(t *testing.T) (cleanup func(), retURL string) {
if os.Getenv("MONGODB_URL") != "" {
return func() {}, os.Getenv("MONGODB_URL")
}
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)
}
}
retURL = fmt.Sprintf("mongodb://localhost:%s", resource.GetPort("27017/tcp"))
// exponential backoff-retry
if err = pool.Retry(func() error {
var err error
dialInfo, err := parseMongoURL(retURL)
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, connURL := prepareMongoDBTestContainer(t)
defer cleanup()
connectionDetails := map[string]interface{}{
"connection_url": connURL,
}
dbRaw, err := New()
if err != nil {
t.Fatalf("err: %s", err)
}
db := dbRaw.(*MongoDB)
connProducer := db.ConnectionProducer.(*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, connURL := prepareMongoDBTestContainer(t)
defer cleanup()
connectionDetails := map[string]interface{}{
"connection_url": connURL,
}
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, connURL, username, password); err != nil {
t.Fatalf("Could not connect with new credentials: %s", err)
}
}
func TestMongoDB_RevokeUser(t *testing.T) {
cleanup, connURL := prepareMongoDBTestContainer(t)
defer cleanup()
connectionDetails := map[string]interface{}{
"connection_url": connURL,
}
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, connURL, 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, connURL, username, password); err == nil {
t.Fatal("Credentials were not revoked")
}
}
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)
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()
}

View File

@@ -0,0 +1,39 @@
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"`
}
// 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{} {
var standardRolesArray []interface{}
for _, role := range roles {
if role.DB == "" {
standardRolesArray = append(standardRolesArray, role.Role)
} else {
standardRolesArray = append(standardRolesArray, role)
}
}
return standardRolesArray
}

View File

@@ -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

View File

@@ -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 {

View File

@@ -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.
---

View File

@@ -0,0 +1,87 @@
---
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.
---
# 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
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
- `connection_url` `(string: <required>)` Specifies the MongoDB standard connection string (URI).
### Sample Payload
```json
{
"plugin_name": "mongodb-database-plugin",
"allowed_roles": "readonly",
"connection_url": "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
```
## 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: <required>)` 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",
}
]
}
```

View File

@@ -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.
---

View File

@@ -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.
---

View File

@@ -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.
---
# MongoDB 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" \
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.
```
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.

View File

@@ -36,6 +36,9 @@
<li<%= sidebar_current("docs-http-secret-databases-cassandra") %>>
<a href="/api/secret/databases/cassandra.html">Cassandra</a>
</li>
<li<%= sidebar_current("docs-http-secret-databases-mongodb") %>>
<a href="/api/secret/databases/mongodb.html">MongoDB</a>
</li>
<li<%= sidebar_current("docs-http-secret-databases-mssql") %>>
<a href="/api/secret/databases/mssql.html">MSSQL</a>
</li>
@@ -52,7 +55,7 @@
<a href="/api/secret/generic/index.html">Generic</a>
</li>
<li<%= sidebar_current("docs-http-secret-mongodb") %>>
<a href="/api/secret/mongodb/index.html">MongoDB</a>
<a href="/api/secret/mongodb/index.html">MongoDB (Deprecated)</a>
</li>
<li<%= sidebar_current("docs-http-secret-mssql") %>>
<a href="/api/secret/mssql/index.html">MSSQL (Deprecated)</a>

View File

@@ -225,6 +225,9 @@
<li<%= sidebar_current("docs-secrets-databases-cassandra") %>>
<a href="/docs/secrets/databases/cassandra.html">Cassandra</a>
</li>
<li<%= sidebar_current("docs-secrets-databases-mongodb") %>>
<a href="/docs/secrets/databases/mysql-maria.html">MongoDB</a>
</li>
<li<%= sidebar_current("docs-secrets-databases-mssql") %>>
<a href="/docs/secrets/databases/mssql.html">MSSQL</a>
</li>
@@ -245,7 +248,7 @@
</li>
<li<%= sidebar_current("docs-secrets-mongodb") %>>
<a href="/docs/secrets/mongodb/index.html">MongoDB</a>
<a href="/docs/secrets/mongodb/index.html">MongoDB (Deprecated)</a>
</li>
<li<%= sidebar_current("docs-secrets-mssql") %>>