Skip to content

Commit

Permalink
Merge pull request #2200 from hashicorp/database-refactor
Browse files Browse the repository at this point in the history
Combined Database Backend with Plugins
  • Loading branch information
briankassouf authored May 4, 2017
2 parents 0c683bd + c48b7fa commit f4ae1ff
Show file tree
Hide file tree
Showing 85 changed files with 10,961 additions and 40 deletions.
3 changes: 2 additions & 1 deletion audit/hashstructure.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"strings"

"github.com/hashicorp/vault/helper/salt"
"github.com/hashicorp/vault/helper/wrapping"
"github.com/hashicorp/vault/logical"
"github.com/mitchellh/copystructure"
"github.com/mitchellh/reflectwalk"
Expand Down Expand Up @@ -84,7 +85,7 @@ func Hash(salter *salt.Salt, raw interface{}) error {

s.Data = data.(map[string]interface{})

case *logical.ResponseWrapInfo:
case *wrapping.ResponseWrapInfo:
if s == nil {
return nil
}
Expand Down
7 changes: 4 additions & 3 deletions audit/hashstructure_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

"github.com/hashicorp/vault/helper/certutil"
"github.com/hashicorp/vault/helper/salt"
"github.com/hashicorp/vault/helper/wrapping"
"github.com/hashicorp/vault/logical"
"github.com/mitchellh/copystructure"
)
Expand Down Expand Up @@ -69,7 +70,7 @@ func TestCopy_response(t *testing.T) {
Data: map[string]interface{}{
"foo": "bar",
},
WrapInfo: &logical.ResponseWrapInfo{
WrapInfo: &wrapping.ResponseWrapInfo{
TTL: 60,
Token: "foo",
CreationTime: time.Now(),
Expand Down Expand Up @@ -140,7 +141,7 @@ func TestHash(t *testing.T) {
Data: map[string]interface{}{
"foo": "bar",
},
WrapInfo: &logical.ResponseWrapInfo{
WrapInfo: &wrapping.ResponseWrapInfo{
TTL: 60,
Token: "bar",
CreationTime: now,
Expand All @@ -151,7 +152,7 @@ func TestHash(t *testing.T) {
Data: map[string]interface{}{
"foo": "hmac-sha256:f9320baf0249169e73850cd6156ded0106e2bb6ad8cab01b7bbbebe6d1065317",
},
WrapInfo: &logical.ResponseWrapInfo{
WrapInfo: &wrapping.ResponseWrapInfo{
TTL: 60,
Token: "hmac-sha256:f9320baf0249169e73850cd6156ded0106e2bb6ad8cab01b7bbbebe6d1065317",
CreationTime: now,
Expand Down
177 changes: 177 additions & 0 deletions builtin/logical/database/backend.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
package database

import (
"fmt"
"net/rpc"
"strings"
"sync"

log "github.com/mgutz/logxi/v1"

"github.com/hashicorp/vault/builtin/logical/database/dbplugin"
"github.com/hashicorp/vault/logical"
"github.com/hashicorp/vault/logical/framework"
)

const databaseConfigPath = "database/config/"

func Factory(conf *logical.BackendConfig) (logical.Backend, error) {
return Backend(conf).Setup(conf)
}

func Backend(conf *logical.BackendConfig) *databaseBackend {
var b databaseBackend
b.Backend = &framework.Backend{
Help: strings.TrimSpace(backendHelp),

Paths: []*framework.Path{
pathConfigurePluginConnection(&b),
pathListRoles(&b),
pathRoles(&b),
pathCredsCreate(&b),
pathResetConnection(&b),
},

Secrets: []*framework.Secret{
secretCreds(&b),
},

Clean: b.closeAllDBs,

Invalidate: b.invalidate,
}

b.logger = conf.Logger
b.connections = make(map[string]dbplugin.Database)
return &b
}

type databaseBackend struct {
connections map[string]dbplugin.Database
logger log.Logger

*framework.Backend
sync.RWMutex
}

// closeAllDBs closes all connections from all database types
func (b *databaseBackend) closeAllDBs() {
b.Lock()
defer b.Unlock()

for _, db := range b.connections {
db.Close()
}

b.connections = make(map[string]dbplugin.Database)
}

// This function is used to retrieve a database object either from the cached
// connection map. The caller of this function needs to hold the backend's read
// lock.
func (b *databaseBackend) getDBObj(name string) (dbplugin.Database, bool) {
db, ok := b.connections[name]
return db, ok
}

// This function creates a new db object from the stored configuration and
// caches it in the connections map. The caller of this function needs to hold
// the backend's write lock
func (b *databaseBackend) createDBObj(s logical.Storage, name string) (dbplugin.Database, error) {
db, ok := b.connections[name]
if ok {
return db, nil
}

config, err := b.DatabaseConfig(s, name)
if err != nil {
return nil, err
}

db, err = dbplugin.PluginFactory(config.PluginName, b.System(), b.logger)
if err != nil {
return nil, err
}

err = db.Initialize(config.ConnectionDetails, true)
if err != nil {
return nil, err
}

b.connections[name] = db

return db, nil
}

func (b *databaseBackend) DatabaseConfig(s logical.Storage, name string) (*DatabaseConfig, error) {
entry, err := s.Get(fmt.Sprintf("config/%s", name))
if err != nil {
return nil, fmt.Errorf("failed to read connection configuration: %s", err)
}
if entry == nil {
return nil, fmt.Errorf("failed to find entry for connection with name: %s", name)
}

var config DatabaseConfig
if err := entry.DecodeJSON(&config); err != nil {
return nil, err
}

return &config, nil
}

func (b *databaseBackend) Role(s logical.Storage, roleName string) (*roleEntry, error) {
entry, err := s.Get("role/" + roleName)
if err != nil {
return nil, err
}
if entry == nil {
return nil, nil
}

var result roleEntry
if err := entry.DecodeJSON(&result); err != nil {
return nil, err
}

return &result, nil
}

func (b *databaseBackend) invalidate(key string) {
b.Lock()
defer b.Unlock()

switch {
case strings.HasPrefix(key, databaseConfigPath):
name := strings.TrimPrefix(key, databaseConfigPath)
b.clearConnection(name)
}
}

// clearConnection closes the database connection and
// removes it from the b.connections map.
func (b *databaseBackend) clearConnection(name string) {
db, ok := b.connections[name]
if ok {
db.Close()
delete(b.connections, name)
}
}

func (b *databaseBackend) closeIfShutdown(name string, err error) {
// Plugin has shutdown, close it so next call can reconnect.
if err == rpc.ErrShutdown {
b.Lock()
b.clearConnection(name)
b.Unlock()
}
}

const backendHelp = `
The database backend supports using many different databases
as secret backends, including but not limited to:
cassandra, mssql, mysql, postgres
After mounting this backend, configure it using the endpoints within
the "database/config/" path.
`
Loading

0 comments on commit f4ae1ff

Please sign in to comment.