db: honor static role TTL across restarts when skip import rotation i… (#29537)

* db: honor static role TTL across restarts when skip import rotation is enabled

* changelog
This commit is contained in:
John-Michael Faircloth
2025-02-10 15:28:19 -06:00
committed by GitHub
parent 49ecdad1ad
commit 8d0443fd48
5 changed files with 65 additions and 11 deletions

View File

@@ -256,9 +256,11 @@ func (b *databaseBackend) pathStaticCredsRead() framework.OperationFunc {
} }
respData := map[string]interface{}{ respData := map[string]interface{}{
"username": role.StaticAccount.Username, "username": role.StaticAccount.Username,
"ttl": role.StaticAccount.CredentialTTL().Seconds(), "ttl": role.StaticAccount.CredentialTTL().Seconds(),
"last_vault_rotation": role.StaticAccount.LastVaultRotation, }
if !role.StaticAccount.LastVaultRotation.IsZero() {
respData["last_vault_rotation"] = role.StaticAccount.LastVaultRotation
} }
if role.StaticAccount.UsesRotationPeriod() { if role.StaticAccount.UsesRotationPeriod() {

View File

@@ -713,14 +713,22 @@ func (b *databaseBackend) pathStaticRoleCreateUpdate(ctx context.Context, req *l
switch req.Operation { switch req.Operation {
case logical.CreateOperation: case logical.CreateOperation:
if role.SkipImportRotation { if role.SkipImportRotation {
b.Logger().Trace("skipping static role import rotation", "role", name) b.Logger().Debug("skipping static role import rotation", "role", name)
// synthetically set lastVaultRotation to now, so that it gets
// queued correctly // Synthetically set lastVaultRotation to now, so that it gets
lastVaultRotation = time.Now() // queued correctly.
// we intentionally do not set role.StaticAccount.LastVaultRotation // NOTE: We intentionally do not set role.StaticAccount.LastVaultRotation
// because the zero value indicates Vault has not rotated the // because the zero value indicates Vault has not rotated the
// password yet // password yet
lastVaultRotation = time.Now()
// NextVaultRotation allows calculating the TTL on GET /static-creds
// requests and to calculate the queue priority in populateQueue()
// across restarts. We can't rely on LastVaultRotation in these
// cases bacause, when import rotation is skipped, LastVaultRotation
// is set to a zero value in storage.
role.StaticAccount.SetNextVaultRotation(lastVaultRotation) role.StaticAccount.SetNextVaultRotation(lastVaultRotation)
// we were told to not rotate, just add the entry // we were told to not rotate, just add the entry
err := b.StoreStaticRole(ctx, req.Storage, role) err := b.StoreStaticRole(ctx, req.Storage, role)
if err != nil { if err != nil {
@@ -936,7 +944,7 @@ type staticAccount struct {
// querying for the next schedule expiry since the last known vault rotation. // querying for the next schedule expiry since the last known vault rotation.
func (s *staticAccount) NextRotationTime() time.Time { func (s *staticAccount) NextRotationTime() time.Time {
if s.UsesRotationPeriod() { if s.UsesRotationPeriod() {
return s.LastVaultRotation.Add(s.RotationPeriod) return s.NextVaultRotation
} }
return s.Schedule.Next(time.Now()) return s.Schedule.Next(time.Now())
} }
@@ -985,7 +993,8 @@ func (s *staticAccount) ShouldRotate(priority int64, t time.Time) bool {
return priority <= t.Unix() && s.IsInsideRotationWindow(t) return priority <= t.Unix() && s.IsInsideRotationWindow(t)
} }
// SetNextVaultRotation // SetNextVaultRotation sets the next vault rotation to time t plus the role's
// rotation period or to the next schedule.
func (s *staticAccount) SetNextVaultRotation(t time.Time) { func (s *staticAccount) SetNextVaultRotation(t time.Time) {
if s.UsesRotationPeriod() { if s.UsesRotationPeriod() {
s.NextVaultRotation = t.Add(s.RotationPeriod) s.NextVaultRotation = t.Add(s.RotationPeriod)

View File

@@ -1388,6 +1388,22 @@ func createRoleWithData(t *testing.T, b *databaseBackend, s logical.Storage, moc
} }
} }
func readStaticCred(t *testing.T, b *databaseBackend, s logical.Storage, mockDB *mockNewDatabase, roleName string) *logical.Response {
t.Helper()
mockDB.On("UpdateUser", mock.Anything, mock.Anything).
Return(v5.UpdateUserResponse{}, nil).
Once()
resp, err := b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.ReadOperation,
Path: "static-creds/" + roleName,
Storage: s,
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatal(resp, err)
}
return resp
}
const testRoleStaticCreate = ` const testRoleStaticCreate = `
CREATE ROLE "{{name}}" WITH CREATE ROLE "{{name}}" WITH
LOGIN LOGIN

View File

@@ -1664,6 +1664,29 @@ func requireWALs(t *testing.T, storage logical.Storage, expectedCount int) []str
return wals return wals
} }
// getBackendWithConfig returns an initialized test backend for the given
// BackendConfig
func getBackendInitQueue(t *testing.T, c *logical.BackendConfig, tickInterval string) (*databaseBackend, *logical.BackendConfig, *mockNewDatabase) {
t.Helper()
// make queue ticks more frequent for tests
c.Config[queueTickIntervalKey] = tickInterval
c.StorageView = &logical.InmemStorage{}
// Create and init the backend ourselves instead of using a Factory because
// the factory function kicks off threads that cause racy tests.
b := Backend(c)
ctx := context.Background()
if err := b.Setup(ctx, c); err != nil {
t.Fatal(err)
}
b.schedule = &TestSchedule{}
b.credRotationQueue = queue.New()
b.initQueue(ctx, c)
mockDB := setupMockDB(b)
return b, c, mockDB
}
func getBackend(t *testing.T) (*databaseBackend, logical.Storage, *mockNewDatabase) { func getBackend(t *testing.T) (*databaseBackend, logical.Storage, *mockNewDatabase) {
t.Helper() t.Helper()
config := logical.TestBackendConfig() config := logical.TestBackendConfig()
@@ -1671,7 +1694,8 @@ func getBackend(t *testing.T) (*databaseBackend, logical.Storage, *mockNewDataba
// Create and init the backend ourselves instead of using a Factory because // Create and init the backend ourselves instead of using a Factory because
// the factory function kicks off threads that cause racy tests. // the factory function kicks off threads that cause racy tests.
b := Backend(config) b := Backend(config)
if err := b.Setup(context.Background(), config); err != nil { ctx := context.Background()
if err := b.Setup(ctx, config); err != nil {
t.Fatal(err) t.Fatal(err)
} }
b.schedule = &TestSchedule{} b.schedule = &TestSchedule{}

3
changelog/29537.txt Normal file
View File

@@ -0,0 +1,3 @@
```release-note:bug
database: Fix a bug where static role passwords are erroneously rotated across backend restarts when using skip import rotation.
```