Skip to content

Commit

Permalink
Support MongoDB session-wide write concern (#3646)
Browse files Browse the repository at this point in the history
* Initial work on write concern support, set for the lifetime of the session

* Add base64 encoded value support, include docs and tests

* Handle error from json.Unmarshal, fix test and docs

* Remove writeConcern struct, move JSON unmarshal to Initialize

* Return error on empty mapping of write_concern into mgo.Safe struct
  • Loading branch information
calvn committed Dec 5, 2017
1 parent 208dc55 commit a9e7dbb
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 5 deletions.
33 changes: 33 additions & 0 deletions plugins/database/mongodb/connection_producer.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package mongodb

import (
"crypto/tls"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"net"
Expand All @@ -21,10 +23,12 @@ import (
// interface for databases to make connections.
type mongoDBConnectionProducer struct {
ConnectionURL string `json:"connection_url" structs:"connection_url" mapstructure:"connection_url"`
WriteConcern string `json:"write_concern" structs:"write_concern" mapstructure:"write_concern"`

Initialized bool
Type string
session *mgo.Session
safe *mgo.Safe
sync.Mutex
}

Expand All @@ -42,6 +46,30 @@ func (c *mongoDBConnectionProducer) Initialize(conf map[string]interface{}, veri
return fmt.Errorf("connection_url cannot be empty")
}

if c.WriteConcern != "" {
input := c.WriteConcern

// Try to base64 decode the input. If successful, consider the decoded
// value as input.
inputBytes, err := base64.StdEncoding.DecodeString(input)
if err == nil {
input = string(inputBytes)
}

concern := &mgo.Safe{}
err = json.Unmarshal([]byte(input), concern)
if err != nil {
return fmt.Errorf("error mashalling write_concern: %s", err)
}

// Guard against empty, non-nil mgo.Safe object; we don't want to pass that
// into mgo.SetSafe in Connection().
if (mgo.Safe{} == *concern) {
return fmt.Errorf("provided write_concern values did not map to any mgo.Safe fields")
}
c.safe = concern
}

// 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
Expand Down Expand Up @@ -78,6 +106,11 @@ func (c *mongoDBConnectionProducer) Connection() (interface{}, error) {
if err != nil {
return nil, err
}

if c.safe != nil {
c.session.SetSafe(c.safe)
}

c.session.SetSyncTimeout(1 * time.Minute)
c.session.SetSocketTimeout(1 * time.Minute)

Expand Down
40 changes: 40 additions & 0 deletions plugins/database/mongodb/mongodb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import (

const testMongoDBRole = `{ "db": "admin", "roles": [ { "role": "readWrite" } ] }`

const testMongoDBWriteConcern = `{ "wmode": "majority", "wtimeout": 5000 }`

func prepareMongoDBTestContainer(t *testing.T) (cleanup func(), retURL string) {
if os.Getenv("MONGODB_URL") != "" {
return func() {}, os.Getenv("MONGODB_URL")
Expand Down Expand Up @@ -129,6 +131,44 @@ func TestMongoDB_CreateUser(t *testing.T) {
}
}

func TestMongoDB_CreateUser_writeConcern(t *testing.T) {
cleanup, connURL := prepareMongoDBTestContainer(t)
defer cleanup()

connectionDetails := map[string]interface{}{
"connection_url": connURL,
"write_concern": testMongoDBWriteConcern,
}

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

usernameConfig := dbplugin.UsernameConfig{
DisplayName: "test",
RoleName: "test",
}

username, password, err := db.CreateUser(statements, usernameConfig, 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()
Expand Down
21 changes: 16 additions & 5 deletions website/source/api/secret/databases/mongodb.html.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,26 @@ has a number of parameters to further configure a connection.

| Method | Path | Produces |
| :------- | :--------------------------- | :--------------------- |
| `POST` | `/database/config/:name` | `204 (empty body)` |
| `POST` | `/database/config/:name` | `204 (empty body)` |

### Parameters
- `connection_url` `(string: <required>)` – Specifies the MongoDB standard connection string (URI).

- `connection_url` `(string: <required>)` – Specifies the MongoDB standard
connection string (URI).
- `write_concern` `(string: "")` - Specifies the MongoDB [write
concern][mongodb-write-concern]. This is set for the entirety of the session,
maintained for the lifecycle of the plugin process. Must be a serialized JSON
object, or a base64-encoded serialized JSON object. The JSON payload values
map to the values in the [Safe][mgo-safe] struct from the mgo driver.

### Sample Payload

```json
{
"plugin_name": "mongodb-database-plugin",
"allowed_roles": "readonly",
"connection_url": "mongodb://admin:Password!@mongodb.acme.com:27017/admin?ssl=true"
"connection_url": "mongodb://admin:Password!@mongodb.acme.com:27017/admin?ssl=true",
"write_concern": "{ \"wmode\": \"majority\", \"wtimeout\": 5000 }"
}
```

Expand Down Expand Up @@ -68,7 +76,7 @@ list the plugin does not support that statement type.
[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
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.

Expand All @@ -84,4 +92,7 @@ list the plugin does not support that statement type.
}
]
}
```
```

[mongodb-write-concern]: https://docs.mongodb.com/manual/reference/write-concern/
[mgo-safe]: https://godoc.org/gopkg.in/mgo.v2#Safe

0 comments on commit a9e7dbb

Please sign in to comment.