open-vault/plugins/database/mongodb/connection_producer.go

261 lines
7.0 KiB
Go
Raw Normal View History

package mongodb
import (
"context"
"crypto/tls"
"crypto/x509"
"encoding/base64"
"encoding/json"
"fmt"
"sync"
"time"
Database Root Credential Rotation (#3976) * redoing connection handling * a little more cleanup * empty implementation of rotation * updating rotate signature * signature update * updating interfaces again :( * changing back to interface * adding templated url support and rotation for postgres * adding correct username * return updates * updating statements to be a list * adding error sanitizing middleware * fixing log sanitizier * adding postgres rotate test * removing conf from rotate * adding rotate command * adding mysql rotate * finishing up the endpoint in the db backend for rotate * no more structs, just store raw config * fixing tests * adding db instance lock * adding support for statement list in cassandra * wip redoing interface to support BC * adding falllback for Initialize implementation * adding backwards compat for statements * fix tests * fix more tests * fixing up tests, switching to new fields in statements * fixing more tests * adding mssql and mysql * wrapping all the things in middleware, implementing templating for mongodb * wrapping all db servers with error santizer * fixing test * store the name with the db instance * adding rotate to cassandra * adding compatibility translation to both server and plugin * reordering a few things * store the name with the db instance * reordering * adding a few more tests * switch secret values from slice to map * addressing some feedback * reinstate execute plugin after resetting connection * set database connection to closed * switching secret values func to map[string]interface for potential future uses * addressing feedback
2018-03-21 19:05:56 +00:00
"github.com/hashicorp/errwrap"
2019-04-15 18:10:07 +00:00
"github.com/hashicorp/vault/sdk/database/helper/connutil"
"github.com/hashicorp/vault/sdk/database/helper/dbutil"
"github.com/mitchellh/mapstructure"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"go.mongodb.org/mongo-driver/mongo/readpref"
"go.mongodb.org/mongo-driver/mongo/writeconcern"
)
// 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"`
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"`
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{}
Type string
clientOptions *options.ClientOptions
client *mongo.Client
sync.Mutex
}
// writeConcern defines the write concern options
type writeConcern struct {
W int // Min # of servers to ack before success
WMode string // Write mode for MongoDB 2.0+ (e.g. "majority")
WTimeout int // Milliseconds to wait for W before timing out
FSync bool // DEPRECATED: Is now handled by J. See: https://jira.mongodb.org/browse/CXX-910
J bool // Sync via the journal if present
}
func (c *mongoDBConnectionProducer) Initialize(ctx context.Context, conf map[string]interface{}, verifyConnection bool) error {
Database Root Credential Rotation (#3976) * redoing connection handling * a little more cleanup * empty implementation of rotation * updating rotate signature * signature update * updating interfaces again :( * changing back to interface * adding templated url support and rotation for postgres * adding correct username * return updates * updating statements to be a list * adding error sanitizing middleware * fixing log sanitizier * adding postgres rotate test * removing conf from rotate * adding rotate command * adding mysql rotate * finishing up the endpoint in the db backend for rotate * no more structs, just store raw config * fixing tests * adding db instance lock * adding support for statement list in cassandra * wip redoing interface to support BC * adding falllback for Initialize implementation * adding backwards compat for statements * fix tests * fix more tests * fixing up tests, switching to new fields in statements * fixing more tests * adding mssql and mysql * wrapping all the things in middleware, implementing templating for mongodb * wrapping all db servers with error santizer * fixing test * store the name with the db instance * adding rotate to cassandra * adding compatibility translation to both server and plugin * reordering a few things * store the name with the db instance * reordering * adding a few more tests * switch secret values from slice to map * addressing some feedback * reinstate execute plugin after resetting connection * set database connection to closed * switching secret values func to map[string]interface for potential future uses * addressing feedback
2018-03-21 19:05:56 +00:00
_, err := c.Init(ctx, conf, verifyConnection)
return err
}
// Initialize parses connection configuration.
func (c *mongoDBConnectionProducer) Init(ctx context.Context, conf map[string]interface{}, verifyConnection bool) (map[string]interface{}, error) {
c.Lock()
defer c.Unlock()
Database Root Credential Rotation (#3976) * redoing connection handling * a little more cleanup * empty implementation of rotation * updating rotate signature * signature update * updating interfaces again :( * changing back to interface * adding templated url support and rotation for postgres * adding correct username * return updates * updating statements to be a list * adding error sanitizing middleware * fixing log sanitizier * adding postgres rotate test * removing conf from rotate * adding rotate command * adding mysql rotate * finishing up the endpoint in the db backend for rotate * no more structs, just store raw config * fixing tests * adding db instance lock * adding support for statement list in cassandra * wip redoing interface to support BC * adding falllback for Initialize implementation * adding backwards compat for statements * fix tests * fix more tests * fixing up tests, switching to new fields in statements * fixing more tests * adding mssql and mysql * wrapping all the things in middleware, implementing templating for mongodb * wrapping all db servers with error santizer * fixing test * store the name with the db instance * adding rotate to cassandra * adding compatibility translation to both server and plugin * reordering a few things * store the name with the db instance * reordering * adding a few more tests * switch secret values from slice to map * addressing some feedback * reinstate execute plugin after resetting connection * set database connection to closed * switching secret values func to map[string]interface for potential future uses * addressing feedback
2018-03-21 19:05:56 +00:00
c.RawConfig = conf
err := mapstructure.WeakDecode(conf, c)
if err != nil {
Database Root Credential Rotation (#3976) * redoing connection handling * a little more cleanup * empty implementation of rotation * updating rotate signature * signature update * updating interfaces again :( * changing back to interface * adding templated url support and rotation for postgres * adding correct username * return updates * updating statements to be a list * adding error sanitizing middleware * fixing log sanitizier * adding postgres rotate test * removing conf from rotate * adding rotate command * adding mysql rotate * finishing up the endpoint in the db backend for rotate * no more structs, just store raw config * fixing tests * adding db instance lock * adding support for statement list in cassandra * wip redoing interface to support BC * adding falllback for Initialize implementation * adding backwards compat for statements * fix tests * fix more tests * fixing up tests, switching to new fields in statements * fixing more tests * adding mssql and mysql * wrapping all the things in middleware, implementing templating for mongodb * wrapping all db servers with error santizer * fixing test * store the name with the db instance * adding rotate to cassandra * adding compatibility translation to both server and plugin * reordering a few things * store the name with the db instance * reordering * adding a few more tests * switch secret values from slice to map * addressing some feedback * reinstate execute plugin after resetting connection * set database connection to closed * switching secret values func to map[string]interface for potential future uses * addressing feedback
2018-03-21 19:05:56 +00:00
return nil, err
}
if len(c.ConnectionURL) == 0 {
Database Root Credential Rotation (#3976) * redoing connection handling * a little more cleanup * empty implementation of rotation * updating rotate signature * signature update * updating interfaces again :( * changing back to interface * adding templated url support and rotation for postgres * adding correct username * return updates * updating statements to be a list * adding error sanitizing middleware * fixing log sanitizier * adding postgres rotate test * removing conf from rotate * adding rotate command * adding mysql rotate * finishing up the endpoint in the db backend for rotate * no more structs, just store raw config * fixing tests * adding db instance lock * adding support for statement list in cassandra * wip redoing interface to support BC * adding falllback for Initialize implementation * adding backwards compat for statements * fix tests * fix more tests * fixing up tests, switching to new fields in statements * fixing more tests * adding mssql and mysql * wrapping all the things in middleware, implementing templating for mongodb * wrapping all db servers with error santizer * fixing test * store the name with the db instance * adding rotate to cassandra * adding compatibility translation to both server and plugin * reordering a few things * store the name with the db instance * reordering * adding a few more tests * switch secret values from slice to map * addressing some feedback * reinstate execute plugin after resetting connection * set database connection to closed * switching secret values func to map[string]interface for potential future uses * addressing feedback
2018-03-21 19:05:56 +00:00
return nil, fmt.Errorf("connection_url cannot be empty")
}
writeOpts, err := c.getWriteConcern()
if err != nil {
return nil, err
}
authOpts, err := c.getTLSAuth()
if err != nil {
return nil, err
}
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
if verifyConnection {
if _, err := c.Connection(ctx); err != nil {
Database Root Credential Rotation (#3976) * redoing connection handling * a little more cleanup * empty implementation of rotation * updating rotate signature * signature update * updating interfaces again :( * changing back to interface * adding templated url support and rotation for postgres * adding correct username * return updates * updating statements to be a list * adding error sanitizing middleware * fixing log sanitizier * adding postgres rotate test * removing conf from rotate * adding rotate command * adding mysql rotate * finishing up the endpoint in the db backend for rotate * no more structs, just store raw config * fixing tests * adding db instance lock * adding support for statement list in cassandra * wip redoing interface to support BC * adding falllback for Initialize implementation * adding backwards compat for statements * fix tests * fix more tests * fixing up tests, switching to new fields in statements * fixing more tests * adding mssql and mysql * wrapping all the things in middleware, implementing templating for mongodb * wrapping all db servers with error santizer * fixing test * store the name with the db instance * adding rotate to cassandra * adding compatibility translation to both server and plugin * reordering a few things * store the name with the db instance * reordering * adding a few more tests * switch secret values from slice to map * addressing some feedback * reinstate execute plugin after resetting connection * set database connection to closed * switching secret values func to map[string]interface for potential future uses * addressing feedback
2018-03-21 19:05:56 +00:00
return nil, errwrap.Wrapf("error verifying connection: {{err}}", err)
}
if err := c.client.Ping(ctx, readpref.Primary()); err != nil {
Database Root Credential Rotation (#3976) * redoing connection handling * a little more cleanup * empty implementation of rotation * updating rotate signature * signature update * updating interfaces again :( * changing back to interface * adding templated url support and rotation for postgres * adding correct username * return updates * updating statements to be a list * adding error sanitizing middleware * fixing log sanitizier * adding postgres rotate test * removing conf from rotate * adding rotate command * adding mysql rotate * finishing up the endpoint in the db backend for rotate * no more structs, just store raw config * fixing tests * adding db instance lock * adding support for statement list in cassandra * wip redoing interface to support BC * adding falllback for Initialize implementation * adding backwards compat for statements * fix tests * fix more tests * fixing up tests, switching to new fields in statements * fixing more tests * adding mssql and mysql * wrapping all the things in middleware, implementing templating for mongodb * wrapping all db servers with error santizer * fixing test * store the name with the db instance * adding rotate to cassandra * adding compatibility translation to both server and plugin * reordering a few things * store the name with the db instance * reordering * adding a few more tests * switch secret values from slice to map * addressing some feedback * reinstate execute plugin after resetting connection * set database connection to closed * switching secret values func to map[string]interface for potential future uses * addressing feedback
2018-03-21 19:05:56 +00:00
return nil, errwrap.Wrapf("error verifying connection: {{err}}", err)
}
}
Database Root Credential Rotation (#3976) * redoing connection handling * a little more cleanup * empty implementation of rotation * updating rotate signature * signature update * updating interfaces again :( * changing back to interface * adding templated url support and rotation for postgres * adding correct username * return updates * updating statements to be a list * adding error sanitizing middleware * fixing log sanitizier * adding postgres rotate test * removing conf from rotate * adding rotate command * adding mysql rotate * finishing up the endpoint in the db backend for rotate * no more structs, just store raw config * fixing tests * adding db instance lock * adding support for statement list in cassandra * wip redoing interface to support BC * adding falllback for Initialize implementation * adding backwards compat for statements * fix tests * fix more tests * fixing up tests, switching to new fields in statements * fixing more tests * adding mssql and mysql * wrapping all the things in middleware, implementing templating for mongodb * wrapping all db servers with error santizer * fixing test * store the name with the db instance * adding rotate to cassandra * adding compatibility translation to both server and plugin * reordering a few things * store the name with the db instance * reordering * adding a few more tests * switch secret values from slice to map * addressing some feedback * reinstate execute plugin after resetting connection * set database connection to closed * switching secret values func to map[string]interface for potential future uses * addressing feedback
2018-03-21 19:05:56 +00:00
return conf, nil
}
2018-03-20 18:54:10 +00:00
// Connection creates or returns an existing a database connection. If the session fails
// on a ping check, the session will be closed and then re-created.
// This method does not lock the mutex and it is intended that this is the callers
// responsibility.
func (c *mongoDBConnectionProducer) Connection(ctx context.Context) (interface{}, error) {
if !c.Initialized {
return nil, connutil.ErrNotInitialized
}
if c.client != nil {
if err := c.client.Ping(ctx, readpref.Primary()); err == nil {
return c.client, nil
}
// Ignore error on purpose since we want to re-create a session
_ = c.client.Disconnect(ctx)
}
connURL := c.getConnectionURL()
client, err := createClient(ctx, connURL, c.clientOptions)
if err != nil {
return nil, err
}
c.client = client
return c.client, nil
}
func createClient(ctx context.Context, connURL string, clientOptions *options.ClientOptions) (client *mongo.Client, err error) {
if clientOptions == nil {
clientOptions = options.Client()
}
clientOptions.SetSocketTimeout(1 * time.Minute)
clientOptions.SetConnectTimeout(1 * time.Minute)
opts := clientOptions.ApplyURI(connURL)
client, err = mongo.Connect(ctx, opts)
if err != nil {
return nil, err
}
return client, nil
}
// Close terminates the database connection.
func (c *mongoDBConnectionProducer) Close() error {
c.Lock()
defer c.Unlock()
if c.client != nil {
2020-01-28 05:11:00 +00:00
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
defer cancel()
if err := c.client.Disconnect(ctx); err != nil {
return err
}
}
c.client = nil
return nil
}
Database Root Credential Rotation (#3976) * redoing connection handling * a little more cleanup * empty implementation of rotation * updating rotate signature * signature update * updating interfaces again :( * changing back to interface * adding templated url support and rotation for postgres * adding correct username * return updates * updating statements to be a list * adding error sanitizing middleware * fixing log sanitizier * adding postgres rotate test * removing conf from rotate * adding rotate command * adding mysql rotate * finishing up the endpoint in the db backend for rotate * no more structs, just store raw config * fixing tests * adding db instance lock * adding support for statement list in cassandra * wip redoing interface to support BC * adding falllback for Initialize implementation * adding backwards compat for statements * fix tests * fix more tests * fixing up tests, switching to new fields in statements * fixing more tests * adding mssql and mysql * wrapping all the things in middleware, implementing templating for mongodb * wrapping all db servers with error santizer * fixing test * store the name with the db instance * adding rotate to cassandra * adding compatibility translation to both server and plugin * reordering a few things * store the name with the db instance * reordering * adding a few more tests * switch secret values from slice to map * addressing some feedback * reinstate execute plugin after resetting connection * set database connection to closed * switching secret values func to map[string]interface for potential future uses * addressing feedback
2018-03-21 19:05:56 +00:00
func (c *mongoDBConnectionProducer) secretValues() map[string]interface{} {
return 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
}