Documentation
¶
Overview ¶
Package secretrotation schedules and performs per-secret rotation policies for app secrets: generate a new value, keep the old one for a grace window, restart the owning app, and record every step.
Index ¶
- Constants
- Variables
- func Basis(lastRotated, valueUpdated time.Time) time.Time
- func IntFromEnv(getenv func(string) string, key string, def int) int
- func IsPreviousKey(key string) bool
- func NextDue(basis time.Time, intervalDays int) time.Time
- func PreviousKey(key string) string
- func StatusAt(now, due time.Time, soon time.Duration) string
- type Auditor
- type Backend
- type Engine
- type Store
Constants ¶
const ( ReasonLocked = "the secret is locked, unlock it to allow rotation" ReasonMissing = "the secret or its app no longer exists" ReasonPreviousInUse = "the grace key is already used by another secret" ReasonWriteFailed = "writing the new value failed, nothing was changed" ReasonRestartFailed = "the new value is stored but restarting the app failed, retry the restart" ActionRotate = "secret.rotate" ActionRestartFailed = "secret.rotate.restart_failed" ActionRotateFailed = "secret.rotate.failed" ActionGraceExpired = "secret.rotate.grace_expired" )
Reasons stored on a policy. Fixed strings only: an error from a restart or a store call is logged, never persisted, so no value can leak into state.
const ( StatusOK = "ok" StatusDueSoon = "due_soon" StatusOverdue = "overdue" StatusUnknown = "unknown" PreviousSuffix = "_PREVIOUS" )
Policy status values reported to operators.
const ( EnvDueSoonDays = "APP_SECRET_ROTATION_DUE_SOON_DAYS" EnvIntervalSecs = "APP_SECRET_ROTATION_INTERVAL_SECONDS" EnvMaxPerPass = "APP_SECRET_ROTATION_MAX_PER_PASS" //nolint:gosec // env var name, not a credential EnvGraceHours = "APP_SECRET_ROTATION_GRACE_HOURS" EnvDefaultDays = "APP_SECRET_ROTATION_DEFAULT_INTERVAL_DAYS" DefaultGraceHrs = 24 DefaultDays = 90 DefaultDueSoon = 7 * 24 * time.Hour DefaultInterval = time.Hour DefaultMaxPerRun = 10 )
Env names and defaults. The package keeps no global state, callers read the environment and pass values in.
Variables ¶
var ErrNotAuto = errors.New("secretrotation: policy is not auto-rotatable")
ErrNotAuto is returned when rotation is requested for a policy that is not marked auto-rotatable.
Functions ¶
func Basis ¶
Basis picks the reference time for the next due date: the later of the policy's own last rotation and the secret value's last write, so a manual edit of the value also resets the clock.
func IntFromEnv ¶
IntFromEnv reads a positive integer env value, or def when unset or invalid.
func IsPreviousKey ¶
IsPreviousKey reports whether key is a grace companion, never a policy target.
func NextDue ¶
NextDue returns when a rotation is due, or the zero time when the basis is unknown or the interval is not positive.
func PreviousKey ¶
PreviousKey is the env key that holds the prior value during the grace window.
Types ¶
type Backend ¶
type Backend interface {
// AppState reports whether the app exists and whether it is stopped.
AppState(ctx context.Context, app string) (exists, suspended bool, err error)
// Value reads a secret in plaintext. Only the engine's rotate path calls it.
Value(ctx context.Context, app, key string) (string, error)
SetValue(ctx context.Context, app, key, value string) error
DeleteValue(ctx context.Context, app, key string) error
// KeyState reports existence, lock state and last write time without a value.
KeyState(ctx context.Context, app, key string) (exists, locked bool, updatedAt time.Time, err error)
// Restart recreates the app through the normal health-gated cutover.
Restart(ctx context.Context, app string) error
}
Backend is the secret and app surface rotation acts through.
type Engine ¶
type Engine struct {
Store Store
Backend Backend
Audit Auditor
Logger *slog.Logger
Now func() time.Time
// MaxPerPass bounds scheduled rotations per pass so restarts spread out.
MaxPerPass int
// contains filtered or unexported fields
}
Engine runs rotation policies.
func (*Engine) Rotate ¶
Rotate performs one rotation of a policy's secret, as actor. A failure before the new value is written leaves every value untouched. A failure after leaves the policy in restart_failed so the next pass retries only the restart and the failure stays visible.
func (*Engine) RotateValue ¶
RotateValue writes a new value for a secret (value, or a generated one when empty) without restarting the app, so the caller can offer a redeploy. The policy must already exist; it carries the grace window and progress.
type Store ¶
type Store interface {
GetSecretRotationPolicy(ctx context.Context, service, key string) (store.SecretRotationPolicy, error)
ListSecretRotationPolicies(ctx context.Context, service string) ([]store.SecretRotationPolicy, error)
SaveSecretRotationProgress(ctx context.Context, p store.SecretRotationPolicy, now time.Time) error
DeleteSecretRotationPolicy(ctx context.Context, service, key string) (bool, error)
}
Store is the policy persistence the engine needs.