Hand off the secrets vault to mesh-catalog; open issues 069 and 070

Design 24 flips to in-progress with mesh-catalog as its owner (playbook 04).
Starting the build surfaced two gaps the decision did not settle: a module
requiring `secret` receives exactly one value (069), and no command can
accept an operator's value into a consumer↔vault pair (070). Both opened as
issues rather than improvised around.

Also fills fixed-by on 067 and 068, which the cycle check refused as resolved
with no reference.
This commit is contained in:
2026-09-20 23:08:28 +02:00
parent 731027c009
commit 187389b7d1
5 changed files with 115 additions and 4 deletions
+2 -2
View File
@@ -1,7 +1,7 @@
--- ---
layer: to-be layer: to-be
status: designed status: in-progress
code: [] code: [mesh-catalog]
updated: 2026-09-20 updated: 2026-09-20
decisions: decisions:
- 02-DECISIONS/0085-a-secret-is-a-provision.md - 02-DECISIONS/0085-a-secret-is-a-provision.md
@@ -2,7 +2,7 @@
status: resolved status: resolved
opened: 2026-09-20 opened: 2026-09-20
located-in: [] located-in: []
fixed-by: fixed-by: graduated — hq fc4ab37 (ADR 0084, to-be design 23)
amended-design: 03-DESIGN/01-to-be/23-choosing-a-provider.md amended-design: 03-DESIGN/01-to-be/23-choosing-a-provider.md
--- ---
@@ -2,7 +2,7 @@
status: resolved status: resolved
opened: 2026-09-20 opened: 2026-09-20
located-in: [] located-in: []
fixed-by: fixed-by: graduated — hq fc4ab37 (ADR 0085, to-be design 24)
amended-design: 03-DESIGN/01-to-be/24-the-secrets-vault.md amended-design: 03-DESIGN/01-to-be/24-the-secrets-vault.md
--- ---
@@ -0,0 +1,57 @@
---
status: open
opened: 2026-09-20
located-in: []
fixed-by:
amended-design:
---
# One `secret` provision yields one value, and a module may need several
## Symptom, as observed
[ADR 0085](../../02-DECISIONS/0085-a-secret-is-a-provision.md) makes a module's own secret a
provision: the module requires `secret` from a vault and reads the pair credential the mesh
minted for that consumer↔vault pair. A pair has **exactly one** credential — that is the property
[13](../../03-DESIGN/01-to-be/13-credentials-and-their-rotation.md) is built on, and the reason
rotation touches one holder and nothing else.
A module may only require a provision name once, so a module that requires `secret` receives
**one value**. Counted across the catalogue at the time of writing, a module's own secrets other
than its broker account number as follows:
| own secrets | modules |
|---|---|
| one | 22 |
| two | 6 (an internal token and an admin password, a user and a password for an outbound mail relay, …) |
| three or more | 4 (a source, an admin and a relay password; a root certificate, its key and that key's password; …) |
Ten modules cannot be migrated onto the vault as designed, because the design gives them one
value where they hold two or more, and nothing in the record says what they should do instead.
## Why it matters beyond this instance
- **The migration in ADR 0085 is "module by module, not a flag day"** — but for a third of the
modules with own secrets there is no target to migrate to, and the gap is silent: such a
module keeps `own-secrets` for the rest and looks migrated.
- **Two candidate answers pull in different directions and neither is recorded.** A module could
derive several keys from its one value (a key-derivation step inside the module, which puts
cryptography into every module that needs two secrets), or the mesh could let a consumer
require several *named* secrets from one vault (which is a pair with several credentials, the
thing 13 deliberately does not have, or several pairs between the same two modules, which the
identity derivation in [ADR 0049](../../02-DECISIONS/0049-a-consumers-identity-fits-the-tightest-backend.md)
cannot express).
- **A secret with structure is not one value either.** A certificate authority's root
certificate, key and key password are three things with one lifecycle; a value the controller
mints at random is none of them. The vault's third species — an operator-delivered secret — is
the nearer fit, and it has its own gap ([070](../070-an-operator-cannot-deliver-a-pair-credential/00-report.md)).
## Open questions
- Is "one value per module" a rule to keep, with derivation the module's business, or does a
consumer name several secrets and the vault serve each as its own pair?
- If the latter: what is the login of the second pair between the same consumer and the same
vault, given that a login is derived from the (node, module) pair and is what the secret is
keyed on?
- Which modules genuinely need several independent secrets, and which hold two names for one
thing (an admin password and a token that is only ever set from it)?
@@ -0,0 +1,54 @@
---
status: open
opened: 2026-09-20
located-in: [mesh-controller]
fixed-by:
amended-design:
---
# 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?