diff --git a/builtin/logical/database/path_roles.go b/builtin/logical/database/path_roles.go index 56e7ecea10ea..5366ac711c0c 100644 --- a/builtin/logical/database/path_roles.go +++ b/builtin/logical/database/path_roles.go @@ -802,6 +802,10 @@ type staticAccount struct { // LastVaultRotation represents the last time Vault rotated the password LastVaultRotation time.Time `json:"last_vault_rotation"` + // NextVaultRotation represents the next time Vault is expected to rotate + // the password + NextVaultRotation time.Time `json:"next_vault_rotation"` + // RotationPeriod is number in seconds between each rotation, effectively a // "time to live". This value is compared to the LastVaultRotation to // determine if a password needs to be rotated @@ -860,6 +864,39 @@ func (s *staticAccount) UsesRotationPeriod() bool { return s.RotationPeriod != 0 && s.RotationSchedule == "" } +// IsInsideRotationWindow returns true if the current time t is within a given +// static account's rotation window. +// +// Returns true if the rotation window is not set. In this case, the rotation +// window is effectively the span of time between two consecutive rotation +// schedules and we should not prevent rotation. +func (s *staticAccount) IsInsideRotationWindow(t time.Time) bool { + if s.UsesRotationSchedule() && s.RotationWindow != 0 { + return t.Before(s.NextVaultRotation.Add(s.RotationWindow)) + } + return true +} + +// ShouldRotate returns true if a given static account should have its +// credentials rotated. +// +// This will return true when the priority <= the current Unix time. If this +// static account is schedule-based with a rotation window, this method will +// return false if now is outside the rotation window. +func (s *staticAccount) ShouldRotate(priority int64) bool { + now := time.Now() + return priority <= now.Unix() && s.IsInsideRotationWindow(now) +} + +// SetNextVaultRotation +func (s *staticAccount) SetNextVaultRotation(t time.Time) { + if s.UsesRotationPeriod() { + s.NextVaultRotation = t.Add(s.RotationPeriod) + } else { + s.NextVaultRotation = s.Schedule.Next(t) + } +} + // CredentialTTL calculates the approximate time remaining until the credential is // no longer valid. This is approximate because the rotation expiry is only // checked approximately every 5 seconds, and each rotation can take a small diff --git a/builtin/logical/database/rotation.go b/builtin/logical/database/rotation.go index b063bff2c9ea..090678bcba22 100644 --- a/builtin/logical/database/rotation.go +++ b/builtin/logical/database/rotation.go @@ -96,9 +96,6 @@ func (b *databaseBackend) populateQueue(ctx context.Context, s logical.Storage) } else { // previous rotation attempt was interrupted, so we set the // Priority as highest to be processed immediately - - // TODO(JM): ensure we don't process schedule-based rotations - // outside the rotation_window log.Info("found WAL for role", "role", item.Key, "WAL ID", walEntry.walID) item.Value = walEntry.walID item.Priority = time.Now().Unix() @@ -223,11 +220,8 @@ func (b *databaseBackend) rotateCredential(ctx context.Context, s logical.Storag logger = logger.With("database", role.DBName) - // If "now" is less than the Item priority, then this item does not need to - // be rotated - // TODO(JM): ensure we don't process schedule-based rotations - // outside the rotation_window - if time.Now().Unix() < item.Priority { + if !role.StaticAccount.ShouldRotate(item.Priority) { + // do not rotate now, push item back onto queue to be rotated later if err := b.pushItem(item); err != nil { logger.Error("unable to push item on to queue", "error", err) } @@ -274,16 +268,7 @@ func (b *databaseBackend) rotateCredential(ctx context.Context, s logical.Storag } // Update priority and push updated Item to the queue - if role.StaticAccount.UsesRotationSchedule() { - next := role.StaticAccount.Schedule.Next(lvr) - logger.Debug("update priority for Schedule", "next", next) - item.Priority = role.StaticAccount.Schedule.Next(lvr).Unix() - } else if role.StaticAccount.UsesRotationPeriod() { - logger.Debug("update priority for RotationPeriod", "lvr", lvr, "next", lvr.Add(role.StaticAccount.RotationPeriod)) - item.Priority = lvr.Add(role.StaticAccount.RotationPeriod).Unix() - } else { - logger.Error("rotation has not been properly configured") - } + item.Priority = role.StaticAccount.NextRotationTimeFromInput(lvr).Unix() if err := b.pushItem(item); err != nil { logger.Warn("unable to push item on to queue", "error", err) @@ -509,6 +494,7 @@ func (b *databaseBackend) setStaticAccount(ctx context.Context, s logical.Storag // lvr is the known LastVaultRotation lvr := time.Now() input.Role.StaticAccount.LastVaultRotation = lvr + input.Role.StaticAccount.SetNextVaultRotation(lvr) output.RotationTime = lvr entry, err := logical.StorageEntryJSON(databaseStaticRolePath+input.RoleName, input.Role)