2016-12-19 18:15:58 +00:00
package database
import (
2017-02-16 00:51:59 +00:00
"errors"
2016-12-19 18:15:58 +00:00
"fmt"
2017-01-04 19:28:30 +00:00
"time"
2016-12-19 18:15:58 +00:00
"github.com/fatih/structs"
"github.com/hashicorp/vault/builtin/logical/database/dbs"
"github.com/hashicorp/vault/logical"
"github.com/hashicorp/vault/logical/framework"
)
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 ,
Description : "Name of this DB type" ,
} ,
} ,
Callbacks : map [ logical . Operation ] framework . OperationFunc {
logical . UpdateOperation : b . pathConnectionReset ,
} ,
HelpSynopsis : pathConfigConnectionHelpSyn ,
HelpDescription : pathConfigConnectionHelpDesc ,
}
}
func ( b * databaseBackend ) pathConnectionReset ( req * logical . Request , data * framework . FieldData ) ( * logical . Response , error ) {
name := data . Get ( "name" ) . ( string )
if name == "" {
return nil , errors . New ( "No database name set" )
}
// Grab the mutex lock
b . Lock ( )
defer b . Unlock ( )
entry , err := req . Storage . Get ( fmt . Sprintf ( "dbs/%s" , name ) )
if err != nil {
return nil , fmt . Errorf ( "failed to read connection configuration" )
}
if entry == nil {
return nil , nil
}
var config dbs . DatabaseConfig
if err := entry . DecodeJSON ( & config ) ; err != nil {
return nil , err
}
db , ok := b . connections [ name ]
if ! ok {
return logical . ErrorResponse ( "Can not change type of existing connection." ) , nil
}
db . Close ( )
2017-03-10 05:31:29 +00:00
factory := config . GetFactory ( )
db , err = factory ( & config )
2017-02-16 00:51:59 +00:00
if err != nil {
return logical . ErrorResponse ( fmt . Sprintf ( "Error creating database object: %s" , err ) ) , nil
}
b . connections [ name ] = db
return nil , nil
}
2017-03-10 05:31:29 +00:00
func pathConfigureConnection ( b * databaseBackend ) * framework . Path {
return buildConfigConnectionPath ( "dbs/%s" , b . connectionWriteHandler ( dbs . BuiltinFactory ) , b . connectionReadHandler ( ) )
}
func pathConfigurePluginConnection ( b * databaseBackend ) * framework . Path {
return buildConfigConnectionPath ( "dbs/plugin/%s" , b . connectionWriteHandler ( dbs . PluginFactory ) , b . connectionReadHandler ( ) )
}
func buildConfigConnectionPath ( path string , updateOp , readOp framework . OperationFunc ) * framework . Path {
2016-12-19 18:15:58 +00:00
return & framework . Path {
2017-03-10 05:31:29 +00:00
Pattern : fmt . Sprintf ( path , framework . GenericNameRegex ( "name" ) ) ,
2016-12-19 18:15:58 +00:00
Fields : map [ string ] * framework . FieldSchema {
"name" : & framework . FieldSchema {
Type : framework . TypeString ,
Description : "Name of this DB type" ,
} ,
"connection_type" : & framework . FieldSchema {
Type : framework . TypeString ,
Description : "DB type (e.g. postgres)" ,
} ,
"verify_connection" : & framework . FieldSchema {
Type : framework . TypeBool ,
Default : true ,
Description : ` If set, connection_url is verified by actually connecting to the database ` ,
} ,
"max_open_connections" : & framework . FieldSchema {
Type : framework . TypeInt ,
Description : ` Maximum number of open connections to the database ;
a zero uses the default value of two and a
negative value means unlimited ` ,
} ,
"max_idle_connections" : & framework . FieldSchema {
Type : framework . TypeInt ,
Description : ` Maximum number of idle connections to the database ;
a zero uses the value of max_open_connections
and a negative value disables idle connections .
If larger than max_open_connections it will be
reduced to the same size . ` ,
} ,
2017-01-04 19:28:30 +00:00
"max_connection_lifetime" : & framework . FieldSchema {
2017-02-08 01:32:08 +00:00
Type : framework . TypeString ,
Default : "0s" ,
2017-01-04 19:28:30 +00:00
Description : ` Maximum amount of time a connection may be reused ;
a zero or negative value reuses connections forever . ` ,
} ,
2017-03-10 01:43:37 +00:00
"plugin_command" : & framework . FieldSchema {
Type : framework . TypeString ,
Description : ` Maximum amount of time a connection may be reused ;
a zero or negative value reuses connections forever . ` ,
} ,
2016-12-19 18:15:58 +00:00
} ,
Callbacks : map [ logical . Operation ] framework . OperationFunc {
2017-03-10 05:31:29 +00:00
logical . UpdateOperation : updateOp ,
logical . ReadOperation : readOp ,
2016-12-19 18:15:58 +00:00
} ,
HelpSynopsis : pathConfigConnectionHelpSyn ,
HelpDescription : pathConfigConnectionHelpDesc ,
}
}
// pathConnectionRead reads out the connection configuration
2017-03-10 05:31:29 +00:00
func ( b * databaseBackend ) connectionReadHandler ( ) framework . OperationFunc {
return func ( req * logical . Request , data * framework . FieldData ) ( * logical . Response , error ) {
name := data . Get ( "name" ) . ( string )
2016-12-19 18:15:58 +00:00
2017-03-10 05:31:29 +00:00
entry , err := req . Storage . Get ( fmt . Sprintf ( "dbs/%s" , name ) )
if err != nil {
return nil , fmt . Errorf ( "failed to read connection configuration" )
}
if entry == nil {
return nil , nil
}
2016-12-19 18:15:58 +00:00
2017-03-10 05:31:29 +00:00
var config dbs . DatabaseConfig
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-10 05:31:29 +00:00
func ( b * databaseBackend ) connectionWriteHandler ( factory dbs . Factory ) framework . OperationFunc {
return func ( req * logical . Request , data * framework . FieldData ) ( * logical . Response , error ) {
connType := data . Get ( "connection_type" ) . ( string )
if connType == "" {
return logical . ErrorResponse ( "connection_type not set" ) , nil
}
2016-12-19 18:15:58 +00:00
2017-03-10 05:31:29 +00:00
maxOpenConns := data . Get ( "max_open_connections" ) . ( int )
if maxOpenConns == 0 {
maxOpenConns = 2
}
2016-12-19 18:15:58 +00:00
2017-03-10 05:31:29 +00:00
maxIdleConns := data . Get ( "max_idle_connections" ) . ( int )
if maxIdleConns == 0 {
maxIdleConns = maxOpenConns
}
if maxIdleConns > maxOpenConns {
maxIdleConns = maxOpenConns
}
2016-12-19 18:15:58 +00:00
2017-03-10 05:31:29 +00:00
maxConnLifetimeRaw := data . Get ( "max_connection_lifetime" ) . ( string )
maxConnLifetime , err := time . ParseDuration ( maxConnLifetimeRaw )
if err != nil {
return logical . ErrorResponse ( fmt . Sprintf (
"Invalid max_connection_lifetime: %s" , err ) ) , nil
}
2017-01-04 19:28:30 +00:00
2017-03-10 05:31:29 +00:00
config := & dbs . DatabaseConfig {
DatabaseType : connType ,
ConnectionDetails : data . Raw ,
MaxOpenConnections : maxOpenConns ,
MaxIdleConnections : maxIdleConns ,
MaxConnectionLifetime : maxConnLifetime ,
PluginCommand : data . Get ( "plugin_command" ) . ( string ) ,
}
2016-12-19 18:15:58 +00:00
2017-03-10 05:31:29 +00:00
name := data . Get ( "name" ) . ( string )
2016-12-19 18:15:58 +00:00
2017-03-10 05:31:29 +00:00
// Grab the mutex lock
b . Lock ( )
defer b . Unlock ( )
2016-12-19 18:15:58 +00:00
2017-03-10 05:31:29 +00:00
var db dbs . DatabaseType
if _ , ok := b . connections [ name ] ; ok {
2016-12-19 18:15:58 +00:00
2017-03-10 05:31:29 +00:00
// Don't allow the connection type to change
if b . connections [ name ] . Type ( ) != connType {
return logical . ErrorResponse ( "Can not change type of existing connection." ) , nil
}
} else {
db , err = factory ( config )
if err != nil {
return logical . ErrorResponse ( fmt . Sprintf ( "Error creating database object: %s" , err ) ) , nil
}
b . connections [ name ] = db
2016-12-19 18:15:58 +00:00
}
2017-02-16 00:51:59 +00:00
2017-03-10 05:31:29 +00:00
/ * TODO :
// Don't check the connection_url if verification is disabled
verifyConnection := data . Get ( "verify_connection" ) . ( bool )
if verifyConnection {
// Verify the string
db , err := sql . Open ( "postgres" , connURL )
if err != nil {
return logical . ErrorResponse ( fmt . Sprintf (
"Error validating connection info: %s" , err ) ) , nil
}
defer db . Close ( )
if err := db . Ping ( ) ; err != nil {
return logical . ErrorResponse ( fmt . Sprintf (
"Error validating connection info: %s" , err ) ) , nil
}
}
* /
2016-12-19 18:15:58 +00:00
2017-03-10 05:31:29 +00:00
// Store it
entry , err := logical . StorageEntryJSON ( fmt . Sprintf ( "dbs/%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
// Reset the DB connection
resp := & logical . Response { }
resp . AddWarning ( "Read access to this endpoint should be controlled via ACLs as it will return the connection string or URL as it 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 = `
Configure the connection string to talk to PostgreSQL .
`
const pathConfigConnectionHelpDesc = `
This path configures the connection string used to connect to PostgreSQL .
The value of the string can be a URL , or a PG style string in the
format of "user=foo host=bar" etc .
The URL looks like :
"postgresql://user:pass@host:port/dbname"
When configuring the connection string , the backend will verify its validity .
`