Add x509 Client Auth to MongoDB Database Plugin (#8329)

* Mark deprecated plugins as deprecated

* Add redaction capability to database plugins

* Add x509 client auth

* Update vendored files

* Add integration test for x509 client auth

* Remove redaction logic pending further discussion

* Update vendored files

* Minor updates from code review

* Updated docs with x509 client auth

* Roles are required

* Disable x509 test because it doesn't work in CircleCI

* Add timeouts for container lifetime
This commit is contained in:
Michael Golowka
2020-02-13 15:54:00 -07:00
committed by GitHub
parent 33a7011e99
commit f96f4eebfc
10 changed files with 809 additions and 63 deletions

View File

@@ -2,6 +2,8 @@ package mongodb
import (
"context"
"crypto/tls"
"crypto/x509"
"encoding/base64"
"encoding/json"
"fmt"
@@ -23,8 +25,12 @@ import (
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"`
Username string `json:"username" structs:"username" mapstructure:"username"`
Password string `json:"password" structs:"password" mapstructure:"password"`
Username string `json:"username" structs:"username" mapstructure:"username"`
Password string `json:"password" structs:"password" mapstructure:"password"`
TLSCertificateKeyData []byte `json:"tls_certificate_key" structs:"-" mapstructure:"tls_certificate_key"`
TLSCAData []byte `json:"tls_ca" structs:"-" mapstructure:"tls_ca"`
Initialized bool
RawConfig map[string]interface{}
@@ -64,58 +70,19 @@ func (c *mongoDBConnectionProducer) Init(ctx context.Context, conf map[string]in
return nil, fmt.Errorf("connection_url cannot be empty")
}
c.ConnectionURL = dbutil.QueryHelper(c.ConnectionURL, map[string]string{
"username": c.Username,
"password": c.Password,
})
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 := &writeConcern{}
err = json.Unmarshal([]byte(input), concern)
if err != nil {
return nil, errwrap.Wrapf("error unmarshalling write_concern: {{err}}", err)
}
// Translate write concern to mongo options
var w writeconcern.Option
switch {
case concern.W != 0:
w = writeconcern.W(concern.W)
case concern.WMode != "":
w = writeconcern.WTagSet(concern.WMode)
default:
w = writeconcern.WMajority()
}
var j writeconcern.Option
switch {
case concern.FSync:
j = writeconcern.J(concern.FSync)
case concern.J:
j = writeconcern.J(concern.J)
default:
j = writeconcern.J(false)
}
writeConcern := writeconcern.New(
w,
j,
writeconcern.WTimeout(time.Duration(concern.WTimeout)*time.Millisecond))
c.clientOptions = &options.ClientOptions{
WriteConcern: writeConcern,
}
writeOpts, err := c.getWriteConcern()
if err != nil {
return nil, err
}
authOpts, err := c.getTLSAuth()
if err != nil {
return nil, err
}
c.ConnectionURL = c.getConnectionURL()
c.clientOptions = options.MergeClientOptions(writeOpts, authOpts)
// 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
@@ -157,7 +124,8 @@ func (c *mongoDBConnectionProducer) Connection(ctx context.Context) (interface{}
c.clientOptions.SetConnectTimeout(1 * time.Minute)
var err error
c.client, err = mongo.Connect(ctx, c.clientOptions.ApplyURI(c.ConnectionURL))
opts := c.clientOptions.ApplyURI(c.ConnectionURL)
c.client, err = mongo.Connect(ctx, opts)
if err != nil {
return nil, err
}
@@ -187,3 +155,98 @@ func (c *mongoDBConnectionProducer) secretValues() map[string]interface{} {
c.Password: "[password]",
}
}
func (c *mongoDBConnectionProducer) getConnectionURL() (connURL string) {
connURL = dbutil.QueryHelper(c.ConnectionURL, map[string]string{
"username": c.Username,
"password": c.Password,
})
return connURL
}
func (c *mongoDBConnectionProducer) getWriteConcern() (opts *options.ClientOptions, err error) {
if c.WriteConcern == "" {
return nil, nil
}
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 := &writeConcern{}
err = json.Unmarshal([]byte(input), concern)
if err != nil {
return nil, errwrap.Wrapf("error unmarshalling write_concern: {{err}}", err)
}
// Translate write concern to mongo options
var w writeconcern.Option
switch {
case concern.W != 0:
w = writeconcern.W(concern.W)
case concern.WMode != "":
w = writeconcern.WTagSet(concern.WMode)
default:
w = writeconcern.WMajority()
}
var j writeconcern.Option
switch {
case concern.FSync:
j = writeconcern.J(concern.FSync)
case concern.J:
j = writeconcern.J(concern.J)
default:
j = writeconcern.J(false)
}
writeConcern := writeconcern.New(
w,
j,
writeconcern.WTimeout(time.Duration(concern.WTimeout)*time.Millisecond))
opts = options.Client()
opts.SetWriteConcern(writeConcern)
return opts, nil
}
func (c *mongoDBConnectionProducer) getTLSAuth() (opts *options.ClientOptions, err error) {
if len(c.TLSCAData) == 0 && len(c.TLSCertificateKeyData) == 0 {
return nil, nil
}
opts = options.Client()
tlsConfig := &tls.Config{}
if len(c.TLSCAData) > 0 {
tlsConfig.RootCAs = x509.NewCertPool()
ok := tlsConfig.RootCAs.AppendCertsFromPEM(c.TLSCAData)
if !ok {
return nil, fmt.Errorf("failed to append CA to client options")
}
}
if len(c.TLSCertificateKeyData) > 0 {
certificate, err := tls.X509KeyPair(c.TLSCertificateKeyData, c.TLSCertificateKeyData)
if err != nil {
return nil, fmt.Errorf("unable to load tls_certificate_key_data: %w", err)
}
opts.SetAuth(options.Credential{
AuthMechanism: "MONGODB-X509",
Username: c.Username,
})
tlsConfig.Certificates = append(tlsConfig.Certificates, certificate)
}
opts.SetTLSConfig(tlsConfig)
return opts, nil
}