diff --git a/builtin/logical/database/dbplugin/databasemiddleware.go b/builtin/logical/database/dbplugin/databasemiddleware.go index d38efd8951b6..752dcda43cf5 100644 --- a/builtin/logical/database/dbplugin/databasemiddleware.go +++ b/builtin/logical/database/dbplugin/databasemiddleware.go @@ -5,6 +5,7 @@ import ( "errors" "net/url" "strings" + "sync" "time" "github.com/hashicorp/errwrap" @@ -205,59 +206,73 @@ func (mw *databaseMetricsMiddleware) Close() (err error) { // ---- Error Sanitizer Middleware Domain ---- -// databaseErrorSanitizerMiddleware wraps an implementation of Databases and +// DatabaseErrorSanitizerMiddleware wraps an implementation of Databases and // sanitizes returned error messages -type databaseErrorSanitizerMiddleware struct { - next Database +type DatabaseErrorSanitizerMiddleware struct { + l sync.RWMutex + next Database + secretsFn func() []string +} + +func NewDatabaseErrorSanitizerMiddleware(next Database, secretsFn func() []string) *DatabaseErrorSanitizerMiddleware { + return &DatabaseErrorSanitizerMiddleware{ + next: next, + secretsFn: secretsFn, + } } -func (mw *databaseErrorSanitizerMiddleware) Type() (string, error) { +func (mw *DatabaseErrorSanitizerMiddleware) Type() (string, error) { dbType, err := mw.next.Type() return dbType, mw.sanitize(err) } -func (mw *databaseErrorSanitizerMiddleware) CreateUser(ctx context.Context, statements Statements, usernameConfig UsernameConfig, expiration time.Time) (username string, password string, err error) { +func (mw *DatabaseErrorSanitizerMiddleware) CreateUser(ctx context.Context, statements Statements, usernameConfig UsernameConfig, expiration time.Time) (username string, password string, err error) { username, password, err = mw.next.CreateUser(ctx, statements, usernameConfig, expiration) return username, password, mw.sanitize(err) } -func (mw *databaseErrorSanitizerMiddleware) RenewUser(ctx context.Context, statements Statements, username string, expiration time.Time) (err error) { +func (mw *DatabaseErrorSanitizerMiddleware) RenewUser(ctx context.Context, statements Statements, username string, expiration time.Time) (err error) { return mw.sanitize(mw.next.RenewUser(ctx, statements, username, expiration)) } -func (mw *databaseErrorSanitizerMiddleware) RevokeUser(ctx context.Context, statements Statements, username string) (err error) { +func (mw *DatabaseErrorSanitizerMiddleware) RevokeUser(ctx context.Context, statements Statements, username string) (err error) { return mw.sanitize(mw.next.RevokeUser(ctx, statements, username)) } -func (mw *databaseErrorSanitizerMiddleware) RotateRootCredentials(ctx context.Context, statements []string) (conf map[string]interface{}, err error) { +func (mw *DatabaseErrorSanitizerMiddleware) RotateRootCredentials(ctx context.Context, statements []string) (conf map[string]interface{}, err error) { conf, err = mw.next.RotateRootCredentials(ctx, statements) return conf, mw.sanitize(err) } -func (mw *databaseErrorSanitizerMiddleware) Initialize(ctx context.Context, conf map[string]interface{}, verifyConnection bool) error { +func (mw *DatabaseErrorSanitizerMiddleware) Initialize(ctx context.Context, conf map[string]interface{}, verifyConnection bool) error { _, err := mw.Init(ctx, conf, verifyConnection) return err } -func (mw *databaseErrorSanitizerMiddleware) Init(ctx context.Context, conf map[string]interface{}, verifyConnection bool) (saveConf map[string]interface{}, err error) { +func (mw *DatabaseErrorSanitizerMiddleware) Init(ctx context.Context, conf map[string]interface{}, verifyConnection bool) (saveConf map[string]interface{}, err error) { saveConf, err = mw.next.Init(ctx, conf, verifyConnection) return saveConf, mw.sanitize(err) } -func (mw *databaseErrorSanitizerMiddleware) Close() (err error) { +func (mw *DatabaseErrorSanitizerMiddleware) Close() (err error) { return mw.sanitize(mw.next.Close()) } // sanitize -func (mw *databaseErrorSanitizerMiddleware) sanitize(err error) error { +func (mw *DatabaseErrorSanitizerMiddleware) sanitize(err error) error { if err == nil { return nil } - errStr := err.Error() - if errwrap.ContainsType(err, new(url.Error)) || - strings.Contains(errStr, "//") || - strings.Contains(errStr, "@") { + if errwrap.ContainsType(err, new(url.Error)) { return errors.New("unable to parse connection url") } + if mw.secretsFn != nil { + for _, secret := range mw.secretsFn() { + if secret == "" { + continue + } + err = errors.New(strings.Replace(err.Error(), secret, "*****", -1)) + } + } return err } diff --git a/builtin/logical/database/dbplugin/plugin.go b/builtin/logical/database/dbplugin/plugin.go index 37175b5cc38c..c406d2603e92 100644 --- a/builtin/logical/database/dbplugin/plugin.go +++ b/builtin/logical/database/dbplugin/plugin.go @@ -119,7 +119,7 @@ type DatabasePlugin struct { } func (d DatabasePlugin) Server(*plugin.MuxBroker) (interface{}, error) { - impl := &databaseErrorSanitizerMiddleware{ + impl := &DatabaseErrorSanitizerMiddleware{ next: d.impl, } @@ -131,7 +131,7 @@ func (DatabasePlugin) Client(b *plugin.MuxBroker, c *rpc.Client) (interface{}, e } func (d DatabasePlugin) GRPCServer(_ *plugin.GRPCBroker, s *grpc.Server) error { - impl := &databaseErrorSanitizerMiddleware{ + impl := &DatabaseErrorSanitizerMiddleware{ next: d.impl, } diff --git a/plugins/database/cassandra/cassandra.go b/plugins/database/cassandra/cassandra.go index 304ccbb19b8c..6fd36cf1b4cd 100644 --- a/plugins/database/cassandra/cassandra.go +++ b/plugins/database/cassandra/cassandra.go @@ -43,11 +43,13 @@ func New() (interface{}, error) { Separator: "_", } - dbType := &Cassandra{ + db := &Cassandra{ ConnectionProducer: connProducer, CredentialsProducer: credsProducer, } + dbType := dbplugin.NewDatabaseErrorSanitizerMiddleware(db, connProducer.secretValues) + return dbType, nil } diff --git a/plugins/database/cassandra/connection_producer.go b/plugins/database/cassandra/connection_producer.go index 7d3e2e181175..fed1f1d806cd 100644 --- a/plugins/database/cassandra/connection_producer.go +++ b/plugins/database/cassandra/connection_producer.go @@ -242,3 +242,7 @@ func (c *cassandraConnectionProducer) createSession() (*gocql.Session, error) { return session, nil } + +func (c *cassandraConnectionProducer) secretValues() []string { + return []string{c.Password, c.PemBundle, c.PemJSON} +} diff --git a/plugins/database/hana/hana.go b/plugins/database/hana/hana.go index 70938810655e..9087968147bd 100644 --- a/plugins/database/hana/hana.go +++ b/plugins/database/hana/hana.go @@ -41,11 +41,14 @@ func New() (interface{}, error) { Separator: "_", } - dbType := &HANA{ + db := &HANA{ ConnectionProducer: connProducer, CredentialsProducer: credsProducer, } + // Wrap the plugin with middleware to sanitize errors + dbType := dbplugin.NewDatabaseErrorSanitizerMiddleware(db, db.SecretValues) + return dbType, nil } @@ -56,7 +59,7 @@ func Run(apiTLSConfig *api.TLSConfig) error { return err } - plugins.Serve(dbType.(*HANA), apiTLSConfig) + plugins.Serve(dbType.(dbplugin.Database), apiTLSConfig) return nil } diff --git a/plugins/database/mongodb/connection_producer.go b/plugins/database/mongodb/connection_producer.go index fbb60d1648df..91c6fd343976 100644 --- a/plugins/database/mongodb/connection_producer.go +++ b/plugins/database/mongodb/connection_producer.go @@ -16,6 +16,7 @@ import ( "github.com/hashicorp/errwrap" "github.com/hashicorp/vault/plugins/helper/database/connutil" + "github.com/hashicorp/vault/plugins/helper/database/dbutil" "github.com/mitchellh/mapstructure" "gopkg.in/mgo.v2" @@ -26,8 +27,11 @@ 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"` Initialized bool + RawConfig map[string]interface{} Type string session *mgo.Session safe *mgo.Safe @@ -44,6 +48,8 @@ func (c *mongoDBConnectionProducer) Init(ctx context.Context, conf map[string]in c.Lock() defer c.Unlock() + c.RawConfig = conf + err := mapstructure.WeakDecode(conf, c) if err != nil { return nil, err @@ -53,6 +59,11 @@ 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 @@ -209,3 +220,7 @@ func parseMongoURL(rawURL string) (*mgo.DialInfo, error) { return &info, nil } + +func (c *mongoDBConnectionProducer) secretValues() []string { + return []string{c.Password} +} diff --git a/plugins/database/mongodb/mongodb.go b/plugins/database/mongodb/mongodb.go index 93eaccf772c3..ce473db7f917 100644 --- a/plugins/database/mongodb/mongodb.go +++ b/plugins/database/mongodb/mongodb.go @@ -14,7 +14,6 @@ import ( "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" @@ -24,7 +23,7 @@ const mongoDBTypeName = "mongodb" // MongoDB is an implementation of Database interface type MongoDB struct { - connutil.ConnectionProducer + *mongoDBConnectionProducer credsutil.CredentialsProducer } @@ -42,10 +41,12 @@ func New() (interface{}, error) { Separator: "-", } - dbType := &MongoDB{ - ConnectionProducer: connProducer, - CredentialsProducer: credsProducer, + db := &MongoDB{ + mongoDBConnectionProducer: connProducer, + CredentialsProducer: credsProducer, } + + dbType := dbplugin.NewDatabaseErrorSanitizerMiddleware(db, db.secretValues) return dbType, nil } @@ -191,7 +192,7 @@ func (m *MongoDB) RevokeUser(ctx context.Context, statements dbplugin.Statements switch { case err == nil, err == mgo.ErrNotFound: case err == io.EOF, strings.Contains(err.Error(), "EOF"): - if err := m.ConnectionProducer.Close(); err != nil { + if err := m.Close(); err != nil { return errwrap.Wrapf("error closing EOF'd mongo connection: {{err}}", err) } session, err := m.getConnection(ctx) diff --git a/plugins/database/mssql/mssql.go b/plugins/database/mssql/mssql.go index 85e2b56c1c03..63343e3b39fe 100644 --- a/plugins/database/mssql/mssql.go +++ b/plugins/database/mssql/mssql.go @@ -40,11 +40,14 @@ func New() (interface{}, error) { Separator: "-", } - dbType := &MSSQL{ + db := &MSSQL{ SQLConnectionProducer: connProducer, CredentialsProducer: credsProducer, } + // Wrap the plugin with middleware to sanitize errors + dbType := dbplugin.NewDatabaseErrorSanitizerMiddleware(db, db.SecretValues) + return dbType, nil } @@ -55,7 +58,7 @@ func Run(apiTLSConfig *api.TLSConfig) error { return err } - plugins.Serve(dbType.(*MSSQL), apiTLSConfig) + plugins.Serve(dbType.(dbplugin.Database), apiTLSConfig) return nil } diff --git a/plugins/database/mysql/mysql.go b/plugins/database/mysql/mysql.go index 8fe301e40905..57f5a6d79ea5 100644 --- a/plugins/database/mysql/mysql.go +++ b/plugins/database/mysql/mysql.go @@ -57,11 +57,14 @@ func New(displayNameLen, roleNameLen, usernameLen int) func() (interface{}, erro Separator: "-", } - dbType := &MySQL{ + db := &MySQL{ SQLConnectionProducer: connProducer, CredentialsProducer: credsProducer, } + // Wrap the plugin with middleware to sanitize errors + dbType := dbplugin.NewDatabaseErrorSanitizerMiddleware(db, db.SecretValues) + return dbType, nil } } @@ -88,7 +91,7 @@ func runCommon(legacy bool, apiTLSConfig *api.TLSConfig) error { return err } - plugins.Serve(dbType.(*MySQL), apiTLSConfig) + plugins.Serve(dbType.(dbplugin.Database), apiTLSConfig) return nil } diff --git a/plugins/database/postgresql/postgresql.go b/plugins/database/postgresql/postgresql.go index e66c21c6fc5b..76cf8b22bbf0 100644 --- a/plugins/database/postgresql/postgresql.go +++ b/plugins/database/postgresql/postgresql.go @@ -43,11 +43,14 @@ func New() (interface{}, error) { Separator: "-", } - dbType := &PostgreSQL{ + db := &PostgreSQL{ SQLConnectionProducer: connProducer, CredentialsProducer: credsProducer, } + // Wrap the plugin with middleware to sanitize errors + dbType := dbplugin.NewDatabaseErrorSanitizerMiddleware(db, db.SecretValues) + return dbType, nil } @@ -58,7 +61,7 @@ func Run(apiTLSConfig *api.TLSConfig) error { return err } - plugins.Serve(dbType.(*PostgreSQL), apiTLSConfig) + plugins.Serve(dbType.(dbplugin.Database), apiTLSConfig) return nil } @@ -202,11 +205,7 @@ func (p *PostgreSQL) RenewUser(ctx context.Context, statements dbplugin.Statemen } } - if err := tx.Commit(); err != nil { - return err - } - - return nil + return tx.Commit() } func (p *PostgreSQL) RevokeUser(ctx context.Context, statements dbplugin.Statements, username string) error { @@ -256,11 +255,7 @@ func (p *PostgreSQL) customRevokeUser(ctx context.Context, username string, revo } } - if err := tx.Commit(); err != nil { - return err - } - - return nil + return tx.Commit() } func (p *PostgreSQL) defaultRevokeUser(ctx context.Context, username string) error { diff --git a/plugins/helper/database/connutil/connutil.go b/plugins/helper/database/connutil/connutil.go index 58c2ac9e495c..45f6fa0ad7a7 100644 --- a/plugins/helper/database/connutil/connutil.go +++ b/plugins/helper/database/connutil/connutil.go @@ -18,8 +18,8 @@ type ConnectionProducer interface { Init(context.Context, map[string]interface{}, bool) (map[string]interface{}, error) Connection(context.Context) (interface{}, error) + sync.Locker + // DEPRECATED, will be removed in 0.12 Initialize(context.Context, map[string]interface{}, bool) error - - sync.Locker } diff --git a/plugins/helper/database/connutil/sql.go b/plugins/helper/database/connutil/sql.go index a2e10777cc23..3633e3dca333 100644 --- a/plugins/helper/database/connutil/sql.go +++ b/plugins/helper/database/connutil/sql.go @@ -142,6 +142,10 @@ func (c *SQLConnectionProducer) Connection(ctx context.Context) (interface{}, er return c.db, nil } +func (c *SQLConnectionProducer) SecretValues() []string { + return []string{c.Password} +} + // Close attempts to close the connection func (c *SQLConnectionProducer) Close() error { // Grab the write lock