Documentation
¶
Overview ¶
Package rotate re-wraps stored ciphertext under a new custody key.
Two keys live in the cluster and can leak there: the SRW key, which wraps every Space's Data Key, and the TW key, which wraps every target's S3 credentials (decisions.md #14). Until now neither could be replaced. A suspected exposure therefore had no remedy short of re-running the key ceremony for every Space — the operation that destroys backup history — or re-entering every target credential by hand.
What rotation does and does not touch is the whole point:
- The Data Key is unchanged. Envelopes are re-wrapped, not re-keyed, so no snapshot is rewritten, no data is re-uploaded, and every existing backup stays readable.
- The user's Recovery Key is unchanged and unaffected. It is not in this package: the plaintext RK never reaches the server, so RK rotation happens in the browser and arrives as a finished envelope (see internal/api).
- Nothing here logs, returns, or formats key material. Errors name the record that failed, never its contents.
Rotation is resumable rather than transactional. The backend has no transactions (internal/state), so a rotation interrupted halfway leaves some records on the new key and some on the old. Re-running finishes the job: a record that no longer opens with the old key but does open with the new one is counted as already rotated and left alone. A record that opens with neither is a hard error — that is corruption or the wrong key pair, and carrying on would rewrite the rest under an assumption that has already failed once.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ErrKeyMismatch = errors.New("rotate: record opens with neither the old nor the new key")
ErrKeyMismatch is returned when a stored record opens under neither the old nor the new key. It means the wrong old key was supplied, or the record is corrupt; either way the rotation stops rather than continuing on a false assumption.
Functions ¶
This section is empty.
Types ¶
type EnvelopeStore ¶
type EnvelopeStore interface {
// Spaces returns every space holding an envelope.
Spaces(ctx context.Context) ([]string, error)
// GetSRW returns a space's server envelope.
GetSRW(ctx context.Context, spaceID string) (keys.WrappedDK, error)
// PutSRW stores a space's server envelope.
PutSRW(ctx context.Context, spaceID string, w keys.WrappedDK) error
}
EnvelopeStore is the part of keys.Store a server-key rotation needs.
type Result ¶
type Result struct {
// Rotated is how many records were re-wrapped under the new key.
Rotated int
// AlreadyRotated is how many were found to be on the new key already.
AlreadyRotated int
// Skipped is how many records held nothing to rotate.
Skipped int
}
Result counts what a rotation did. AlreadyRotated is not a warning: it is the normal outcome of finishing an interrupted run.
func SRW ¶
SRW re-wraps every Space's Data Key from oldKey to newKey.
A Space with no server envelope is skipped, not failed: a half-finished key ceremony must not block the rotation of every other Space.
func TW ¶
TW re-seals every target's S3 credentials from oldKey to newKey.
A target's credentials are one sealed record covering every role, so this moves all of them together and cannot leave a target with its backup credential rotated and its maintenance credential stranded.
The plaintext credentials exist in memory for the duration of one re-seal, which is the same exposure a backup run already has (decisions.md #14). They are never logged and never leave this function.
type TargetStore ¶
type TargetStore interface {
// ListTargets returns every target, including its sealed credentials.
ListTargets(ctx context.Context) ([]targets.Target, error)
// UpdateTarget stores a target; a non-nil WrappedCreds replaces the sealed
// credentials.
UpdateTarget(ctx context.Context, t targets.Target) (targets.Target, error)
}
TargetStore is the part of targets.Store a credential rotation needs.