\Drupal\strata\Crypto KeyRotation

Measures how much of the store still needs a retired key, and re-seals it a batch at a time.

A rotation is not an event, it is a state the store is in until every frame has been re-sealed. This is what reports on that state and what advances it.

Re-sealing is incremental and bounded, and it is safe to stop at any point. A frame is read, opened with whichever key works, sealed with the active key and written back to the same address - the content address is over the DECODED bytes, so re-sealing does not move a frame and nothing that references it has to be rewritten. A pass that runs out of budget leaves a consistent store where some frames are new and some are old, which is the same state it started in.

A rotation is only finished when nothing needs a retired key. Until then the retired keys must stay configured; removing one makes every frame still sealed under it unreadable, and nothing can tell that apart from corruption afterwards. isComplete() is the only safe signal for removing a key, and the report says so.

Summary

Methods
Properties
Constants
__construct
measure
run
checkSegment
No public properties found
DEFAULT_BUDGET
SAMPLE
No protected methods found
No protected properties found
No protected constants found
frames
fingerprintOf
read
resealAll
cipher
store
recompressor
index
ledger
logger
No private constants found

Constant

DEFAULT_BUDGET

DEFAULT_BUDGET = 500

Frames one pass re-seals when no budget is given.

Each one is a read, an open, a seal and a write, so the budget is a request budget as much as a time one.

SAMPLE

SAMPLE = 200

Frames one measurement samples.

A measurement is a report, not a pass, so it reads a sample rather than the whole index; the share it reports is an estimate and is labelled as one.

Properties

$cipher

$cipher : \Drupal\strata\Crypto\RotatingCipher

Type

RotatingCipher

$store

$store : \Drupal\strata\Cas\ObjectStore

Type

ObjectStore

$recompressor

$recompressor : \Drupal\strata\Compaction\Recompressor

Type

Recompressor

$logger

$logger : \Psr\Log\LoggerInterface

Type

LoggerInterface

Methods

__construct()

__construct(\Drupal\strata\Crypto\RotatingCipher  $cipher, \Drupal\strata\Cas\ObjectStore  $store, \Drupal\strata\Compaction\Recompressor  $recompressor, \Drupal\strata\Cas\FrameIndexInterface  $index, \Drupal\strata\Health\HealthLedgerInterface  $ledger, \Psr\Log\LoggerInterface  $logger) : mixed

Constructs a rotation.

Parameters

\Drupal\strata\Crypto\RotatingCipher $cipher

Opens a frame with whichever key works and reports which one that was.

\Drupal\strata\Cas\ObjectStore $store

Reads the frames.

\Drupal\strata\Compaction\Recompressor $recompressor

Rewrites an object under the active key. A packed frame is re-sealed with its pack, which is the rule compaction already follows and the reason a frame is not rewritten here.

\Drupal\strata\Cas\FrameIndexInterface $index

Enumerates the frames to examine.

\Drupal\strata\Health\HealthLedgerInterface $ledger

Where a mid-flush rotation is recorded.

\Psr\Log\LoggerInterface $logger

Records what a pass did.

Returns

mixed —

measure()

measure(int  $sample = \self::SAMPLE) : array{rotating: bool, keys: int, sampled: int, stale: int, unreadable: int, complete: bool, fingerprints: list<string>}

How much of the store still needs a retired key.

Parameters

int $sample

Frames to examine.

Returns

array{rotating: bool, keys: int, sampled: int, stale: int, unreadable: int, complete: bool, fingerprints: list} —

Whether a rotation is in progress, how many keys are on the ring, what the sample found, and whether it is safe to remove the retired keys.

run()

run(int  $budget = \self::DEFAULT_BUDGET) : array{examined: int, resealed: int, skipped: int, unreadable: int, problems: list<string>}

Re-seals frames that still open under a retired key.

Parameters

int $budget

Frames to select at most. A packed frame is re-sealed with its whole pack, so a pass can re-seal more frames than the budget names: the budget bounds what is read and examined, and one object is the smallest unit that can be re-sealed.

Returns

array{examined: int, resealed: int, skipped: int, unreadable: int, problems: list} —

What the pass did.

checkSegment()

checkSegment(string  $segment, list  $frames) : bool

Records a finding when one segment's frames were sealed under more than one key.

Parameters

string $segment

The segment key.

list $frames

The frame addresses the segment references.

Returns

bool —

TRUE when a finding was recorded.

frames()

frames(int  $limit) : list<\Drupal\strata\Cas\FrameRecord>

The frames to examine, oldest first.

Every frame, referenced or not. A rotation is not a reachability question: a frame nothing points at yet is still sealed under a key, and a frame the tree does point at is exactly the one that must not stop opening.

Parameters

int $limit

Most frames to return.

Returns

list<\Drupal\strata\Cas\FrameRecord> —

Frame records.

fingerprintOf()

fingerprintOf(string  $hash) : string|null

The fingerprint of the key that opens one frame.

Parameters

string $hash

The frame address.

Returns

string|null —

The fingerprint, or NULL when no key on the ring opened it.

read()

read(string  $hash, list  $problems) : string|null

Reads one frame, recording rather than throwing when it will not open.

Parameters

string $hash

The frame address.

list $problems

Problems collected so far, added to by reference.

Returns

string|null —

The decoded bytes, or NULL when the frame could not be read.

resealAll()

resealAll(list<\Drupal\strata\Cas\FrameRecord>  $records, list  $problems) : int

Writes a set of frames back under the active key.

A frame cannot be re-sealed by writing its bytes again: the store deduplicates on the content address, so a second write of the same bytes finds the frame already present and only takes a reference. The object itself has to be rewritten, which is what the recompressor does, and a packed frame is rewritten with its whole pack rather than pulled out of it.

The address never moves, so nothing that names a frame has to be rewritten. A pack that is rewritten leaves its predecessor behind for the prune, exactly as recompaction does.

Parameters

list<\Drupal\strata\Cas\FrameRecord> $records

The frames that still open under a retired key.

list $problems

Problems collected so far, added to by reference.

Returns

int —

How many frames were re-sealed. A pack counts every frame it carried forward, because all of them are sealed under the active key afterwards.