Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Combined Database Backend: Static Accounts #6834

Merged
merged 106 commits into from
Jun 19, 2019
Merged
Show file tree
Hide file tree
Changes from 103 commits
Commits
Show all changes
106 commits
Select commit Hold shift + click to select a range
561ce7b
Add priority queue to sdk
catsby Apr 30, 2019
4f6e85c
fix issue of storing pointers and now copy
catsby Apr 30, 2019
d61d162
update to use copy structure
catsby Apr 30, 2019
d7e72bc
Remove file, put Item struct def. into other file
catsby May 1, 2019
60f24bc
add link
catsby May 1, 2019
e684708
clean up docs
catsby May 1, 2019
dd454e5
Merge branch 'master' into cdcr-priority-queue
catsby May 1, 2019
97ac24c
refactor internal data structure to hide heap method implementations.…
catsby May 2, 2019
f6ed9b9
rename PushItem and PopItem to just Push/Pop, after encapsulating the…
catsby May 2, 2019
41ffc88
updates after feedback
catsby May 3, 2019
fbca439
refactoring/renaming
catsby May 3, 2019
5eb067c
guard against pushing a nil item
catsby May 3, 2019
47b4a4d
minor updates after feedback
catsby May 3, 2019
2c38dc6
Add SetCredentials, GenerateCredentials gRPC methods to combined data…
catsby May 1, 2019
50b221b
Initial Combined database backend implementation of static accounts a…
catsby May 1, 2019
0a3c56b
vendor updates
catsby May 1, 2019
5af0fdb
initial implementation of static accounts with Combined database back…
catsby May 1, 2019
4d47b9a
add lock and setup of rotation queue
catsby May 1, 2019
c1ae80d
vendor the queue
catsby May 1, 2019
42d01f8
rebase on new method signature of queue
catsby May 2, 2019
bebd2b5
remove mongo tests for now
catsby May 2, 2019
f94ffb8
update default role sql
catsby May 3, 2019
784585d
gofmt after rebase
catsby May 3, 2019
c006f1e
cleanup after rebasing to remove checks for ErrNotFound error
catsby May 3, 2019
2041814
rebase cdcr-priority-queue
catsby May 3, 2019
2ba681d
vendor dependencies with 'go mod vendor'
catsby May 3, 2019
7a9774b
website database docs for Static Role support
catsby May 6, 2019
3aa9b09
document the rotate-role API endpoint
catsby May 6, 2019
e4977bb
postgres specific static role docs
catsby May 6, 2019
b701a4a
use constants for paths
catsby May 9, 2019
236dc18
updates from review
catsby May 9, 2019
ce5fea3
remove dead code
catsby May 9, 2019
bf9358c
combine and clarify error message for older plugins
catsby May 10, 2019
d10a715
Update builtin/logical/database/backend.go
catsby May 13, 2019
c0907c0
cleanups from feedback
catsby May 13, 2019
d5df52c
code and comment cleanups
catsby May 14, 2019
447bd4d
move db.RLock higher to protect db.GenerateCredentials call
catsby May 14, 2019
8259534
Return output with WALID if we failed to delete the WAL
catsby May 14, 2019
616e4e0
Update builtin/logical/database/path_creds_create.go
catsby May 14, 2019
da1b8cc
Merge branch 'master' into cdcr-backend-grpc
catsby May 16, 2019
9c570c1
updates after running 'make fmt'
catsby May 16, 2019
cee43de
update after running 'make proto'
catsby May 16, 2019
f488a46
Update builtin/logical/database/path_roles.go
catsby May 16, 2019
9d86150
Update builtin/logical/database/path_roles.go
catsby May 16, 2019
21edf95
update comment and remove and rearrange some dead code
catsby May 16, 2019
e19e134
Update website/source/api/secret/databases/index.html.md
catsby May 16, 2019
a6affaf
cleanups after review
catsby May 16, 2019
134d8ec
Merge branch 'cdcr-backend-grpc' of github.com:hashicorp/vault into c…
catsby May 16, 2019
fb789a6
Update sdk/database/dbplugin/grpc_transport.go
catsby May 16, 2019
2ca4849
code cleanup after feedback
catsby May 17, 2019
5017de7
Merge branch 'cdcr-backend-grpc' of github.com:hashicorp/vault into c…
catsby May 17, 2019
06c4624
remove PasswordLastSet; it's not used
catsby May 17, 2019
01dab21
document GenerateCredentials and SetCredentials
catsby May 17, 2019
accd35e
Update builtin/logical/database/path_rotate_credentials.go
catsby May 17, 2019
c9246ad
wrap pop and popbykey in backend methods to protect against nil cred …
catsby May 17, 2019
b631b4f
Merge branch 'cdcr-backend-grpc' of github.com:hashicorp/vault into c…
catsby May 17, 2019
4081d27
use strings.HasPrefix instead of direct equality check for path
catsby May 17, 2019
7ee9e10
Forgot to commit this
catsby May 17, 2019
5eb9da1
updates after feedback
catsby May 20, 2019
96b3cbb
re-purpose an outdated test to now check that static and dynamic role…
catsby May 20, 2019
e1ffe33
check for unique name across dynamic and static roles
catsby May 20, 2019
547a8eb
refactor loadStaticWALs to return a map of name/setCredentialsWAL str…
catsby May 20, 2019
52c039b
remove commented out code
catsby May 20, 2019
8839757
refactor to have loadstaticwals filter out wals for roles that no lon…
catsby May 22, 2019
ba1b4a7
return error if nil input given
catsby May 22, 2019
29b0564
add nil check for input into setStaticAccount
catsby May 22, 2019
da66aca
Update builtin/logical/database/path_roles.go
catsby May 22, 2019
03b1ae9
add constant for queue tick time in seconds, used for comparrison in …
catsby May 22, 2019
e19307d
Update builtin/logical/database/path_roles.go
catsby May 22, 2019
c30b33f
code cleanup after review
catsby May 22, 2019
c00320f
remove misplaced code comment
catsby May 22, 2019
f6df0ff
Merge branch 'cdcr-backend-grpc' of github.com:hashicorp/vault into c…
catsby May 22, 2019
b1b8a62
remove commented out code
catsby May 22, 2019
35b66f2
create a queue in the Factory method, even if it's never used
catsby May 22, 2019
5b387f5
update path_roles to use a common set of fields, with specific overri…
catsby May 22, 2019
6babbfa
document new method
catsby May 23, 2019
a3be9e4
move rotation things into a specific file
catsby May 24, 2019
cc60788
rename test file and consolidate some static account tests
catsby Jun 5, 2019
d4fd2bf
Update builtin/logical/database/path_roles.go
catsby Jun 7, 2019
9c06ac4
Update builtin/logical/database/rotation.go
catsby Jun 7, 2019
e9ce49d
Update builtin/logical/database/rotation.go
catsby Jun 7, 2019
8ee12a3
Update builtin/logical/database/rotation.go
catsby Jun 7, 2019
d26c7b1
Update builtin/logical/database/rotation.go
catsby Jun 7, 2019
28312dc
Update builtin/logical/database/rotation.go
catsby Jun 7, 2019
baa08ce
update code comments, method names, and move more methods into rotati…
catsby Jun 7, 2019
b410c03
update comments to be capitalized
catsby Jun 7, 2019
102be14
remove the item from the queue before we try to destroy it
catsby Jun 7, 2019
97d562b
findStaticWAL returns an error
catsby Jun 7, 2019
f5c7a9c
use lowercase keys when encoding WAL entries
catsby Jun 7, 2019
c637769
small cleanups
catsby Jun 7, 2019
42f088f
remove vestigial static account check
catsby Jun 7, 2019
94b9106
remove redundant DeleteWAL call in populate queue
catsby Jun 10, 2019
3cf4b4d
if we error on loading role, push back to queue with 10 second backoff
catsby Jun 10, 2019
0d694a7
poll in initqueue to make sure the backend is setup and can write/del…
catsby Jun 11, 2019
5198d65
add revoke_user_on_delete flag to allow users to opt-in to revoking t…
catsby Jun 11, 2019
61acdfe
add code comments on read-only loop
catsby Jun 12, 2019
ad4c68f
code comment updates
catsby Jun 12, 2019
46c9a1d
re-push if error returned from find static wal
catsby Jun 12, 2019
82e7779
add locksutil and acquire locks when pop'ing from the queue
catsby Jun 12, 2019
ea4f96a
grab exclusive locks for updating static roles
catsby Jun 13, 2019
779e195
Add SetCredentials and GenerateCredentials stubs to mockPlugin
catsby Jun 17, 2019
ae5563c
add a switch in initQueue to listen for cancelation
catsby Jun 17, 2019
ff69fd4
remove guard on zero time, it should have no affect
catsby Jun 17, 2019
7e6ca8a
create a new context in Factory to pass on and use for closing the ba…
catsby Jun 18, 2019
3f4df2e
Merge branch 'master' into cdcr-rotation
catsby Jun 18, 2019
d4d3af6
restore master copy of vendor dir
catsby Jun 18, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions builtin/credential/cert/backend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1163,8 +1163,8 @@ func TestBackend_email_singleCert(t *testing.T) {
Subject: pkix.Name{
CommonName: "example.com",
},
EmailAddresses: []string{"[email protected]"},
IPAddresses: []net.IP{net.ParseIP("127.0.0.1")},
EmailAddresses: []string{"[email protected]"},
IPAddresses: []net.IP{net.ParseIP("127.0.0.1")},
ExtKeyUsage: []x509.ExtKeyUsage{
x509.ExtKeyUsageServerAuth,
x509.ExtKeyUsageClientAuth,
Expand Down
82 changes: 71 additions & 11 deletions builtin/logical/database/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,17 @@ import (
"github.com/hashicorp/vault/sdk/database/dbplugin"
"github.com/hashicorp/vault/sdk/database/helper/dbutil"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/helper/locksutil"
"github.com/hashicorp/vault/sdk/helper/strutil"
"github.com/hashicorp/vault/sdk/logical"
"github.com/hashicorp/vault/sdk/queue"
)

const databaseConfigPath = "database/config/"
const (
databaseConfigPath = "database/config/"
databaseRolePath = "role/"
databaseStaticRolePath = "static-role/"
)

type dbPluginInstance struct {
sync.RWMutex
Expand Down Expand Up @@ -46,6 +52,14 @@ func Factory(ctx context.Context, conf *logical.BackendConfig) (logical.Backend,
if err := b.Setup(ctx, conf); err != nil {
return nil, err
}

b.credRotationQueue = queue.New()
// Create a context with a cancel method for processing any WAL entries and
// populating the queue
ctx, cancel := context.WithCancel(ctx)
b.cancelQueue = cancel
// Load queue and kickoff new periodic ticker
go b.initQueue(ctx, conf)
return b, nil
}

Expand All @@ -55,31 +69,39 @@ func Backend(conf *logical.BackendConfig) *databaseBackend {
Help: strings.TrimSpace(backendHelp),

PathsSpecial: &logical.Paths{
LocalStorage: []string{
framework.WALPrefix,
},
SealWrapStorage: []string{
"config/*",
"static-role/*",
},
},

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

Secrets: []*framework.Secret{
secretCreds(&b),
},
Clean: b.closeAllDBs,
Clean: b.clean,
Invalidate: b.invalidate,
BackendType: logical.TypeLogical,
}

b.logger = conf.Logger
b.connections = make(map[string]*dbPluginInstance)

b.roleLocks = locksutil.CreateLocks()

return &b
}

Expand All @@ -89,6 +111,20 @@ type databaseBackend struct {

*framework.Backend
sync.RWMutex
// CredRotationQueue is an in-memory priority queue used to track Static Roles
// that require periodic rotation. Backends will have a PriorityQueue
// initialized on setup, but only backends that are mounted by a primary
// server or mounted as a local mount will perform the rotations.
//
// cancelQueue is used to remove the priority queue and terminate the
// background ticker.
credRotationQueue *queue.PriorityQueue
cancelQueue context.CancelFunc

// roleLocks is used to lock modifications to roles in the queue, to ensure
// concurrent requests are not modifying the same role and possibly causing
// issues with the priority queue.
roleLocks []*locksutil.LockEntry
}

func (b *databaseBackend) DatabaseConfig(ctx context.Context, s logical.Storage, name string) (*DatabaseConfig, error) {
Expand Down Expand Up @@ -124,7 +160,15 @@ type upgradeCheck struct {
}

func (b *databaseBackend) Role(ctx context.Context, s logical.Storage, roleName string) (*roleEntry, error) {
entry, err := s.Get(ctx, "role/"+roleName)
return b.roleAtPath(ctx, s, roleName, databaseRolePath)
}

func (b *databaseBackend) StaticRole(ctx context.Context, s logical.Storage, roleName string) (*roleEntry, error) {
return b.roleAtPath(ctx, s, roleName, databaseStaticRolePath)
}

func (b *databaseBackend) roleAtPath(ctx context.Context, s logical.Storage, roleName string, pathPrefix string) (*roleEntry, error) {
entry, err := s.Get(ctx, pathPrefix+roleName)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -228,6 +272,17 @@ func (b *databaseBackend) GetConnection(ctx context.Context, s logical.Storage,
return db, nil
}

// invalidateQueue cancels any background queue loading and destroys the queue.
func (b *databaseBackend) invalidateQueue() {
b.Lock()
defer b.Unlock()

if b.cancelQueue != nil {
b.cancelQueue()
}
b.credRotationQueue = nil
}

// ClearConnection closes the database connection and
// removes it from the b.connections map.
func (b *databaseBackend) ClearConnection(name string) error {
Expand Down Expand Up @@ -267,8 +322,13 @@ func (b *databaseBackend) CloseIfShutdown(db *dbPluginInstance, err error) {
}
}

// closeAllDBs closes all connections from all database types
func (b *databaseBackend) closeAllDBs(ctx context.Context) {
// clean closes all connections from all database types
// and cancels any rotation queue loading operation.
func (b *databaseBackend) clean(ctx context.Context) {
// invalidateQueue acquires it's own lock on the backend, removes queue, and
// terminates the background ticker
b.invalidateQueue()

b.Lock()
defer b.Unlock()

Expand Down
39 changes: 3 additions & 36 deletions builtin/logical/database/backend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func preparePostgresTestContainer(t *testing.T, s logical.Storage, b logical.Bac

retURL = fmt.Sprintf("postgres://postgres:secret@localhost:%s/database?sslmode=disable", resource.GetPort("5432/tcp"))

// exponential backoff-retry
// Exponential backoff-retry
if err = pool.Retry(func() error {
// This will cause a validation to run
resp, err := b.HandleRequest(namespace.RootContext(nil), &logical.Request{
Expand Down Expand Up @@ -101,12 +101,12 @@ func getCluster(t *testing.T) (*vault.TestCluster, logical.SystemView) {
os.Setenv(pluginutil.PluginCACertPEMEnv, cluster.CACertPEMFile)

sys := vault.TestDynamicSystemView(cores[0].Core)
vault.TestAddTestPlugin(t, cores[0].Core, "postgresql-database-plugin", consts.PluginTypeDatabase, "TestBackend_PluginMain", []string{}, "")
vault.TestAddTestPlugin(t, cores[0].Core, "postgresql-database-plugin", consts.PluginTypeDatabase, "TestBackend_PluginMain_Postgres", []string{}, "")

return cluster, sys
}

func TestBackend_PluginMain(t *testing.T) {
func TestBackend_PluginMain_Postgres(t *testing.T) {
if os.Getenv(pluginutil.PluginUnwrapTokenEnv) == "" {
return
}
Expand Down Expand Up @@ -850,17 +850,6 @@ func TestBackend_roleCrud(t *testing.T) {
t.Fatalf("err:%s resp:%#v\n", err, resp)
}

exists, err := b.pathRoleExistenceCheck()(context.Background(), req, &framework.FieldData{
Raw: data,
Schema: pathRoles(b).Fields,
})
if err != nil {
t.Fatal(err)
}
if exists {
t.Fatal("expected not exists")
}

// Read the role
data = map[string]interface{}{}
req = &logical.Request{
Expand Down Expand Up @@ -920,17 +909,6 @@ func TestBackend_roleCrud(t *testing.T) {
t.Fatalf("err:%v resp:%#v\n", err, resp)
}

exists, err := b.pathRoleExistenceCheck()(context.Background(), req, &framework.FieldData{
Raw: data,
Schema: pathRoles(b).Fields,
})
if err != nil {
t.Fatal(err)
}
if !exists {
t.Fatal("expected exists")
}

// Read the role
data = map[string]interface{}{}
req = &logical.Request{
Expand Down Expand Up @@ -994,17 +972,6 @@ func TestBackend_roleCrud(t *testing.T) {
t.Fatalf("err:%v resp:%#v\n", err, resp)
}

exists, err := b.pathRoleExistenceCheck()(context.Background(), req, &framework.FieldData{
Raw: data,
Schema: pathRoles(b).Fields,
})
if err != nil {
t.Fatal(err)
}
if !exists {
t.Fatal("expected exists")
}

// Read the role
data = map[string]interface{}{}
req = &logical.Request{
Expand Down
10 changes: 10 additions & 0 deletions builtin/logical/database/dbplugin/plugin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ type mockPlugin struct {
users map[string][]string
}

var _ dbplugin.Database = &mockPlugin{}

func (m *mockPlugin) Type() (string, error) { return "mock", nil }
func (m *mockPlugin) CreateUser(_ context.Context, statements dbplugin.Statements, usernameConf dbplugin.UsernameConfig, expiration time.Time) (username string, password string, err error) {
err = errors.New("err")
Expand Down Expand Up @@ -86,6 +88,14 @@ func (m *mockPlugin) Close() error {
return nil
}

func (m *mockPlugin) GenerateCredentials(ctx context.Context) (password string, err error) {
return password, err
}

func (m *mockPlugin) SetCredentials(ctx context.Context, statements dbplugin.Statements, staticConfig dbplugin.StaticUserConfig) (username string, password string, err error) {
return username, password, err
}

func getCluster(t *testing.T) (*vault.TestCluster, logical.SystemView) {
cluster := vault.NewTestCluster(t, nil, &vault.TestClusterOptions{
HandlerFunc: vaulthttp.Handler,
Expand Down
88 changes: 76 additions & 12 deletions builtin/logical/database/path_creds_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,40 @@ import (
"github.com/hashicorp/vault/sdk/logical"
)

func pathCredsCreate(b *databaseBackend) *framework.Path {
return &framework.Path{
Pattern: "creds/" + framework.GenericNameRegex("name"),
Fields: map[string]*framework.FieldSchema{
"name": &framework.FieldSchema{
Type: framework.TypeString,
Description: "Name of the role.",
func pathCredsCreate(b *databaseBackend) []*framework.Path {
return []*framework.Path{
&framework.Path{
Pattern: "creds/" + framework.GenericNameRegex("name"),
Fields: map[string]*framework.FieldSchema{
"name": &framework.FieldSchema{
Type: framework.TypeString,
Description: "Name of the role.",
},
},

Callbacks: map[logical.Operation]framework.OperationFunc{
logical.ReadOperation: b.pathCredsCreateRead(),
},
},

Callbacks: map[logical.Operation]framework.OperationFunc{
logical.ReadOperation: b.pathCredsCreateRead(),
HelpSynopsis: pathCredsCreateReadHelpSyn,
HelpDescription: pathCredsCreateReadHelpDesc,
},
&framework.Path{
Pattern: "static-creds/" + framework.GenericNameRegex("name"),
Fields: map[string]*framework.FieldSchema{
"name": &framework.FieldSchema{
Type: framework.TypeString,
Description: "Name of the static role.",
},
},

HelpSynopsis: pathCredsCreateReadHelpSyn,
HelpDescription: pathCredsCreateReadHelpDesc,
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.ReadOperation: b.pathStaticCredsRead(),
},

HelpSynopsis: pathStaticCredsReadHelpSyn,
HelpDescription: pathStaticCredsReadHelpDesc,
},
}
}

Expand Down Expand Up @@ -99,6 +117,41 @@ func (b *databaseBackend) pathCredsCreateRead() framework.OperationFunc {
}
}

func (b *databaseBackend) pathStaticCredsRead() framework.OperationFunc {
return func(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
name := data.Get("name").(string)

role, err := b.StaticRole(ctx, req.Storage, name)
if err != nil {
return nil, err
}
if role == nil {
return logical.ErrorResponse("unknown role: %s", name), nil
}

dbConfig, err := b.DatabaseConfig(ctx, req.Storage, role.DBName)
if err != nil {
return nil, err
}

// If role name isn't in the database's allowed roles, send back a
// permission denied.
if !strutil.StrListContains(dbConfig.AllowedRoles, "*") && !strutil.StrListContainsGlob(dbConfig.AllowedRoles, name) {
return nil, fmt.Errorf("%q is not an allowed role", name)
}

return &logical.Response{
Data: map[string]interface{}{
"username": role.StaticAccount.Username,
"password": role.StaticAccount.Password,
"ttl": role.StaticAccount.PasswordTTL().Seconds(),
"rotation_period": role.StaticAccount.RotationPeriod.Seconds(),
"last_vault_rotation": role.StaticAccount.LastVaultRotation,
},
}, nil
}
}

const pathCredsCreateReadHelpSyn = `
Request database credentials for a certain role.
`
Expand All @@ -108,3 +161,14 @@ This path reads database credentials for a certain role. The
database credentials will be generated on demand and will be automatically
revoked when the lease is up.
`

const pathStaticCredsReadHelpSyn = `
Request database credentials for a certain static role. These credentials are
rotated periodically.
`

const pathStaticCredsReadHelpDesc = `
This path reads database credentials for a certain static role. The database
credentials are rotated periodically according to their configuration, and will
return the same password until they are rotated.
`
Loading