Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to specify TLS config for mongodb database plugin #5632

Closed
wants to merge 7 commits into from
41 changes: 38 additions & 3 deletions plugins/database/mongodb/connection_producer.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package mongodb
import (
"context"
"crypto/tls"
"crypto/x509"
"io/ioutil"
"encoding/base64"
"encoding/json"
"errors"
Expand All @@ -29,6 +31,9 @@ type mongoDBConnectionProducer struct {
WriteConcern string `json:"write_concern" structs:"write_concern" mapstructure:"write_concern"`
Username string `json:"username" structs:"username" mapstructure:"username"`
Password string `json:"password" structs:"password" mapstructure:"password"`
SSLCert string `json:"ssl_cert" structs:"ssl_cert" mapstructure:"ssl_cert"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These should all be labeled TLS instead of SSL.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch

SSLKey string `json:"ssl_key" structs:"ssl_key" mapstructure:"ssl_key"`
SSLCA string `json:"ssl_ca" structs:"ssl_ca" mapstructure:"ssl_ca"`

Initialized bool
RawConfig map[string]interface{}
Expand Down Expand Up @@ -119,7 +124,7 @@ func (c *mongoDBConnectionProducer) Connection(_ context.Context) (interface{},
c.session.Close()
}

dialInfo, err := parseMongoURL(c.ConnectionURL)
dialInfo, err := parseMongoURL(c.ConnectionURL, c.SSLCert, c.SSLKey, c.SSLCA)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -153,7 +158,7 @@ func (c *mongoDBConnectionProducer) Close() error {
return nil
}

func parseMongoURL(rawURL string) (*mgo.DialInfo, error) {
func parseMongoURL(rawURL string, sslCert string, sslKey string, sslCA string) (*mgo.DialInfo, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If all variable are the same type, you can just repeat the variable names and put the type at the end.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great advice, thanks

url, err := url.Parse(rawURL)
if err != nil {
return nil, err
Expand Down Expand Up @@ -201,7 +206,37 @@ func parseMongoURL(rawURL string) (*mgo.DialInfo, error) {
}
if ssl {
info.DialServer = func(addr *mgo.ServerAddr) (net.Conn, error) {
return tls.Dial("tcp", addr.String(), &tls.Config{})
tlsConfig := &tls.Config{}
if sslCert != "" && sslKey != "" && sslCA != "" {
clientCertPEM, err := ioutil.ReadFile(sslCert);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The API server will not have access to local resources. You must consume the PEM encoded string directly.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I totally forgot about that. I have changed the code to consume as strings instead

if err != nil {
return nil, errors.New("could not read file: " + sslCert)
}
clientKeyPEM, err := ioutil.ReadFile(sslKey);
if err != nil {
return nil, errors.New("could not read file: " + sslKey)
}
caPEM, err := ioutil.ReadFile(sslCA);
if err != nil {
return nil, errors.New("could not read file: " + sslCA)
}
caCerts := x509.NewCertPool()
ok := caCerts.AppendCertsFromPEM([]byte(caPEM))
if !ok {
return nil, errors.New("failed to parse root certificate " + sslCA)
}
clientCert, err := tls.X509KeyPair(clientCertPEM, clientKeyPEM)
clientCert.Leaf, err = x509.ParseCertificate(clientCert.Certificate[0])
if err != nil {
return nil, errors.New("failed parsing ssl options")
}
tlsConfig = &tls.Config{
Certificates: []tls.Certificate{clientCert},
RootCAs: caCerts,
InsecureSkipVerify: false,
}
}
return tls.Dial("tcp", addr.String(), tlsConfig)
}
}
case "connect":
Expand Down
4 changes: 2 additions & 2 deletions plugins/database/mongodb/mongodb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,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
}
Expand Down Expand Up @@ -203,7 +203,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
}
Expand Down
16 changes: 11 additions & 5 deletions website/source/api/secret/databases/mongodb.html.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,19 @@ has a number of parameters to further configure a connection.
### Parameters

- `connection_url` `(string: <required>)` – Specifies the MongoDB standard
connection string (URI). This field can be templated and supports passing the
username and password parameters in the following format {{field_name}}. A
connection string (URI). This field can be templated and supports passing the
username and password parameters in the following format {{field_name}}. A
templated connection URL is required when using root credential rotation.
- `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.
- `username` `(string: "")` - The root credential username used in the connection URL.
- `password` `(string: "")` - The root credential password used in the connection URL.
- `username` `(string: "")` - The root credential username used in the connection URL.
- `password` `(string: "")` - The root credential password used in the connection URL.
- `ssl_cert` `(string: "")` - Path to a PEM-encoded client certificate.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Path is an incorrect description since this is this is an API endpoint not a local resource.

- `ssl_key` `(string: "")` - Path to the private key of the client certificate.
- `ssl_ca` `(string: "")` - Path to a PEM-encoded CA certificate to verify the server against.

### Sample Payload

Expand All @@ -46,7 +49,10 @@ has a number of parameters to further configure a connection.
"connection_url": "mongodb://{{username}}:{{password}}@mongodb.acme.com:27017/admin?ssl=true",
"write_concern": "{ \"wmode\": \"majority\", \"wtimeout\": 5000 }",
"username": "admin",
"password": "Password!"
"password": "Password!",
"ssl_cert": "/path/to/ssl/cert",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These parameters would not be paths. They should be PEM encoded certs.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right

"ssl_key": "/path/to/ssl/key",
"ssl_ca": "/path/to/ssl/ca"
}
```

Expand Down
5 changes: 4 additions & 1 deletion website/source/docs/secrets/databases/mongodb.html.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@ more information about setting up the database secrets engine.
allowed_roles="my-role" \
connection_url="mongodb://{{username}}:{{password}}@mongodb.acme.com:27017/admin?ssl=true" \
username="admin" \
password="Password!"
password="Password!" \
ssl_cert="path/to/ssl/cert" \
ssl_key="path/to/ssl/key" \
ssl_ca="path/to/ssl/ca"
```

1. Configure a role that maps a name in Vault to an SQL statement to execute to
Expand Down