Documentation
¶
Overview ¶
Package rotate rotates key-encryption keys: it creates and activates the next version, re-wraps the data keys that still sit under the old one, rolls back, and says when a version may be retired.
Rotation never touches stored application data. A data key is wrapped by a KEK version, and rotating moves the wrapping; the ciphertext of the data itself, which names only its data key, is unchanged.
Index ¶
- Constants
- Variables
- type Backup
- type Due
- type Eligibility
- type EmergencyKEKOptions
- type EmergencyKEKResult
- type EpochAdvancer
- type ExposedDEK
- type Options
- type PendingVersion
- type RetireResult
- type RewrapAllResult
- type RewrapFailure
- type RewrapOptions
- type RewrapResult
- type Rotation
- type Rotator
- func (r *Rotator) CancelRetirement(ctx context.Context, kekID envelope.ID, version uint32) (RetireResult, error)
- func (r *Rotator) CompleteRetirement(ctx context.Context, kekID envelope.ID, version uint32) (RetireResult, error)
- func (r *Rotator) CompleteShreds(ctx context.Context) ([]registry.ShredRecord, error)
- func (r *Rotator) CompromiseDEK(ctx context.Context, id envelope.ID) (ExposedDEK, error)
- func (r *Rotator) CompromiseKEK(ctx context.Context, kekID envelope.ID, version uint32, ...) (EmergencyKEKResult, error)
- func (r *Rotator) Destroy(ctx context.Context, kekID envelope.ID, version uint32) (RetireResult, error)
- func (r *Rotator) DestroyDEK(ctx context.Context, id envelope.ID) (RetireResult, error)
- func (r *Rotator) Due(ctx context.Context, purpose keyforge.Purpose) (Due, error)
- func (r *Rotator) Eligibility(ctx context.Context, kekID envelope.ID, version uint32) (Eligibility, error)
- func (r *Rotator) EnsureKEK(ctx context.Context, purpose keyforge.Purpose, providerName string) (registry.KEK, error)
- func (r *Rotator) RequestRetirement(ctx context.Context, kekID envelope.ID, version uint32) (RetireResult, error)
- func (r *Rotator) RequestShred(ctx context.Context, purpose keyforge.Purpose, subject string) (registry.ShredRecord, error)
- func (r *Rotator) Rewrap(ctx context.Context, kekID envelope.ID, from uint32, opts RewrapOptions) (RewrapResult, error)
- func (r *Rotator) RewrapAll(ctx context.Context, kekID envelope.ID, opts RewrapOptions) (RewrapAllResult, error)
- func (r *Rotator) Rollback(ctx context.Context, purpose keyforge.Purpose) (Rotation, error)
- func (r *Rotator) RotateKEK(ctx context.Context, purpose keyforge.Purpose) (Rotation, error)
- func (r *Rotator) ShredStatus(ctx context.Context, purpose keyforge.Purpose, subject string) (ShredStatus, error)
- type ShredStatus
Constants ¶
const ( SentinelPlaintext = "keyforge-sentinel-v1" SentinelContext = "keyforge/sentinel/v1" )
SentinelPlaintext and SentinelContext are the fixed, non-secret values a sentinel envelope is sealed with. The recovery drill decrypts the sentinel of every retained version and compares the result with SentinelPlaintext.
const DefaultBatchSize = registry.DefaultPageSize
DefaultBatchSize is how many data keys one re-wrap batch handles.
Variables ¶
var ( // ErrBackupStale is returned when policy requires a verified backup and the // keyring's backup has not been acknowledged as current. Rotation waits. ErrBackupStale = errors.New("rotate: the keyring backup is not acknowledged as current") // ErrBackupUnknown is returned when policy requires a verified backup and // the provider has no way to say whether it has one. Rotation fails closed. ErrBackupUnknown = errors.New("rotate: cannot tell whether the keyring is backed up") // ErrNothingToRewrap is returned when the from version is the active one. ErrNothingToRewrap = errors.New("rotate: the version to re-wrap from is the active version") )
Typed errors.
var ErrEmergencyIncomplete = errors.New("rotate: the emergency rotation is not complete")
ErrEmergencyIncomplete is returned when an emergency rotation could not finish every step: data keys remain under the compromised version, so it was neither retired nor destroyed. The procedure is idempotent; call it again.
var ErrNoEpochAdvancer = errors.New("rotate: no epoch advancer is configured")
ErrNoEpochAdvancer is returned when replacing an exposed data key needs a way to create the next epoch and none was configured.
var ErrNotEligible = errors.New("rotate: the version is not eligible for retirement")
ErrNotEligible is returned when a version cannot be put forward for retirement. The error lists why.
var ErrWrongState = errors.New("rotate: the version is not in the state this operation needs")
ErrWrongState is returned when an operation needs a version in a state it is not in.
Functions ¶
This section is empty.
Types ¶
type Backup ¶
Backup reports whether a provider's keyring has a backup acknowledged as current. The local provider implements it.
type Due ¶
type Due struct {
Purpose keyforge.Purpose
KEK registry.KEK
Version uint32
ActivatedAt time.Time
DueAt time.Time
Overdue bool
}
Due describes when a purpose's KEK is next due for rotation.
type Eligibility ¶
type Eligibility struct {
// Eligible is true when nothing here stops retirement.
Eligible bool
// State is the version's state.
State registry.VersionState
// References is how many data keys in the registry are still wrapped under
// the version.
References int
// InFloor is true when the retained-versions floor holds the version.
InFloor bool
// Reasons lists what stops retirement, in words that name no key material.
Reasons []string
}
Eligibility says whether a KEK version may be retired now, and if not why.
type EmergencyKEKOptions ¶
type EmergencyKEKOptions struct {
// KeepMaterial stops after the version is retired and leaves its material at
// the provider, so that data outside the registry (inline envelopes,
// exports, snapshots) can be recovered and re-encrypted before the material
// goes. The default destroys it, which is the procedure's last step.
KeepMaterial bool
// Rewrap tunes the re-wrap of the data keys.
Rewrap RewrapOptions
}
EmergencyKEKOptions tunes the response to a compromised KEK version.
type EmergencyKEKResult ¶
type EmergencyKEKResult struct {
// Replacement is the rotation that moved writes off the version, or the
// zero value if another version was already active.
Replacement Rotation
// Rewrap is the re-wrap of the data keys that were under it.
Rewrap RewrapResult
// Remaining is how many data keys in the registry are still wrapped under
// the compromised version.
Remaining int
// Guard is what the guard answered. It was asked and recorded, and did not
// decide: it cannot block this procedure.
Guard guard.Decision
// Retired and Destroyed say how far the procedure got.
Retired bool
Destroyed bool
}
EmergencyKEKResult reports a response to a compromised KEK version.
type EpochAdvancer ¶
type EpochAdvancer interface {
AdvanceEpoch(ctx context.Context, purpose keyforge.Purpose, subject string) (keyforge.DEKRecord, error)
}
EpochAdvancer creates the next data key epoch for a purpose and subject.
type ExposedDEK ¶
type ExposedDEK struct {
// Exposed is the data key that was marked compromised.
Exposed keyforge.DEKRecord
// Replacement is the epoch that writes use now, or the zero value if the
// exposed key was not the current one.
Replacement keyforge.DEKRecord
// EncryptionsUpperBound is the most encryptions that can have been made
// under the exposed key: the registry's reserved high-water mark. It bounds
// how much data is affected, and it counts blocks reserved by processes that
// may not have used them, so it can overstate.
EncryptionsUpperBound uint64
}
ExposedDEK reports the response to an exposed data key.
type Options ¶
type Options struct {
// Store is the registry. Required.
Store registry.Store
// Providers hold the KEKs, matched to a key by name. At least one is
// required.
Providers []provider.Provider
// Policy is the rotation and retirement policy. The zero value is the
// default policy for every purpose.
Policy policy.Set
// Backups reports backup state by provider name, for purposes whose policy
// requires a verified backup.
Backups map[string]Backup
// Clock defaults to the system clock.
Clock keyforge.Clock
// Audit receives lifecycle events. The default discards them.
Audit audit.Sink
// Guard is consulted before a version is retired and again before it is
// destroyed. With none configured nothing is retired or destroyed: the
// absence of a veto is not consent.
Guard guard.Guard
// Metrics receives rotation and re-wrap counts. The default discards them.
Metrics metrics.Metrics
// Epochs creates a new data key epoch on demand, which replacing an
// exposed data key needs. A [keyforge.Client] satisfies it.
Epochs EpochAdvancer
// Entropy supplies sentinel keys and event identifiers. The default is
// crypto/rand.
Entropy io.Reader
}
Options configures a Rotator.
type PendingVersion ¶
type PendingVersion struct {
Version keyforge.KEKVersionRef
State registry.VersionState
// ExpectedBy is when the version is expected to be destroyed if rotation
// and retirement proceed on schedule, under the purpose's policy.
ExpectedBy time.Time
}
PendingVersion is a KEK version that must be destroyed before a shred is complete.
type RetireResult ¶
type RetireResult struct {
// Outcome is the guard's answer. Allowed means the step happened.
Outcome guard.Outcome
// Reason is the guard's reason when it did not allow.
Reason string
// State is the version's state afterwards.
State registry.VersionState
}
RetireResult is the outcome of a retirement or destruction step.
type RewrapAllResult ¶
type RewrapAllResult struct {
// Versions is each version that was drained and how it went.
Versions map[uint32]RewrapResult
// Done is true when no readable version other than the active one is left
// holding a data key.
Done bool
}
RewrapAllResult reports a drain of every version of a KEK that still holds data keys.
type RewrapFailure ¶
RewrapFailure describes a data key that could not be moved.
type RewrapOptions ¶
type RewrapOptions struct {
// BatchSize is how many records to read and move at a time. Zero means
// [DefaultBatchSize].
BatchSize int
// After resumes after a data key identifier, from a previous result's
// Cursor. Because a moved record no longer matches the filter, restarting
// from the beginning is also correct and only does extra reading.
After envelope.ID
// Progress, if set, is called after every batch.
Progress func(RewrapResult)
}
RewrapOptions tunes a re-wrap run.
type RewrapResult ¶
type RewrapResult struct {
// Rewrapped is how many data keys were moved to the new version.
Rewrapped int
// Skipped is how many were already moved, by an earlier run or another
// worker, or had been destroyed.
Skipped int
// FailedCount is how many could not be moved. Failed lists the first few.
FailedCount int
Failed []RewrapFailure
// Cursor is the last identifier handled, to resume from.
Cursor envelope.ID
// Done is true when every data key under the old version has been moved.
// It is false when any failed, so a caller does not retire a version that
// is still referenced.
Done bool
}
RewrapResult reports a re-wrap run.
type Rotation ¶
type Rotation struct {
KEK registry.KEK
From uint32 // the version that was active, or zero if none was
To uint32 // the version that is active now
At time.Time
}
Rotation is the outcome of Rotator.RotateKEK.
type Rotator ¶
type Rotator struct {
// contains filtered or unexported fields
}
Rotator performs KEK rotation. It is safe for concurrent use; the registry's atomic operations are what keep concurrent rotators correct.
func (*Rotator) CancelRetirement ¶
func (r *Rotator) CancelRetirement(ctx context.Context, kekID envelope.ID, version uint32) (RetireResult, error)
CancelRetirement returns a retire-requested version to decrypt-only before its delay has run, for an operator who has changed their mind.
func (*Rotator) CompleteRetirement ¶
func (r *Rotator) CompleteRetirement(ctx context.Context, kekID envelope.ID, version uint32) (RetireResult, error)
CompleteRetirement finishes a retirement whose delay has run: it checks the delay and that nothing in the registry still references the version, asks the guard, and only on an allow moves the version to retired.
A denial returns the version to decrypt-only, usable, with the denial recorded; a version whose retirement is denied can be requested again later. A pending answer leaves it retire-requested and the guard is asked again on the next call. A guard that fails, or is absent, advances nothing.
func (*Rotator) CompleteShreds ¶
CompleteShreds records completion for every shred whose KEK versions have all been destroyed, and returns those it completed. It is safe to call repeatedly: a scheduler calls it after each destruction.
func (*Rotator) CompromiseDEK ¶
CompromiseDEK responds to a data key that is known or suspected to be exposed.
If it is the current epoch, the next epoch is created first so that writes move off it at once, then the key is marked compromised. It stays readable: the host re-encrypts what it protects, with keyforge.Client.Reencrypt as it finds each record or in a pass of its own, and keyforge cannot do that for it, since the data is the application's. Rotator.DestroyDEK removes the key when the host is done.
The report gives an upper bound on how much was encrypted under the key, which is all the registry can know; which records are affected is the host's to find, by the data key identifier their envelopes carry.
func (*Rotator) CompromiseKEK ¶
func (r *Rotator) CompromiseKEK(ctx context.Context, kekID envelope.ID, version uint32, opts EmergencyKEKOptions) (EmergencyKEKResult, error)
CompromiseKEK responds to a KEK version that is known or suspected to be exposed.
It replaces the version so that nothing new is wrapped under it, marks it compromised, re-wraps every data key away from it, then retires and destroys it. It differs from a scheduled rotation in three ways: it does not wait for a backup to be acknowledged, it has no retirement delay, and the guard is asked and its answer recorded but it cannot block.
It is idempotent and resumable. If any data key cannot be moved the version is left compromised and referenced, not retired, and the call returns ErrEmergencyIncomplete with the progress; calling it again continues.
Data outside the registry that is wrapped under the version is not covered by the re-wrap and is lost when the version is destroyed: set KeepMaterial to stop short of that, recover what is needed, and call Destroy.
func (*Rotator) Destroy ¶
func (r *Rotator) Destroy(ctx context.Context, kekID envelope.ID, version uint32) (RetireResult, error)
Destroy destroys a retired version's key material at the provider and records it. The guard is asked again, because retirement and destruction are separate decisions and the facts can change between them.
Destruction is irreversible. The order is: the guard allows, the provider destroys, then the registry records it. Destroying at the provider is idempotent, so a crash between the last two is repaired by calling Destroy again; the reverse order could record a destruction that did not happen.
func (*Rotator) DestroyDEK ¶
DestroyDEK removes an exposed or superseded data key once the host has re-encrypted what it protects. It asks the guard, and only an allow destroys the wrapped key; the record remains as the trail of what existed.
func (*Rotator) Due ¶
Due reports when the purpose's active KEK version is due for rotation. The interval is measured from when the version was last activated.
func (*Rotator) Eligibility ¶
func (r *Rotator) Eligibility(ctx context.Context, kekID envelope.ID, version uint32) (Eligibility, error)
Eligibility reports whether a version may be retired.
A version is eligible only when all of these hold: it is decrypt-only, or compromised, since an active or pending version is not retired; it is outside the retained-versions floor; and no data key in the registry is wrapped under it. The floor is a minimum on retention and never a trigger to delete: the active version and the most recent decrypt-only ones, up to the policy's count, are kept even when nothing references them, so a bad rotation can be rolled back with nothing to recover.
Data outside the registry (an inline envelope, an export, a snapshot, a backup) is not counted. A version that protects such data is held until the host asserts through the guard that none remains or accepts its loss.
func (*Rotator) EnsureKEK ¶
func (r *Rotator) EnsureKEK(ctx context.Context, purpose keyforge.Purpose, providerName string) (registry.KEK, error)
EnsureKEK makes sure a purpose has an active KEK, creating it at the named provider when it has none. It is how a deployment starts: call it once for each purpose at startup, and it does nothing after the first time.
It resumes. A KEK registered without an active version, or with only a pending one, is completed instead of replaced. If two processes start at once, the registry admits one KEK for the purpose and the other adopts it; the key the loser created at the provider is left unused, and can be destroyed at the provider.
A purpose already served by a KEK at a different provider is an error: moving a purpose between providers is a deliberate migration, not a startup side effect.
A failure to record an audit event is returned as audit.ErrNotRecorded alongside a valid KEK.
func (*Rotator) RequestRetirement ¶
func (r *Rotator) RequestRetirement(ctx context.Context, kekID envelope.ID, version uint32) (RetireResult, error)
RequestRetirement starts the retirement of a version: it moves it from decrypt-only to retire-requested and starts the delay, whose length is the purpose's policy. The version stays usable throughout.
The version must be outside the retained-versions floor. It need not be unreferenced yet: the re-wrap can finish during the delay, and completion checks. The guard is consulted at completion, not here.
func (*Rotator) RequestShred ¶
func (r *Rotator) RequestShred(ctx context.Context, purpose keyforge.Purpose, subject string) (registry.ShredRecord, error)
RequestShred erases a subject: it destroys every data key the subject has under the purpose, which makes its data unrecoverable from the live registry at once, and records which KEK versions have to be destroyed before the erasure is complete. A shredded subject stays shredded: a new key for it is refused.
The erasure is not complete when this returns. Registry backups still hold the subject's keys wrapped under the KEK versions that were active while the keys existed, and while any of those versions survives, a restored backup can unwrap them. Rotator.ShredStatus reports what is outstanding and when it is expected, and Rotator.CompleteShreds records completion once the last version is destroyed.
Clients cache unwrapped keys, so a process that has the subject's key cached keeps using it until its entry expires or keyforge.Client.Purge is called. A host that needs the erasure to take effect at once purges its clients.
func (*Rotator) Rewrap ¶
func (r *Rotator) Rewrap(ctx context.Context, kekID envelope.ID, from uint32, opts RewrapOptions) (RewrapResult, error)
Rewrap moves every data key wrapped under version from of a KEK to the KEK's active version, in batches.
It unwraps each key with the old version and wraps it again under the new one with the same additional data, then replaces the record with a compare-and-set on the version it was read under. The step is idempotent, so a run that is interrupted, or two that overlap, leave nothing half done: it can be started again at any point. Stored application data is not touched, because it names only its data key.
A key that cannot be moved does not stop the run; it is counted, reported, and leaves Done false. A context that ends returns the progress made so far with the context's error.
func (*Rotator) RewrapAll ¶
func (r *Rotator) RewrapAll(ctx context.Context, kekID envelope.ID, opts RewrapOptions) (RewrapAllResult, error)
RewrapAll moves every data key off every readable version of a KEK that is not the active one. It is Rotator.Rewrap for each such version, and it is self-contained: it needs only the KEK, so a scheduled job can run it without being told which version to drain.
Compromised versions are left to the emergency procedure, which a person starts, and a KEK with no active version is not drained at all.
func (*Rotator) Rollback ¶
Rollback reactivates the most recent decrypt-only version of the purpose's KEK, demoting the active one in the same transaction. It is for a version that turns out to be bad (a provider fault, a wrong configuration): nothing needs recovering, because the earlier version was kept. Data keys already wrapped under the demoted version still unwrap under it.
func (*Rotator) RotateKEK ¶
RotateKEK creates the next version at the provider, registers and activates it, and records a sentinel for it. The previous version becomes decrypt-only in the same registry transaction, and stays usable. Data keys wrapped under it are moved by Rotator.Rewrap, which the caller starts straight away so the old version drains.
It resumes: a version left pending by an interrupted rotation is activated instead of creating another.
A failure to record an audit event is returned as audit.ErrNotRecorded alongside a valid Rotation.
func (*Rotator) ShredStatus ¶
func (r *Rotator) ShredStatus(ctx context.Context, purpose keyforge.Purpose, subject string) (ShredStatus, error)
ShredStatus reports what is outstanding for a subject's erasure and when it is expected to finish.
The estimate follows from policy and assumes rotation proceeds on schedule. A version stays until it is outside the retained-versions floor, which takes that many further rotations at the KEK interval, and then waits out the retirement delay. With the defaults (a 365-day interval, two retained versions, a 30-day delay) that is more than two years for a version that is active today, which is why a purpose that needs a faster erasure guarantee sets a shorter KEK interval.
type ShredStatus ¶
type ShredStatus struct {
Record registry.ShredRecord
// Complete is true once every version has been destroyed and the shred
// recorded as complete.
Complete bool
// Pending lists the versions still to be destroyed.
Pending []PendingVersion
// ExpectedBy is when the last pending version is expected to be destroyed,
// or the zero value if the shred is complete.
ExpectedBy time.Time
}
ShredStatus reports a shred.