Skip to main content
Version: v5.7.0

Encryption key lifecycle status

Included from Basic tier

The payloads of the v2 SystemService encryption key lifecycle verbs (admin-gated; opschain admin encryption status / reencrypt):

  • GetEncryptionStatus — the census: the configured key ring, the key registry (EncryptionKeyRecord), one EncryptedColumnStatus per encrypted-at-rest column with its EncryptionKeyCount rows, what is pending, the referenced keys this server lacks, and the last EncryptionReencryptRun.
  • ReencryptAtRest — the re-encryption run, answering its EncryptionReencryptRun with one EncryptedColumnOutcome per column touched.

A key id, wherever it appears below, is the leading 64 bits of SHA-256 over the derived AES key (16 hex characters): it names the (primary key, salt) pair without revealing either. No payload here ever carries a key or a decrypted value — counts and ids only.

The v2 OpenAPI document declares these schemas; the resource-field coverage gate holds this page to them.

Status payload (GetEncryptionStatus)​

FieldTypeMeaning
backendstringpostgres or sqlite.
current_key_idstringThe key every write seals under (OPSCHAIN_PRIMARY_KEY + OPSCHAIN_KEY_DERIVATION_SALT).
configured_key_idsarrayThe ring this server holds: the current key first, then OPSCHAIN_PREVIOUS_KEYS in order.
keysarrayThe key registry, EncryptionKeyRecord entries below, oldest first.
columnsarrayOne EncryptedColumnStatus per encrypted-at-rest column, in inventory order.
pendingintEnvelopes not sealed under the current key (legacy envelopes included) — what a re-encryption run would rewrite.
missing_key_idsarrayKey ids referenced by stored envelopes, or registered and not retired, that this server's ring lacks. The CLI exits 1 while any exist; re-encryption refuses.
last_runobjectThe most recent EncryptionReencryptRun. Absent when no run has happened.

keys entries (EncryptionKeyRecord)​

FieldTypeMeaning
key_idstringThe key id.
first_seen_attimestampWhen a server first registered the key as its current key against this database.
last_seen_attimestampThe last boot that registered it as current.
retired_attimestampWhen a re-encryption run verified nothing references the key any more. Absent while the key is active; a key re-introduced as current is un-retired.
retired_bystringThe id of the re-encryption run that retired it ("" while active).
currentboolTrue for this server's current key.
configuredboolTrue when this server's ring carries the key (current or previous).

columns entries (EncryptedColumnStatus)​

FieldTypeMeaning
tablestringThe table.
columnstringThe column.
envelopesintEncryption envelopes in the column; plaintext rows are not counted.
currentintEnvelopes sealed under the current key.
legacyintEnvelopes with no key reference — written before key references existed, or by the Rails application; decrypted by trying each configured key.
pendingintEnvelopes a re-encryption run would rewrite (everything not under the current key).
by_keyarrayCounts per referenced key id, EncryptionKeyCount entries below.

by_key entries (EncryptionKeyCount)​

FieldTypeMeaning
key_idstringThe referenced key id.
envelopesintEnvelopes referencing it in this column.
configuredboolFalse when this server's ring lacks the key — the column cannot be re-encrypted until it is listed in OPSCHAIN_PREVIOUS_KEYS.

Run payload (ReencryptAtRest, and last_run above — EncryptionReencryptRun)​

FieldTypeMeaning
idstringThe run id (the progress row's key; cited by retired_by).
target_key_idstringThe key the run seals everything under — the current key when it opened.
statusstringrunning, partial, completed, completed_with_failures or failed — vocabulary below.
started_attimestampWhen the run opened.
updated_attimestampThe last batch's commit (the progress row is updated after every batch).
finished_attimestampWhen a full pass completed. Absent while partial, failed or running.
convertedintEnvelopes rewritten under the target key, across every invocation of the run.
failedintEnvelopes the latest pass could not decrypt under any configured key — the rows still pending, not a running sum.
skippedintRows that changed under the run between read and write and were left for the next pass.
resumedintHow many invocations resumed the run after a partial or failed stop.
columnsarrayOne EncryptedColumnOutcome per column the run touched, below.
retired_key_idsarrayThe keys the run retired on its clean completion ([] otherwise).
errorstringThe statement error that stopped a failed run. Absent otherwise.

columns entries (EncryptedColumnOutcome)​

FieldTypeMeaning
tablestringThe table.
columnstringThe column.
convertedintEnvelopes rewritten in this column.
failedintEnvelopes in this column the latest pass could not decrypt.
skippedintRows in this column that changed under the run.

Vocabularies (verbatim)​

  • Run statuses: running (a batch loop holds it), partial (a row limit stopped it; the next call resumes), completed (a full pass found nothing left; superseded keys retired), completed_with_failures (failed rows remain; no key is retired), failed (a statement error; the next call resumes).
  • Events: every ReencryptAtRest call records api:encryption:reencrypt with the run id, target key id, status, counts and retired key ids.
  • Refusals (failed_precondition, named): no encryption keys configured; an envelope references a key this server lacks (<table>.<column> holds N envelope(s) under key <id>); a run is already in progress elsewhere in the estate.

What changes this​

OPSCHAIN_PRIMARY_KEY / OPSCHAIN_KEY_DERIVATION_SALT set the current key; OPSCHAIN_PREVIOUS_KEYS adds the rotated-out keys the server still decrypts; every boot registers the current key and refuses when a registered, unretired key is missing from the ring (opschain docs storage, "Key references and rotation").

  • opschain docs cli/admin-encryption — the verbs.
  • opschain docs storage — the keyset and the boot gate.