vault
vault
¶
Vault passphrase CLI verbs — session unlock / lock plus passphrase management.
The unlock/lock pair drives the session-tier slot of the SQLCipher
passphrase resolution chain: unlock lands a passphrase on the
session-unlock tmpfs file; lock removes it. Everything else lives
under vault passphrase:
vault passphrase sealpromotes the current passphrase into a machine-boundsystemd-credscredential.vault passphrase to-keyringmoves it from whichever tier holds it now into the OS keyring (the recommended upgrade path off the session-file / plaintext-config tiers).vault passphrase revealresolves and prints the current passphrase (to/dev/ttyby default, or stdout with--allow-redirect) and offers to mark the recovery key as saved.vault passphrase acknowledgemarks the current passphrase as saved without displaying it — the silent ack a TUI / CI captures.vault lockclears every stored copy of the passphrase — session file, keyring, sealed systemd-creds credential, and plaintext config — so the vault becomes irrecoverable without an off-host copy. The machine-bound tiers are an automatic-unlock convenience on top of a passphrase the operator is expected to have saved; locking peels them away.purge_passphrase_tiersis the prompt-free corelockand panic share.
Each container mounts its own short-lived
VaultProxy
that resolves the passphrase on demand. vault unlock / vault
lock therefore only manage the passphrase tier; a supervisor that's
already running keeps the passphrase it resolved at spawn, so picking
up a changed tier means starting a fresh task (delete the matching
one — per the no-state-preservation rule).
VAULT_COMMANDS = (CommandDef(name='vault', help='Vault passphrase management', children=(CommandDef(name='status', help='Show lock state, the passphrase resolution chain, and stored secrets', handler=_handle_vault_status, args=(ArgDef(name='--json', dest='as_json', action='store_true', help='Machine-readable JSON output'),)), CommandDef(name='unlock', help='Provision the credentials-DB passphrase for this session (tmpfs file)', handler=_handle_vault_unlock, args=(ArgDef(name='--force', action='store_true', help='Write the session file even if a durable tier already unlocks the vault'),)), CommandDef(name='lock', help="Clear every stored copy of the passphrase — you'll need it to unlock again", handler=_handle_vault_lock, args=(ArgDef(name='--force', action='store_true', help="Skip the 'have you saved the passphrase?' confirmation"),)), CommandDef(name='list', help='Inventory stored credentials (and optionally proxy tokens)', handler=_handle_vault_list, args=(ArgDef(name='--include-tokens', action='store_true', help='Also show proxy-token rows (token values are masked)'), ArgDef(name='--json', dest='as_json', action='store_true', help='Machine-readable JSON output'))), _PASSPHRASE_GROUP)),)
module-attribute
¶
__all__ = ['VAULT_COMMANDS']
module-attribute
¶
SessionProvisionResult(written, validated=False, shadowed_durable=None)
dataclass
¶
Outcome of provision_session_passphrase.
written is the load-bearing bit: False means the write was
refused because a durable tier already resolves the vault, so a
session file would only shadow it (shadowed_durable names that
tier). Refusal is a normal outcome, not an error — callers report
it ("already unlocked via X") rather than raising. validated
says whether the written value was test-opened against an existing
DB (vs. an empty install where it becomes the key on first use).
SessionShadow(durable_source, redundant)
dataclass
¶
A session-file tier sitting on top of a durable tier.
redundant is the actionable bit:
True— the session copy is byte-identical to what the durable tier resolves to, so it's pure residue (a pastunlockon a box that already auto-unlocks). Safe to delete; nothing is lost.False— the two differ. Either a deliberate re-key in progress or a stale unlock masking the durable value; never auto-removed.None— the durable tier is present but couldn't be read to compare (broken seal, dead helper), so the session file may be doing real work. Left alone.
provision_session_passphrase(cfg, passphrase, *, force=False)
¶
Validate passphrase against the DB, then land it on the session tier.
The single writer of the session-unlock tmpfs file — the CLI
vault unlock and terok's TUI unlock modal both funnel through
here, so the no-shadow and validation guards apply to every caller
by construction; neither can store a value the DB rejects, nor
silently shadow a working durable tier.
Two guards, in order:
- No-shadow. The session tier is the highest-priority slot, so
writing it masks any durable tier (systemd-creds / keyring /
config) underneath — a reboot then wipes the session copy and the
operator only thought the sealed key was in use. When a durable
tier already resolves and force is false, nothing is written and
the result reports
written=False+ the shadowed tier. force (re-key / deliberate override) skips this guard. - Validation. When the DB exists (and isn't a legacy plaintext
file) the value is test-opened first; a mismatch raises
WrongPassphraseErrorand nothing is written. A missing DB skips validation (opening it just to check would create it as a side effect) — the value becomes its key on first use.
Source code in src/terok_sandbox/commands/vault.py
session_shadow_state(cfg)
¶
Describe a session-file-over-durable-tier shadow, or None if there is none.
Returns None in the common cases — no session file, or no durable
tier beneath it — without resolving anything. Only when both are
present does it resolve the durable chain (omitting the session
file) to compare values; that one comparison is the only place
status / remediation pay an unseal. The session secret never leaves
the process — this reads two tiers and compares, exactly what the
resolver already does internally.
Source code in src/terok_sandbox/commands/vault.py
clear_redundant_session_file(cfg)
¶
Remove the session-unlock file iff it merely duplicates a durable tier.
Same-key residue only: a session file whose passphrase differs from
the durable tier is a deliberate override (or a re-key mid-flight)
and is kept. Re-verifies state at call time rather than trusting a
caller's stale read. Returns the durable tier it deduplicated
against, or None when there was nothing safe to remove.
Source code in src/terok_sandbox/commands/vault.py
purge_passphrase_tiers(cfg)
¶
Remove every stored copy of the credentials-DB passphrase.
Clears the session-unlock tmpfs file, the OS keyring entry, the
sealed systemd-creds credential, and the plaintext
credentials.passphrase / credentials.passphrase_command
wiring in config.yml — then drops the recovery-acknowledged
marker, since it's meaningless once no tier remains. After this the
vault can only be reopened by re-supplying the passphrase
(vault unlock); it is unrecoverable without an off-host copy.
No prompts and no acknowledgement check: this is the raw destructive
action. The lock verb gates it behind a typed-SAVED
confirmation when recovery is unacknowledged; panic calls it
directly — no questions asked.
Source code in src/terok_sandbox/commands/vault.py
handle_vault_seal(*, cfg=None, key='auto')
¶
Seal the credentials-DB passphrase into a systemd-creds credential.
Adds the systemd-creds tier to the resolution chain: machine-bound
(TPM2 + host key, or either alone), survives reboot, no OS
keyring required. After sealing, every new supervisor resolves the
passphrase via systemd-creds decrypt on start — no operator
interaction needed at boot, no plaintext-on-disk.
Requires an already-resolvable passphrase — typically from a fresh
vault unlock in the current session.
Source code in src/terok_sandbox/commands/vault.py
handle_vault_to_keyring(*, cfg=None)
¶
Move the current passphrase from its current tier into the OS keyring.
Resolves the passphrase via the chain (or prompts as a last resort),
writes it to the keyring, flips credentials.use_keyring to true
in config.yml, clears any plaintext credentials.passphrase /
credentials.passphrase_command wiring, and removes the
session-file and sealed systemd-creds copies.
The validate-before-destroy ordering is deliberate: if the keyring write fails, the source tier is still intact.