mirror of
https://github.com/hashicorp/vault.git
synced 2026-02-18 18:38:08 -05:00
* initial implementation * fix * tests * changelog * fix vet errors * pr comments Co-authored-by: miagilepner <mia.epner@hashicorp.com>
This commit is contained in:
parent
3c459f7dca
commit
eaf949cb1f
5 changed files with 147 additions and 6 deletions
|
|
@ -108,6 +108,7 @@ func Backend(conf *logical.BackendConfig) *databaseBackend {
|
|||
"config/*",
|
||||
"static-role/*",
|
||||
},
|
||||
AllowSnapshotRead: []string{"static-roles/*", "static-roles", "static-creds/*"},
|
||||
},
|
||||
Paths: framework.PathAppend(
|
||||
[]*framework.Path{
|
||||
|
|
|
|||
|
|
@ -92,10 +92,11 @@ func pathRoles(b *databaseBackend) []*framework.Path {
|
|||
Fields: fieldsForType(databaseStaticRolePath),
|
||||
ExistenceCheck: b.pathStaticRoleExistenceCheck,
|
||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||
logical.ReadOperation: b.pathStaticRoleRead,
|
||||
logical.CreateOperation: b.pathStaticRoleCreateUpdate,
|
||||
logical.UpdateOperation: b.pathStaticRoleCreateUpdate,
|
||||
logical.DeleteOperation: b.pathStaticRoleDelete,
|
||||
logical.ReadOperation: b.pathStaticRoleRead,
|
||||
logical.CreateOperation: b.pathStaticRoleCreateUpdate,
|
||||
logical.UpdateOperation: b.pathStaticRoleCreateUpdate,
|
||||
logical.DeleteOperation: b.pathStaticRoleDelete,
|
||||
logical.RecoverOperation: b.pathStaticRoleRecover,
|
||||
},
|
||||
|
||||
HelpSynopsis: pathStaticRoleHelpSyn,
|
||||
|
|
@ -546,6 +547,45 @@ func (b *databaseBackend) pathRoleCreateUpdate(ctx context.Context, req *logical
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
func (b *databaseBackend) pathStaticRoleRecover(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
exists, err := b.pathStaticRoleExistenceCheck(ctx, req, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if exists {
|
||||
return logical.ErrorResponse("cannot recover a static role that already exists"), nil
|
||||
}
|
||||
|
||||
snapStorage, err := logical.NewSnapshotStorageView(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create snapshot storage: %s", err)
|
||||
}
|
||||
|
||||
name := data.Get("name").(string)
|
||||
if req.RecoverSourcePath != "" {
|
||||
fd, err := b.RecoverSourcePathFieldData(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse the recover source path: %w", err)
|
||||
}
|
||||
name = fd.Get("name").(string)
|
||||
}
|
||||
|
||||
role, err := b.StaticRole(ctx, snapStorage, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if role.StaticAccount.Password != "" {
|
||||
data.Raw["password"] = role.StaticAccount.Password
|
||||
}
|
||||
req.Operation = logical.CreateOperation
|
||||
defer func() {
|
||||
req.Operation = logical.RecoverOperation
|
||||
}()
|
||||
return b.pathStaticRoleCreateUpdate(ctx, req, data)
|
||||
}
|
||||
|
||||
// ignore-nil-nil-function-check
|
||||
func (b *databaseBackend) pathStaticRoleCreateUpdate(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
response := &logical.Response{}
|
||||
name := data.Get("name").(string)
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import (
|
|||
"github.com/hashicorp/vault/helper/namespace"
|
||||
postgreshelper "github.com/hashicorp/vault/helper/testhelpers/postgresql"
|
||||
v5 "github.com/hashicorp/vault/sdk/database/dbplugin/v5"
|
||||
"github.com/hashicorp/vault/sdk/helper/testhelpers/snapshots"
|
||||
"github.com/hashicorp/vault/sdk/logical"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
|
|
@ -1494,3 +1495,90 @@ ALTER USER "{{name}}" WITH PASSWORD '{{password}}';
|
|||
const testRoleStaticUpdateRotation = `
|
||||
ALTER USER "{{name}}" WITH PASSWORD '{{password}}';GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO "{{name}}";
|
||||
`
|
||||
|
||||
// TestStaticRole_Recover verifies that a role that exists in a snapshot can be
|
||||
// read, listed, and recovered
|
||||
func TestStaticRole_Recover(t *testing.T) {
|
||||
b, storage, mockDB := getBackend(t)
|
||||
b2, snapStorage, mockDBSnap := getBackend(t)
|
||||
ctx := context.Background()
|
||||
defer b.Cleanup(ctx)
|
||||
defer b2.Cleanup(ctx)
|
||||
tc := snapshots.NewSnapshotTestCaseWithStorages(t, b, storage, snapStorage)
|
||||
|
||||
configureDBMount(t, storage)
|
||||
configureDBMount(t, snapStorage)
|
||||
|
||||
createRole(t, b2, snapStorage, mockDBSnap, "hashicorp")
|
||||
|
||||
tc.RunRead(t, "static-roles/hashicorp")
|
||||
|
||||
tc.RunList(t, "static-roles")
|
||||
|
||||
mockDB.On("UpdateUser", mock.Anything, mock.Anything).
|
||||
Return(v5.UpdateUserResponse{}, nil).
|
||||
Once()
|
||||
_, err := tc.DoRecover(t, "static-roles/hashicorp")
|
||||
require.NoError(t, err)
|
||||
readStaticCred(t, b, storage, mockDB, "hashicorp")
|
||||
|
||||
mockDB.On("UpdateUser", mock.Anything, mock.Anything).
|
||||
Return(v5.UpdateUserResponse{}, nil).
|
||||
Once()
|
||||
_, err = tc.DoRecover(t, "static-roles/hashicorp-copy", snapshots.WithRecoverSourcePath("static-roles/hashicorp"))
|
||||
require.NoError(t, err)
|
||||
readStaticCred(t, b, storage, mockDB, "hashicorp-copy")
|
||||
}
|
||||
|
||||
// TestStaticRole_RecoverExists verifies that a static role cannot be updated
|
||||
// via a recover operation, but can be copied to a new role
|
||||
func TestStaticRole_RecoverExists(t *testing.T) {
|
||||
b, storage, mockDB := getBackend(t)
|
||||
b2, snapStorage, mockDBSnap := getBackend(t)
|
||||
ctx := context.Background()
|
||||
defer b.Cleanup(ctx)
|
||||
defer b2.Cleanup(ctx)
|
||||
tc := snapshots.NewSnapshotTestCaseWithStorages(t, b, storage, snapStorage)
|
||||
|
||||
configureDBMount(t, storage)
|
||||
configureDBMount(t, snapStorage)
|
||||
|
||||
createRole(t, b2, snapStorage, mockDBSnap, "hashicorp")
|
||||
createRole(t, b, storage, mockDB, "hashicorp")
|
||||
|
||||
resp, err := tc.DoRecover(t, "static-roles/hashicorp")
|
||||
require.NoError(t, err)
|
||||
require.True(t, resp.IsError())
|
||||
require.ErrorContains(t, resp.Error(), "cannot recover a static role that already exists")
|
||||
|
||||
mockDB.On("UpdateUser", mock.Anything, mock.Anything).
|
||||
Return(v5.UpdateUserResponse{}, nil).
|
||||
Once()
|
||||
resp, err = tc.DoRecover(t, "static-roles/hashicorp-copy", snapshots.WithRecoverSourcePath("static-roles/hashicorp"))
|
||||
require.NoError(t, err)
|
||||
require.False(t, resp.IsError())
|
||||
readStaticCred(t, b, storage, mockDB, "hashicorp-copy")
|
||||
}
|
||||
|
||||
// TestStaticCreds_Recover verifies that static credentials can be read from the
|
||||
// snapshot without side effects, but they cannot be recovered
|
||||
func TestStaticCreds_Recover(t *testing.T) {
|
||||
b, storage, mockDB := getBackend(t)
|
||||
b2, snapStorage, mockDBSnap := getBackend(t)
|
||||
ctx := context.Background()
|
||||
defer b.Cleanup(ctx)
|
||||
defer b2.Cleanup(ctx)
|
||||
tc := snapshots.NewSnapshotTestCaseWithStorages(t, b, storage, snapStorage)
|
||||
|
||||
configureDBMount(t, storage)
|
||||
configureDBMount(t, snapStorage)
|
||||
|
||||
createRole(t, b2, snapStorage, mockDBSnap, "hashicorp")
|
||||
createRole(t, b, storage, mockDB, "hashicorp")
|
||||
|
||||
rotateRole(t, b, snapStorage, mockDB, "hashicorp")
|
||||
tc.RunRead(t, "static-creds/hashicorp")
|
||||
|
||||
_, err := tc.DoRecover(t, "static-creds/hashicorp")
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
|
|
|||
3
changelog/_8922.txt
Normal file
3
changelog/_8922.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
```release-note:improvement
|
||||
secrets/database (enterprise): Add support for reading, listing, and recovering static roles from a loaded snapshot. Also add support for reading static credentials from a loaded snapshot.
|
||||
```
|
||||
|
|
@ -33,10 +33,19 @@ var _ logical.SnapshotStorageProvider = (*storageProvider)(nil)
|
|||
// when it receives snapshot operations, without having to do the end-to-end
|
||||
// setup of creating a raft cluster, taking a snapshot, and loading it.
|
||||
func NewSnapshotTestCase(t testing.TB, backend logical.Backend) *SnapshotTestCase {
|
||||
return NewSnapshotTestCaseWithStorages(t, backend, &logical.InmemStorage{}, &logical.InmemStorage{})
|
||||
}
|
||||
|
||||
// NewSnapshotTestCaseWithStorages is used to create a snapshot test case for a
|
||||
// particular backend, using the provided storage instances. The test case is
|
||||
// used to ensure that the backend behaves correctly when it receives snapshot
|
||||
// operations, without having to do the end-to-end setup of creating a raft
|
||||
// cluster, taking a snapshot, and loading it.
|
||||
func NewSnapshotTestCaseWithStorages(t testing.TB, backend logical.Backend, regularStorage, snapshotStorage logical.Storage) *SnapshotTestCase {
|
||||
s := &SnapshotTestCase{
|
||||
backend: backend,
|
||||
regularStorage: &logical.InmemStorage{},
|
||||
snapshotStorage: &logical.InmemStorage{},
|
||||
regularStorage: regularStorage,
|
||||
snapshotStorage: snapshotStorage,
|
||||
}
|
||||
|
||||
s.storageRouter = logical.NewSnapshotStorageRouter(s.regularStorage, &storageProvider{s.snapshotStorage})
|
||||
|
|
|
|||
Loading…
Reference in a new issue