2016-12-19 18:15:58 +00:00
package database
import (
2017-12-14 22:03:11 +00:00
"context"
2017-04-13 00:35:02 +00:00
"errors"
2016-12-19 18:15:58 +00:00
"fmt"
"github.com/fatih/structs"
2017-04-06 19:20:10 +00:00
"github.com/hashicorp/vault/builtin/logical/database/dbplugin"
2016-12-19 18:15:58 +00:00
"github.com/hashicorp/vault/logical"
"github.com/hashicorp/vault/logical/framework"
)
2017-04-13 00:35:02 +00:00
var (
2017-05-01 22:30:56 +00:00
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" `
// 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" `
}
2017-04-11 18:50:34 +00:00
// pathResetConnection configures a path to reset a plugin.
2017-02-16 00:51:59 +00:00
func pathResetConnection ( b * databaseBackend ) * framework . Path {
return & framework . Path {
Pattern : fmt . Sprintf ( "reset/%s" , framework . GenericNameRegex ( "name" ) ) ,
Fields : map [ string ] * framework . FieldSchema {
"name" : & framework . FieldSchema {
Type : framework . TypeString ,
2017-04-13 00:35:02 +00:00
Description : "Name of this database connection" ,
2017-02-16 00:51:59 +00:00
} ,
} ,
Callbacks : map [ logical . Operation ] framework . OperationFunc {
2017-04-11 18:50:34 +00:00
logical . UpdateOperation : b . pathConnectionReset ( ) ,
2017-02-16 00:51:59 +00:00
} ,
2017-04-11 18:50:34 +00:00
HelpSynopsis : pathResetConnectionHelpSyn ,
HelpDescription : pathResetConnectionHelpDesc ,
2017-02-16 00:51:59 +00:00
}
}
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 {
2018-01-08 18:31:38 +00:00
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 == "" {
2017-05-01 22:30:56 +00:00
return logical . ErrorResponse ( respErrEmptyName ) , nil
2017-04-11 18:50:34 +00:00
}
// Grab the mutex lock
b . Lock ( )
defer b . Unlock ( )
2017-02-16 00:51:59 +00:00
2017-04-13 00:35:02 +00:00
// Close plugin and delete the entry in the connections cache.
2017-04-11 18:50:34 +00:00
b . clearConnection ( name )
2017-02-16 00:51:59 +00:00
2017-04-13 00:35:02 +00:00
// Execute plugin again, we don't need the object so throw away.
2018-01-08 18:31:38 +00:00
_ , err := b . createDBObj ( ctx , req . Storage , name )
2017-04-11 18:50:34 +00:00
if err != nil {
return nil , err
}
2017-02-16 00:51:59 +00:00
2017-04-11 18:50:34 +00:00
return nil , nil
2017-03-13 21:39:55 +00:00
}
2017-02-16 00:51:59 +00:00
}
2017-03-22 00:19:30 +00:00
// pathConfigurePluginConnection returns a configured framework.Path setup to
// operate on plugins.
2017-03-10 05:31:29 +00:00
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" ) ) ,
2016-12-19 18:15:58 +00:00
Fields : map [ string ] * framework . FieldSchema {
"name" : & framework . FieldSchema {
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
} ,
2017-04-04 00:52:29 +00:00
"plugin_name" : & framework . FieldSchema {
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
"verify_connection" : & framework . FieldSchema {
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" : & framework . FieldSchema {
2017-04-25 17:26:23 +00:00
Type : framework . TypeCommaStringSlice ,
Description : ` Comma separated string or array of the role names
2017-04-25 18:48:24 +00:00
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
} ,
2016-12-19 18:15:58 +00:00
} ,
Callbacks : map [ logical . Operation ] framework . OperationFunc {
2017-04-11 01:38:34 +00:00
logical . UpdateOperation : b . connectionWriteHandler ( ) ,
logical . ReadOperation : b . connectionReadHandler ( ) ,
logical . DeleteOperation : b . connectionDeleteHandler ( ) ,
2016-12-19 18:15:58 +00:00
} ,
HelpSynopsis : pathConfigConnectionHelpSyn ,
HelpDescription : pathConfigConnectionHelpDesc ,
}
}
2017-06-07 14:03:17 +00:00
func pathListPluginConnection ( b * databaseBackend ) * framework . Path {
return & framework . Path {
Pattern : fmt . Sprintf ( "config/?$" ) ,
Callbacks : map [ logical . Operation ] framework . OperationFunc {
logical . ListOperation : b . connectionListHandler ( ) ,
} ,
HelpSynopsis : pathConfigConnectionHelpSyn ,
HelpDescription : pathConfigConnectionHelpDesc ,
}
}
func ( b * databaseBackend ) connectionListHandler ( ) framework . OperationFunc {
2018-01-08 18:31:38 +00:00
return func ( ctx context . Context , req * logical . Request , data * framework . FieldData ) ( * logical . Response , error ) {
2017-06-07 14:03:17 +00:00
entries , err := req . Storage . List ( "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
2017-03-10 05:31:29 +00:00
func ( b * databaseBackend ) connectionReadHandler ( ) framework . OperationFunc {
2018-01-08 18:31:38 +00:00
return func ( ctx context . Context , req * logical . Request , data * framework . FieldData ) ( * logical . Response , error ) {
2017-03-10 05:31:29 +00:00
name := data . Get ( "name" ) . ( string )
2017-04-13 00:35:02 +00:00
if name == "" {
2017-05-01 22:30:56 +00:00
return logical . ErrorResponse ( respErrEmptyName ) , nil
2017-04-13 00:35:02 +00:00
}
2016-12-19 18:15:58 +00:00
2017-04-13 00:35:02 +00:00
entry , err := req . Storage . Get ( fmt . Sprintf ( "config/%s" , name ) )
2017-03-10 05:31:29 +00:00
if err != nil {
2017-04-13 00:35:02 +00:00
return nil , errors . New ( "failed to read connection configuration" )
2017-03-10 05:31:29 +00:00
}
if entry == nil {
return nil , nil
}
2016-12-19 18:15:58 +00:00
2017-04-05 23:20:31 +00:00
var config DatabaseConfig
2017-03-10 05:31:29 +00:00
if err := entry . DecodeJSON ( & config ) ; err != nil {
return nil , err
}
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 {
2018-01-08 18:31:38 +00:00
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 == "" {
2017-05-01 22:30:56 +00:00
return logical . ErrorResponse ( respErrEmptyName ) , nil
2017-03-22 00:19:30 +00:00
}
2017-04-13 00:35:02 +00:00
err := req . Storage . Delete ( fmt . Sprintf ( "config/%s" , name ) )
2017-03-22 00:19:30 +00:00
if err != nil {
2017-04-13 00:35:02 +00:00
return nil , errors . New ( "failed to delete connection configuration" )
2017-03-22 00:19:30 +00:00
}
2017-03-22 16:54:19 +00:00
b . Lock ( )
defer b . Unlock ( )
2017-03-22 00:19:30 +00:00
if _ , ok := b . connections [ name ] ; ok {
err = b . connections [ name ] . Close ( )
if err != nil {
return nil , err
}
2017-04-13 00:35:02 +00:00
delete ( b . connections , name )
}
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.
2017-04-05 23:20:31 +00:00
func ( b * databaseBackend ) connectionWriteHandler ( ) framework . OperationFunc {
2018-01-08 18:31:38 +00:00
return func ( ctx context . Context , req * logical . Request , data * framework . FieldData ) ( * logical . Response , error ) {
2017-04-13 00:35:02 +00:00
pluginName := data . Get ( "plugin_name" ) . ( string )
if pluginName == "" {
2017-05-01 22:30:56 +00:00
return logical . ErrorResponse ( respErrEmptyPluginName ) , nil
2017-03-10 05:31:29 +00:00
}
2016-12-19 18:15:58 +00:00
2017-03-10 05:31:29 +00:00
name := data . Get ( "name" ) . ( string )
2017-03-13 21:39:55 +00:00
if name == "" {
2017-05-01 22:30:56 +00:00
return logical . ErrorResponse ( respErrEmptyName ) , nil
2017-03-13 21:39:55 +00:00
}
verifyConnection := data . Get ( "verify_connection" ) . ( bool )
2016-12-19 18:15:58 +00:00
2017-04-25 18:11:10 +00:00
allowedRoles := data . Get ( "allowed_roles" ) . ( [ ] 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 , "allowed_roles" )
delete ( data . Raw , "verify_connection" )
2017-04-13 17:33:34 +00:00
2017-04-13 00:35:02 +00:00
config := & DatabaseConfig {
ConnectionDetails : data . Raw ,
PluginName : pluginName ,
2017-04-13 17:33:34 +00:00
AllowedRoles : allowedRoles ,
2017-04-13 00:35:02 +00:00
}
2016-12-19 18:15:58 +00:00
2017-04-06 19:20:10 +00:00
db , err := dbplugin . PluginFactory ( config . PluginName , b . System ( ) , b . logger )
2017-03-21 23:05:59 +00:00
if err != nil {
2017-04-24 21:03:48 +00:00
return logical . ErrorResponse ( fmt . Sprintf ( "error creating database object: %s" , err ) ) , nil
2017-03-21 23:05:59 +00:00
}
2016-12-19 18:15:58 +00:00
2018-01-08 18:31:38 +00:00
err = db . Initialize ( ctx , config . ConnectionDetails , verifyConnection )
2017-03-21 23:05:59 +00:00
if err != nil {
2017-04-10 22:36:59 +00:00
db . Close ( )
2017-04-24 21:03:48 +00:00
return logical . ErrorResponse ( fmt . Sprintf ( "error creating database object: %s" , err ) ) , nil
2016-12-19 18:15:58 +00:00
}
2017-02-16 00:51:59 +00:00
2017-04-13 00:35:02 +00:00
// Grab the mutex lock
b . Lock ( )
defer b . Unlock ( )
2017-04-26 22:55:34 +00:00
// Close and remove the old connection
b . clearConnection ( name )
2016-12-19 18:15:58 +00:00
2017-04-04 01:30:38 +00:00
// Save the new connection
b . connections [ name ] = db
2017-03-10 05:31:29 +00:00
// Store it
2017-04-13 00:35:02 +00:00
entry , err := logical . StorageEntryJSON ( fmt . Sprintf ( "config/%s" , name ) , config )
2017-03-08 22:46:53 +00:00
if err != nil {
2017-03-10 05:31:29 +00:00
return nil , err
2017-03-08 22:46:53 +00:00
}
2017-03-10 05:31:29 +00:00
if err := req . Storage . Put ( entry ) ; err != nil {
return nil , err
2016-12-19 18:15:58 +00:00
}
2017-03-10 05:31:29 +00:00
resp := & logical . Response { }
2017-04-13 00:35:02 +00:00
resp . AddWarning ( "Read access to this endpoint should be controlled via ACLs as it will return the connection details as is, including passwords, if any." )
2016-12-19 18:15:58 +00:00
2017-03-10 05:31:29 +00:00
return resp , 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
`