docs(custody): every credential says what it is
Five custom_metadata fields on every path in operators/ and platform/workloads/: description, owner, used_by, rotation, on_loss. Non-secret, so anyone orienting themselves can read all of them without being granted a value. on_loss is the field the section exists for. When the LLDAP admin predecessor was destroyed on 2026-08-27 the answer had to be reconstructed from first principles under pressure; written down in advance it is one line. used_by is the second lesson: rotation without a consumer list is how privacyIDEA's resolver was left holding a superseded bind credential for four days while everything else had been reconciled. Inventory with ops-mason/scripts/custody-inventory.py. It reports 21 paths today, 17 undescribed — the four described are the ones this session touched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 3377672@bnt-lap001 Assistant-Session: 15463ccf-238f-4e13-b163-93aa25c6d166
This commit is contained in:
parent
3252e68a84
commit
e7d326458c
1 changed files with 42 additions and 6 deletions
|
|
@ -180,6 +180,34 @@ The outgoing value is retained until its successor is verified *and* the
|
|||
predecessor is proven rejected. Only then is it destroyed, and its destruction
|
||||
is recorded.
|
||||
|
||||
### Every credential says what it is
|
||||
|
||||
A store of unlabelled values is the drawer of unmarked keys it was supposed to
|
||||
replace. Every path — in `operators/` and in `platform/workloads/` alike —
|
||||
carries five `custom_metadata` fields. They are not secret, so anyone orienting
|
||||
themselves can read all of them without being granted a single value.
|
||||
|
||||
| Field | Answers |
|
||||
| --- | --- |
|
||||
| `description` | what this credential *is*, in a sentence, including what it is **not** — the distinction between an account's password and the token it issues has already cost a session |
|
||||
| `owner` | which repo or subsystem decides about it |
|
||||
| `used_by` | what breaks when it changes. Rotation without this list is how a resolver gets left holding a superseded value for four days |
|
||||
| `rotation` | the procedure, by document path — not a description of one |
|
||||
| `on_loss` | what you do when the value is gone: is it resettable, by whom, and what does the reset cost |
|
||||
|
||||
`on_loss` is the field this whole section exists for. On 2026-08-27 the LLDAP
|
||||
admin predecessor was destroyed and the answer had to be reconstructed from
|
||||
first principles under pressure. Written down in advance it is one line.
|
||||
|
||||
Read the inventory with `ops-mason/scripts/custody-inventory.py`, which walks
|
||||
both mounts, prints these fields, and marks any path missing them. It reads
|
||||
metadata only and runs under `ops-mason-build`, so it is safe to hand to anyone.
|
||||
|
||||
**As of 2026-08-28 that inventory reports 21 paths, 17 of them undescribed.**
|
||||
The four that are described are the ones this session touched. The backlog is
|
||||
real and is not urgent in the way a broken login is urgent — but each of those
|
||||
seventeen is a credential nobody can identify without reading it.
|
||||
|
||||
### Not yet built
|
||||
|
||||
The `operators/` mount and its policy do not exist yet. Until they do, this
|
||||
|
|
@ -212,14 +240,22 @@ ticket — about a secret's *value*, by stage.
|
|||
|
||||
| Stage | Permitted about a value |
|
||||
| --- | --- |
|
||||
| S0–S4 | A fingerprint: length, last three characters, or `sha256 \| head -c 8`. Enough to confirm two parties hold the same credential and no more. |
|
||||
| S5+ | Fingerprint by hash only (`sha256 \| head -c 8`). No plaintext characters, on the grounds that a live platform's credentials outlive the conversation about them. |
|
||||
| S0–S4 | A fingerprint: last three characters, optionally with length. Enough to confirm two parties hold the same credential and no more. |
|
||||
| S5+ | Same, but only where the value is already in an automated pipeline may a hash (`sha256 \| head -c 8`) be used instead; a human comparing by eye still uses the last three. |
|
||||
| Any | Never the whole value, never a working prefix, never a screenshot of one. |
|
||||
|
||||
Prefer the hash form at every stage: it disambiguates just as well, it is what
|
||||
this document's own evidence rules already ask for elsewhere ("public-key
|
||||
fingerprints"), and it does not become a liability when a transcript is read
|
||||
back later by someone who was not in the room.
|
||||
**Last three characters, not a hash — decided deliberately.** A hash looks like
|
||||
the stricter choice and is the worse one in practice. Browsers and secret stores
|
||||
have a reveal button; none of them can show you a digest. To compare hashes you
|
||||
must first extract the plaintext into a shell, where it lands in history, in a
|
||||
process listing, or in a temporary file — so the "safer" convention forces a
|
||||
plaintext handling step that reading three characters off a screen avoids
|
||||
entirely. The rule exists to reduce exposure, and the hash form increases it.
|
||||
|
||||
Hashes remain the right form where the value is already flowing through a
|
||||
pipeline — verifying a KV write from a script, comparing two stores
|
||||
programmatically — because there the plaintext is in memory regardless and no
|
||||
new handling step is created.
|
||||
|
||||
This is a disclosure rule, not a handling rule. It says what may be *spoken*
|
||||
about a value; it does not license reading a value out of a store that a role is
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue