open-vault/builtin/logical/database/path_config_connection.go

553 lines
18 KiB
Go
Raw Permalink Normal View History

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
2016-12-19 18:15:58 +00:00
package database
import (
"context"
2017-04-13 00:35:02 +00:00
"errors"
2016-12-19 18:15:58 +00:00
"fmt"
"net/url"
"sort"
2016-12-19 18:15:58 +00:00
"github.com/fatih/structs"
"github.com/hashicorp/go-uuid"
"github.com/hashicorp/go-version"
"github.com/hashicorp/vault/helper/versions"
v5 "github.com/hashicorp/vault/sdk/database/dbplugin/v5"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/helper/consts"
"github.com/hashicorp/vault/sdk/helper/pluginutil"
"github.com/hashicorp/vault/sdk/logical"
2016-12-19 18:15:58 +00:00
)
2017-04-13 00:35:02 +00:00
var (
respErrEmptyPluginName = "empty plugin name"
respErrEmptyName = "empty name attribute given"
2017-04-13 00:35:02 +00:00
)
2017-04-24 20:59:12 +00:00
// DatabaseConfig is used by the Factory function to configure a Database
2017-04-13 17:33:34 +00:00
// object.
type DatabaseConfig struct {
PluginName string `json:"plugin_name" structs:"plugin_name" mapstructure:"plugin_name"`
PluginVersion string `json:"plugin_version" structs:"plugin_version" mapstructure:"plugin_version"`
2017-04-13 17:33:34 +00:00
// ConnectionDetails stores the database specific connection settings needed
// by each database type.
ConnectionDetails map[string]interface{} `json:"connection_details" structs:"connection_details" mapstructure:"connection_details"`
AllowedRoles []string `json:"allowed_roles" structs:"allowed_roles" mapstructure:"allowed_roles"`
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
RootCredentialsRotateStatements []string `json:"root_credentials_rotate_statements" structs:"root_credentials_rotate_statements" mapstructure:"root_credentials_rotate_statements"`
PasswordPolicy string `json:"password_policy" structs:"password_policy" mapstructure:"password_policy"`
2017-04-13 17:33:34 +00:00
}
func (c *DatabaseConfig) SupportsCredentialType(credentialType v5.CredentialType) bool {
credTypes, ok := c.ConnectionDetails[v5.SupportedCredentialTypesKey].([]interface{})
if !ok {
// Default to supporting CredentialTypePassword for database plugins that
// don't specify supported credential types in the initialization response
return credentialType == v5.CredentialTypePassword
}
for _, ct := range credTypes {
if ct == credentialType.String() {
return true
}
}
return false
}
2017-04-11 18:50:34 +00:00
// pathResetConnection configures a path to reset a plugin.
func pathResetConnection(b *databaseBackend) *framework.Path {
return &framework.Path{
Pattern: fmt.Sprintf("reset/%s", framework.GenericNameRegex("name")),
DisplayAttrs: &framework.DisplayAttributes{
OperationPrefix: operationPrefixDatabase,
OperationVerb: "reset",
OperationSuffix: "connection",
},
Fields: map[string]*framework.FieldSchema{
"name": {
Type: framework.TypeString,
2017-04-13 00:35:02 +00:00
Description: "Name of this database connection",
},
},
Callbacks: map[logical.Operation]framework.OperationFunc{
2017-04-11 18:50:34 +00:00
logical.UpdateOperation: b.pathConnectionReset(),
},
2017-04-11 18:50:34 +00:00
HelpSynopsis: pathResetConnectionHelpSyn,
HelpDescription: pathResetConnectionHelpDesc,
}
}
2017-04-11 18:50:34 +00:00
// pathConnectionReset resets a plugin by closing the existing instance and
// creating a new one.
func (b *databaseBackend) pathConnectionReset() framework.OperationFunc {
return func(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
2017-04-11 18:50:34 +00:00
name := data.Get("name").(string)
if name == "" {
return logical.ErrorResponse(respErrEmptyName), nil
2017-04-11 18:50:34 +00:00
}
2017-04-13 00:35:02 +00:00
// Close plugin and delete the entry in the connections cache.
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
if err := b.ClearConnection(name); err != nil {
return nil, err
}
2017-04-13 00:35:02 +00:00
// Execute plugin again, we don't need the object so throw away.
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
if _, err := b.GetConnection(ctx, req.Storage, name); err != nil {
2017-04-11 18:50:34 +00:00
return nil, err
}
2017-04-11 18:50:34 +00:00
return nil, nil
}
}
2017-03-22 00:19:30 +00:00
// pathConfigurePluginConnection returns a configured framework.Path setup to
// operate on plugins.
func pathConfigurePluginConnection(b *databaseBackend) *framework.Path {
2016-12-19 18:15:58 +00:00
return &framework.Path{
2017-04-11 01:38:34 +00:00
Pattern: fmt.Sprintf("config/%s", framework.GenericNameRegex("name")),
DisplayAttrs: &framework.DisplayAttributes{
OperationPrefix: operationPrefixDatabase,
},
2016-12-19 18:15:58 +00:00
Fields: map[string]*framework.FieldSchema{
"name": {
2016-12-19 18:15:58 +00:00
Type: framework.TypeString,
2017-04-13 00:35:02 +00:00
Description: "Name of this database connection",
2016-12-19 18:15:58 +00:00
},
"plugin_name": {
2017-03-10 22:10:42 +00:00
Type: framework.TypeString,
2017-04-11 18:50:34 +00:00
Description: `The name of a builtin or previously registered
2017-04-13 17:33:34 +00:00
plugin known to vault. This endpoint will create an instance of
that plugin type.`,
2017-03-10 22:10:42 +00:00
},
2017-04-13 00:35:02 +00:00
"plugin_version": {
Type: framework.TypeString,
Description: `The version of the plugin to use.`,
},
"verify_connection": {
2017-04-13 00:35:02 +00:00
Type: framework.TypeBool,
Default: true,
Description: `If true, the connection details are verified by
2017-04-13 17:33:34 +00:00
actually connecting to the database. Defaults to true.`,
},
"allowed_roles": {
Type: framework.TypeCommaStringSlice,
Description: `Comma separated string or array of the role names
allowed to get creds from this database connection. If empty no
roles are allowed. If "*" all roles are allowed.`,
2017-04-13 00:35:02 +00:00
},
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
"root_rotation_statements": {
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
Type: framework.TypeStringSlice,
Description: `Specifies the database statements to be executed
to rotate the root user's credentials. See the plugin's API
page for more information on support and formatting for this
parameter.`,
},
"password_policy": {
Type: framework.TypeString,
Description: `Password policy to use when generating passwords.`,
},
2016-12-19 18:15:58 +00:00
},
ExistenceCheck: b.connectionExistenceCheck(),
Operations: map[logical.Operation]framework.OperationHandler{
logical.CreateOperation: &framework.PathOperation{
Callback: b.connectionWriteHandler(),
DisplayAttrs: &framework.DisplayAttributes{
OperationVerb: "configure",
OperationSuffix: "connection",
},
},
logical.UpdateOperation: &framework.PathOperation{
Callback: b.connectionWriteHandler(),
DisplayAttrs: &framework.DisplayAttributes{
OperationVerb: "configure",
OperationSuffix: "connection",
},
},
logical.ReadOperation: &framework.PathOperation{
Callback: b.connectionReadHandler(),
DisplayAttrs: &framework.DisplayAttributes{
OperationVerb: "read",
OperationSuffix: "connection-configuration",
},
},
logical.DeleteOperation: &framework.PathOperation{
Callback: b.connectionDeleteHandler(),
DisplayAttrs: &framework.DisplayAttributes{
OperationVerb: "delete",
OperationSuffix: "connection-configuration",
},
},
2016-12-19 18:15:58 +00:00
},
HelpSynopsis: pathConfigConnectionHelpSyn,
HelpDescription: pathConfigConnectionHelpDesc,
}
}
func (b *databaseBackend) connectionExistenceCheck() framework.ExistenceFunc {
return func(ctx context.Context, req *logical.Request, data *framework.FieldData) (bool, error) {
name := data.Get("name").(string)
if name == "" {
return false, errors.New(`missing "name" parameter`)
}
entry, err := req.Storage.Get(ctx, fmt.Sprintf("config/%s", name))
if err != nil {
return false, fmt.Errorf("failed to read connection configuration: %w", err)
}
return entry != nil, nil
}
}
func pathListPluginConnection(b *databaseBackend) *framework.Path {
return &framework.Path{
Pattern: fmt.Sprintf("config/?$"),
DisplayAttrs: &framework.DisplayAttributes{
OperationPrefix: operationPrefixDatabase,
OperationSuffix: "connections",
},
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.ListOperation: b.connectionListHandler(),
},
HelpSynopsis: pathConfigConnectionHelpSyn,
HelpDescription: pathConfigConnectionHelpDesc,
}
}
func (b *databaseBackend) connectionListHandler() framework.OperationFunc {
return func(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
entries, err := req.Storage.List(ctx, "config/")
if err != nil {
return nil, err
}
return logical.ListResponse(entries), nil
}
}
2017-05-04 00:37:34 +00:00
// connectionReadHandler reads out the connection configuration
func (b *databaseBackend) connectionReadHandler() framework.OperationFunc {
return func(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
name := data.Get("name").(string)
2017-04-13 00:35:02 +00:00
if name == "" {
return logical.ErrorResponse(respErrEmptyName), nil
2017-04-13 00:35:02 +00:00
}
2016-12-19 18:15:58 +00:00
entry, err := req.Storage.Get(ctx, fmt.Sprintf("config/%s", name))
if err != nil {
return nil, fmt.Errorf("failed to read connection configuration: %w", err)
}
if entry == nil {
return nil, nil
}
2016-12-19 18:15:58 +00:00
var config DatabaseConfig
if err := entry.DecodeJSON(&config); err != nil {
return nil, err
}
// Ensure that we only ever include a redacted valid URL in the response.
if connURLRaw, ok := config.ConnectionDetails["connection_url"]; ok {
if p, err := url.Parse(connURLRaw.(string)); err == nil {
config.ConnectionDetails["connection_url"] = p.Redacted()
}
}
if versions.IsBuiltinVersion(config.PluginVersion) {
// This gets treated as though it's empty when mounting, and will get
// overwritten to be empty when the config is next written. See #18051.
config.PluginVersion = ""
}
delete(config.ConnectionDetails, "password")
delete(config.ConnectionDetails, "private_key")
return &logical.Response{
Data: structs.New(config).Map(),
}, nil
2016-12-19 18:15:58 +00:00
}
}
2017-03-22 00:19:30 +00:00
// connectionDeleteHandler deletes the connection configuration
func (b *databaseBackend) connectionDeleteHandler() framework.OperationFunc {
return func(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
2017-03-22 00:19:30 +00:00
name := data.Get("name").(string)
if name == "" {
return logical.ErrorResponse(respErrEmptyName), nil
2017-03-22 00:19:30 +00:00
}
err := req.Storage.Delete(ctx, fmt.Sprintf("config/%s", name))
2017-03-22 00:19:30 +00:00
if err != nil {
return nil, fmt.Errorf("failed to delete connection configuration: %w", err)
2017-03-22 00:19:30 +00:00
}
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
if err := b.ClearConnection(name); err != nil {
return nil, err
2017-04-13 00:35:02 +00:00
}
2017-03-22 00:19:30 +00:00
return nil, nil
}
}
// connectionWriteHandler returns a handler function for creating and updating
// both builtin and plugin database types.
func (b *databaseBackend) connectionWriteHandler() framework.OperationFunc {
return func(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
verifyConnection := data.Get("verify_connection").(bool)
2016-12-19 18:15:58 +00:00
name := data.Get("name").(string)
if name == "" {
return logical.ErrorResponse(respErrEmptyName), nil
}
// Baseline
config := &DatabaseConfig{}
entry, err := req.Storage.Get(ctx, fmt.Sprintf("config/%s", name))
if err != nil {
return nil, fmt.Errorf("failed to read connection configuration: %w", err)
}
if entry != nil {
if err := entry.DecodeJSON(config); err != nil {
return nil, err
}
}
if pluginNameRaw, ok := data.GetOk("plugin_name"); ok {
config.PluginName = pluginNameRaw.(string)
} else if req.Operation == logical.CreateOperation {
config.PluginName = data.Get("plugin_name").(string)
}
if config.PluginName == "" {
return logical.ErrorResponse(respErrEmptyPluginName), nil
}
if pluginVersionRaw, ok := data.GetOk("plugin_version"); ok {
config.PluginVersion = pluginVersionRaw.(string)
}
var builtinShadowed bool
if unversionedPlugin, err := b.System().LookupPlugin(ctx, config.PluginName, consts.PluginTypeDatabase); err == nil && !unversionedPlugin.Builtin {
builtinShadowed = true
}
switch {
case config.PluginVersion != "":
semanticVersion, err := version.NewVersion(config.PluginVersion)
if err != nil {
return logical.ErrorResponse("version %q is not a valid semantic version: %s", config.PluginVersion, err), nil
}
// Canonicalize the version.
config.PluginVersion = "v" + semanticVersion.String()
if config.PluginVersion == versions.GetBuiltinVersion(consts.PluginTypeDatabase, config.PluginName) {
if builtinShadowed {
return logical.ErrorResponse("database plugin %q, version %s not found, as it is"+
" overridden by an unversioned plugin of the same name. Omit `plugin_version` to use the unversioned plugin", config.PluginName, config.PluginVersion), nil
}
config.PluginVersion = ""
}
case builtinShadowed:
// We'll select the unversioned plugin that's been registered.
case req.Operation == logical.CreateOperation:
// No version provided and no unversioned plugin of that name available.
// Pin to the current latest version if any versioned plugins are registered.
plugins, err := b.System().ListVersionedPlugins(ctx, consts.PluginTypeDatabase)
if err != nil {
return nil, err
}
var versionedCandidates []pluginutil.VersionedPlugin
for _, plugin := range plugins {
if !plugin.Builtin && plugin.Name == config.PluginName && plugin.Version != "" {
versionedCandidates = append(versionedCandidates, plugin)
}
}
if len(versionedCandidates) != 0 {
// Sort in reverse order.
sort.SliceStable(versionedCandidates, func(i, j int) bool {
return versionedCandidates[i].SemanticVersion.GreaterThan(versionedCandidates[j].SemanticVersion)
})
config.PluginVersion = "v" + versionedCandidates[0].SemanticVersion.String()
b.logger.Debug(fmt.Sprintf("pinning %q database plugin version %q from candidates %v", config.PluginName, config.PluginVersion, versionedCandidates))
}
}
if allowedRolesRaw, ok := data.GetOk("allowed_roles"); ok {
config.AllowedRoles = allowedRolesRaw.([]string)
} else if req.Operation == logical.CreateOperation {
config.AllowedRoles = data.Get("allowed_roles").([]string)
}
if rootRotationStatementsRaw, ok := data.GetOk("root_rotation_statements"); ok {
config.RootCredentialsRotateStatements = rootRotationStatementsRaw.([]string)
} else if req.Operation == logical.CreateOperation {
config.RootCredentialsRotateStatements = data.Get("root_rotation_statements").([]string)
}
if passwordPolicyRaw, ok := data.GetOk("password_policy"); ok {
config.PasswordPolicy = passwordPolicyRaw.(string)
}
// Remove these entries from the data before we store it keyed under
// ConnectionDetails.
delete(data.Raw, "name")
delete(data.Raw, "plugin_name")
delete(data.Raw, "plugin_version")
delete(data.Raw, "allowed_roles")
delete(data.Raw, "verify_connection")
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
delete(data.Raw, "root_rotation_statements")
delete(data.Raw, "password_policy")
2017-04-13 17:33:34 +00:00
id, err := uuid.GenerateUUID()
if err != nil {
return nil, err
}
// If this is an update, take any new values, overwrite what was there
// before, and pass that in as the "new" set of values to the plugin,
// then save what results
if req.Operation == logical.CreateOperation {
config.ConnectionDetails = data.Raw
} else {
if config.ConnectionDetails == nil {
config.ConnectionDetails = make(map[string]interface{})
}
for k, v := range data.Raw {
config.ConnectionDetails[k] = v
}
}
// Create a database plugin and initialize it.
dbw, err := newDatabaseWrapper(ctx, config.PluginName, config.PluginVersion, b.System(), b.logger)
if err != nil {
return logical.ErrorResponse("error creating database object: %s", err), nil
2016-12-19 18:15:58 +00:00
}
initReq := v5.InitializeRequest{
Config: config.ConnectionDetails,
VerifyConnection: verifyConnection,
}
initResp, err := dbw.Initialize(ctx, initReq)
if err != nil {
dbw.Close()
return logical.ErrorResponse("error creating database object: %s", err), nil
}
config.ConnectionDetails = initResp.Config
feature: multiplexing support for database plugins (#14033) * feat: DB plugin multiplexing (#13734) * WIP: start from main and get a plugin runner from core * move MultiplexedClient map to plugin catalog - call sys.NewPluginClient from PluginFactory - updates to getPluginClient - thread through isMetadataMode * use go-plugin ClientProtocol interface - call sys.NewPluginClient from dbplugin.NewPluginClient * move PluginSets to dbplugin package - export dbplugin HandshakeConfig - small refactor of PluginCatalog.getPluginClient * add removeMultiplexedClient; clean up on Close() - call client.Kill from plugin catalog - set rpcClient when muxed client exists * add ID to dbplugin.DatabasePluginClient struct * only create one plugin process per plugin type * update NewPluginClient to return connection ID to sdk - wrap grpc.ClientConn so we can inject the ID into context - get ID from context on grpc server * add v6 multiplexing protocol version * WIP: backwards compat for db plugins * Ensure locking on plugin catalog access - Create public GetPluginClient method for plugin catalog - rename postgres db plugin * use the New constructor for db plugins * grpc server: use write lock for Close and rlock for CRUD * cleanup MultiplexedClients on Close * remove TODO * fix multiplexing regression with grpc server connection * cleanup grpc server instances on close * embed ClientProtocol in Multiplexer interface * use PluginClientConfig arg to make NewPluginClient plugin type agnostic * create a new plugin process for non-muxed plugins * feat: plugin multiplexing: handle plugin client cleanup (#13896) * use closure for plugin client cleanup * log and return errors; add comments * move rpcClient wrapping to core for ID injection * refactor core plugin client and sdk * remove unused ID method * refactor and only wrap clientConn on multiplexed plugins * rename structs and do not export types * Slight refactor of system view interface * Revert "Slight refactor of system view interface" This reverts commit 73d420e5cd2f0415e000c5a9284ea72a58016dd6. * Revert "Revert "Slight refactor of system view interface"" This reverts commit f75527008a1db06d04a23e04c3059674be8adb5f. * only provide pluginRunner arg to the internal newPluginClient method * embed ClientProtocol in pluginClient and name logger * Add back MLock support * remove enableMlock arg from setupPluginCatalog * rename plugin util interface to PluginClient Co-authored-by: Brian Kassouf <bkassouf@hashicorp.com> * feature: multiplexing: fix unit tests (#14007) * fix grpc_server tests and add coverage * update run_config tests * add happy path test case for grpc_server ID from context * update test helpers * feat: multiplexing: handle v5 plugin compiled with new sdk * add mux supported flag and increase test coverage * set multiplexingSupport field in plugin server * remove multiplexingSupport field in sdk * revert postgres to non-multiplexed * add comments on grpc server fields * use pointer receiver on grpc server methods * add changelog * use pointer for grpcserver instance * Use a gRPC server to determine if a plugin should be multiplexed * Apply suggestions from code review Co-authored-by: Brian Kassouf <briankassouf@users.noreply.github.com> * add lock to removePluginClient * add multiplexingSupport field to externalPlugin struct * do not send nil to grpc MultiplexingSupport * check err before logging * handle locking scenario for cleanupFunc * allow ServeConfigMultiplex to dispense v5 plugin * reposition structs, add err check and comments * add comment on locking for cleanupExternalPlugin Co-authored-by: Brian Kassouf <bkassouf@hashicorp.com> Co-authored-by: Brian Kassouf <briankassouf@users.noreply.github.com>
2022-02-17 14:50:33 +00:00
b.Logger().Debug("created database object", "name", name, "plugin_name", config.PluginName)
// Close and remove the old connection
oldConn := b.connPut(name, &dbPluginInstance{
database: dbw,
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
name: name,
id: id,
})
if oldConn != nil {
oldConn.Close()
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
}
// 1.12.0 and 1.12.1 stored builtin plugins in storage, but 1.12.2 reverted
// that, so clean up any pre-existing stored builtin versions on write.
if versions.IsBuiltinVersion(config.PluginVersion) {
config.PluginVersion = ""
}
err = storeConfig(ctx, req.Storage, name, config)
2017-03-08 22:46:53 +00:00
if err != nil {
return nil, err
2017-03-08 22:46:53 +00:00
}
2016-12-19 18:15:58 +00:00
resp := &logical.Response{}
// This is a simple test to check for passwords in the connection_url parameter. If one exists,
// warn the user to use templated url string
if connURLRaw, ok := config.ConnectionDetails["connection_url"]; ok {
if connURL, err := url.Parse(connURLRaw.(string)); err == nil {
if _, ok := connURL.User.Password(); ok {
resp.AddWarning("Password found in connection_url, use a templated url to enable root rotation and prevent read access to password information.")
}
}
}
2016-12-19 18:15:58 +00:00
// If using a legacy DB plugin and set the `password_policy` field, send a warning to the user indicating
// the `password_policy` will not be used
if dbw.isV4() && config.PasswordPolicy != "" {
resp.AddWarning(fmt.Sprintf("%s does not support password policies - upgrade to the latest version of "+
"Vault (or the sdk if using a custom plugin) to gain password policy support", config.PluginName))
}
feature: multiplexing support for database plugins (#14033) * feat: DB plugin multiplexing (#13734) * WIP: start from main and get a plugin runner from core * move MultiplexedClient map to plugin catalog - call sys.NewPluginClient from PluginFactory - updates to getPluginClient - thread through isMetadataMode * use go-plugin ClientProtocol interface - call sys.NewPluginClient from dbplugin.NewPluginClient * move PluginSets to dbplugin package - export dbplugin HandshakeConfig - small refactor of PluginCatalog.getPluginClient * add removeMultiplexedClient; clean up on Close() - call client.Kill from plugin catalog - set rpcClient when muxed client exists * add ID to dbplugin.DatabasePluginClient struct * only create one plugin process per plugin type * update NewPluginClient to return connection ID to sdk - wrap grpc.ClientConn so we can inject the ID into context - get ID from context on grpc server * add v6 multiplexing protocol version * WIP: backwards compat for db plugins * Ensure locking on plugin catalog access - Create public GetPluginClient method for plugin catalog - rename postgres db plugin * use the New constructor for db plugins * grpc server: use write lock for Close and rlock for CRUD * cleanup MultiplexedClients on Close * remove TODO * fix multiplexing regression with grpc server connection * cleanup grpc server instances on close * embed ClientProtocol in Multiplexer interface * use PluginClientConfig arg to make NewPluginClient plugin type agnostic * create a new plugin process for non-muxed plugins * feat: plugin multiplexing: handle plugin client cleanup (#13896) * use closure for plugin client cleanup * log and return errors; add comments * move rpcClient wrapping to core for ID injection * refactor core plugin client and sdk * remove unused ID method * refactor and only wrap clientConn on multiplexed plugins * rename structs and do not export types * Slight refactor of system view interface * Revert "Slight refactor of system view interface" This reverts commit 73d420e5cd2f0415e000c5a9284ea72a58016dd6. * Revert "Revert "Slight refactor of system view interface"" This reverts commit f75527008a1db06d04a23e04c3059674be8adb5f. * only provide pluginRunner arg to the internal newPluginClient method * embed ClientProtocol in pluginClient and name logger * Add back MLock support * remove enableMlock arg from setupPluginCatalog * rename plugin util interface to PluginClient Co-authored-by: Brian Kassouf <bkassouf@hashicorp.com> * feature: multiplexing: fix unit tests (#14007) * fix grpc_server tests and add coverage * update run_config tests * add happy path test case for grpc_server ID from context * update test helpers * feat: multiplexing: handle v5 plugin compiled with new sdk * add mux supported flag and increase test coverage * set multiplexingSupport field in plugin server * remove multiplexingSupport field in sdk * revert postgres to non-multiplexed * add comments on grpc server fields * use pointer receiver on grpc server methods * add changelog * use pointer for grpcserver instance * Use a gRPC server to determine if a plugin should be multiplexed * Apply suggestions from code review Co-authored-by: Brian Kassouf <briankassouf@users.noreply.github.com> * add lock to removePluginClient * add multiplexingSupport field to externalPlugin struct * do not send nil to grpc MultiplexingSupport * check err before logging * handle locking scenario for cleanupFunc * allow ServeConfigMultiplex to dispense v5 plugin * reposition structs, add err check and comments * add comment on locking for cleanupExternalPlugin Co-authored-by: Brian Kassouf <bkassouf@hashicorp.com> Co-authored-by: Brian Kassouf <briankassouf@users.noreply.github.com>
2022-02-17 14:50:33 +00:00
if len(resp.Warnings) == 0 {
return nil, nil
}
return resp, nil
}
2016-12-19 18:15:58 +00:00
}
func storeConfig(ctx context.Context, storage logical.Storage, name string, config *DatabaseConfig) error {
entry, err := logical.StorageEntryJSON(fmt.Sprintf("config/%s", name), config)
if err != nil {
return fmt.Errorf("unable to marshal object to JSON: %w", err)
}
err = storage.Put(ctx, entry)
if err != nil {
return fmt.Errorf("failed to save object: %w", err)
}
return nil
}
2016-12-19 18:15:58 +00:00
const pathConfigConnectionHelpSyn = `
2017-04-11 18:50:34 +00:00
Configure connection details to a database plugin.
2016-12-19 18:15:58 +00:00
`
const pathConfigConnectionHelpDesc = `
2017-04-11 18:50:34 +00:00
This path configures the connection details used to connect to a particular
database. This path runs the provided plugin name and passes the configured
connection details to the plugin. See the documentation for the plugin specified
for a full list of accepted connection details.
2016-12-19 18:15:58 +00:00
2017-05-04 00:37:34 +00:00
In addition to the database specific connection details, this endpoint also
2017-04-11 18:50:34 +00:00
accepts:
* "plugin_name" (required) - The name of a builtin or previously registered
plugin known to vault. This endpoint will create an instance of that
plugin type.
2017-04-13 00:35:02 +00:00
* "verify_connection" (default: true) - A boolean value denoting if the plugin should verify
2017-04-11 18:50:34 +00:00
it is able to connect to the database using the provided connection
details.
`
const pathResetConnectionHelpSyn = `
Resets a database plugin.
`
2016-12-19 18:15:58 +00:00
2017-04-11 18:50:34 +00:00
const pathResetConnectionHelpDesc = `
This path resets the database connection by closing the existing database plugin
instance and running a new one.
2016-12-19 18:15:58 +00:00
`