55 lines
3.1 KiB
Markdown
55 lines
3.1 KiB
Markdown
---
|
|
status: resolved
|
|
opened: 2026-09-20
|
|
located-in: [mesh-controller internal/inventory (secret), mesh-controller cmd/mesh-controller (secret accept)]
|
|
fixed-by: ADR 0092; mesh-controller feat/multiple-fixes (secret accept --provider; origin on the pair; remake and rotate refused)
|
|
amended-design: 03-DESIGN/01-to-be/24-the-secrets-vault.md
|
|
---
|
|
|
|
# An operator cannot deliver a pair credential, so the vault's third species has no entry
|
|
|
|
## Symptom, as observed
|
|
|
|
[ADR 0085](../../02-DECISIONS/0085-a-secret-is-a-provision.md) names three species of secret and
|
|
gives the vault two: a module's own secret, which the mesh mints, and an **operator-delivered**
|
|
secret — a credential for something outside the mesh, which only a person can supply. The design
|
|
([24](../../03-DESIGN/01-to-be/24-the-secrets-vault.md)) says the vault *"takes custody of one an
|
|
operator delivered"*.
|
|
|
|
Under 0085 a secret from the vault is a pair credential between the consumer and the vault. The
|
|
controller has one command that takes a value from a person — `secret accept` — and it writes
|
|
**only a module's own secret**: one node, one module, one name, sealed to that node's key alone.
|
|
There is no command that accepts a value *into a pair*: sealed to the consumer's node **and** to
|
|
the vault's node, recorded as `accepted` so a later push does not replace it with a minted one.
|
|
|
|
The primitive exists. The sealing package's `Accept` takes a value and two keys, and the licence
|
|
adapters already use it to carry an operator's token to both ends of a licence. Nothing exposes it
|
|
for an ordinary provision.
|
|
|
|
So today an operator-delivered secret can be held in only one of two wrong ways: as an own secret
|
|
(un-audited, un-rotatable — the gap 0085 opened to close), or not at all.
|
|
|
|
## Why it matters beyond this instance
|
|
|
|
- **The vault is one third short of its decision** until this exists, and the third that is
|
|
missing is the one with a real leak history — external keys are the secrets that end up in
|
|
transcripts and env files.
|
|
- **The `made`/`accepted` distinction is only recorded for own secrets.** A pair credential has no
|
|
origin column, so even once a value can be accepted into a pair, `rotate` would mint over it —
|
|
generating "32 random bytes where a working credential was", the exact fault `secret accept`
|
|
was written to prevent for own secrets. Rotating an accepted pair credential must refuse, or
|
|
must ask for the replacement value, and neither is designed.
|
|
- **The consumer's login is derived, and an external service did not derive it.** An accepted
|
|
external key has no `as` the far side knows; a pair credential assumes both ends agree on one.
|
|
For the vault this is harmless (the vault creates nothing under that login), but the model
|
|
leaks through in what the consumer is told.
|
|
|
|
## Open questions
|
|
|
|
- Is this `secret accept <consumer-node> <module> secret --from …` growing a provider end, or a
|
|
new verb on the pair?
|
|
- Does an accepted pair credential refuse `rotate`, or does `rotate` become "accept a new value
|
|
and deliver it" for that pair?
|
|
- Should the origin (`made` / `accepted`) become a fact of every pair credential, so the vault's
|
|
ledger can show which of its secrets a person supplied?
|