secretrotation

package
v1.0.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 13, 2026 License: Apache-2.0 Imports: 12 Imported by: 0

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

View Source
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.

View Source
const (
	StatusOK       = "ok"
	StatusDueSoon  = "due_soon"
	StatusOverdue  = "overdue"
	StatusUnknown  = "unknown"
	PreviousSuffix = "_PREVIOUS"
)

Policy status values reported to operators.

View Source
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

View Source
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

func Basis(lastRotated, valueUpdated time.Time) time.Time

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

func IntFromEnv(getenv func(string) string, key string, def int) int

IntFromEnv reads a positive integer env value, or def when unset or invalid.

func IsPreviousKey

func IsPreviousKey(key string) bool

IsPreviousKey reports whether key is a grace companion, never a policy target.

func NextDue

func NextDue(basis time.Time, intervalDays int) time.Time

NextDue returns when a rotation is due, or the zero time when the basis is unknown or the interval is not positive.

func PreviousKey

func PreviousKey(key string) string

PreviousKey is the env key that holds the prior value during the grace window.

func StatusAt

func StatusAt(now, due time.Time, soon time.Duration) string

StatusAt classifies a due date against now and the due-soon window.

Types

type Auditor

type Auditor interface {
	Record(ctx context.Context, action, app, key, actor string)
}

Auditor records one rotation event. It is never handed a value.

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

func (e *Engine) Rotate(ctx context.Context, app, key, actor string) error

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

func (e *Engine) RotateValue(ctx context.Context, app, key, value, actor string) error

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.

func (*Engine) Run

func (e *Engine) Run(ctx context.Context, interval time.Duration) error

Run sweeps at boot and then every interval until ctx ends.

func (*Engine) Sweep

func (e *Engine) Sweep(ctx context.Context) int

Sweep runs one pass: retry stuck restarts, expire grace values, rotate what is due. It returns how many scheduled rotations it started.

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL