rotate

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Oct 21, 2026 License: Apache-2.0 Imports: 6 Imported by: 0

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

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

func SRW(ctx context.Context, store EnvelopeStore, oldKey, newKey []byte) (Result, error)

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

func TW(ctx context.Context, store TargetStore, oldKey, newKey []byte) (Result, error)

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.

Jump to

Keyboard shortcuts

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