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.
99 lines
5.9 KiB
Markdown
99 lines
5.9 KiB
Markdown
---
|
|
layer: to-be
|
|
status: in-progress
|
|
code: [mesh-catalog]
|
|
updated: 2026-09-20
|
|
decisions:
|
|
- 02-DECISIONS/0085-a-secret-is-a-provision.md
|
|
- 02-DECISIONS/0031-the-control-plane-authenticates-nobody.md
|
|
- 02-DECISIONS/0048-a-provider-creates-the-credential-the-mesh-minted.md
|
|
---
|
|
|
|
# 24 — The secrets vault
|
|
|
|
Identity runs on the mesh, not of it — a module other modules require, not a part of the
|
|
controller ([ADR 0031](../../02-DECISIONS/0031-the-control-plane-authenticates-nobody.md)). The
|
|
store and the broker are ordinary modules too
|
|
([ADR 0078](../../02-DECISIONS/0078-the-store-and-broker-are-modules.md)). Secrets are the piece
|
|
that never got the same treatment: nothing owns a secret. This document describes the module that
|
|
does — a **vault** that provides a `secret` provision — and, as importantly, the boundary of what
|
|
it owns and what it deliberately does not.
|
|
|
|
## Three kinds of secret, and which the vault owns
|
|
|
|
A mesh handles three species of secret, and confusing them is how the current arrangement went
|
|
wrong.
|
|
|
|
- **A provisioned credential** is the login one module uses against another — a database password,
|
|
a broker account. The mesh mints it per consumer↔provider pair and seals it to the machines that
|
|
must hold it ([ADR 0048](../../02-DECISIONS/0048-a-provider-creates-the-credential-the-mesh-minted.md)),
|
|
and its provider makes it true. **This is not the vault's**, and the vault does not change it. It
|
|
already has an owner and a rotation
|
|
([13](13-credentials-and-their-rotation.md)); disturbing it would buy nothing.
|
|
- **A module's own secret** is a value a single module needs for itself — the password of a store
|
|
it runs privately, an internal signing token. Today the mesh generates this as a value nothing
|
|
owns, and so it cannot be rotated. **This is the vault's.**
|
|
- **An operator-delivered secret** is a value only a person can supply — a credential for something
|
|
outside the mesh. Today it is sealed in and then held, un-audited and un-rotatable. **The vault
|
|
holds this**, and can hand it out and audit it, though it cannot generate it.
|
|
|
|
The vault, then, is the provider a module turns to for a secret that is **not** the byproduct of
|
|
some other provision. It is the answer to *"this module needs a password, and there is no provider
|
|
whose job it is to give it one."*
|
|
|
|
## A secret as a provision
|
|
|
|
A module that needs a secret for its own use requires a `secret` provision, exactly as it requires
|
|
a database from the store. The vault generates the value — or takes custody of one an operator
|
|
delivered — and the credential belongs to the consumer↔vault pair. Because it is an ordinary pair
|
|
credential, **everything already built for pair credentials applies to it unchanged**: it rotates
|
|
with the one command that discards a credential and delivers both ends together, it is one secret
|
|
per holder so rotating one touches nothing else, and *who holds this* is a query rather than an
|
|
assumption ([13](13-credentials-and-their-rotation.md)). The rotation a module's own secret lacks
|
|
today is not new machinery; it is the machinery that already moves a database password, pointed at
|
|
a secret the vault provides.
|
|
|
|
This is what replaces the "generated value that nothing owns". A module's own password stops being
|
|
a special kind of thing injected by the synchroniser and becomes a provision with a provider, a
|
|
holder, and a lifecycle — the same shape as everything else the mesh grants.
|
|
|
|
## The vault is a module, and node-scoped
|
|
|
|
The vault is an ordinary module. A mesh that wants one runs it; a mesh whose modules require no
|
|
secret of their own runs none — the same test identity meets
|
|
([ADR 0031](../../02-DECISIONS/0031-the-control-plane-authenticates-nobody.md)). It is provisioned
|
|
the ordinary way, and it does **not** mint the mesh's delivery credentials — the controller does
|
|
that, because the controller must mint in order to deliver any provision, the vault's own included.
|
|
The vault mints secrets *for modules*, downstream of its own existence, never the credential that
|
|
delivers it.
|
|
|
|
Like every provider it is node-scoped
|
|
([ADR 0084](../../02-DECISIONS/0084-which-provider-serves-a-consumer.md), [23](23-choosing-a-provider.md)):
|
|
each node may run its own vault, and a module's own secret is held by the vault on the module's
|
|
node, selected the same way any provider is. There is no single mesh vault holding everything, for
|
|
the same reason there is no single mesh store.
|
|
|
|
## Beyond generate and hold
|
|
|
|
Owning a secret means owning more than its creation. The mesh being migrated onto has a working
|
|
secrets subsystem whose surface names the operations a vault is responsible for — locating a secret,
|
|
backing it up, verifying it is what it should be, and a break-glass recovery for when the normal
|
|
path is unavailable. These become the vault module's, specified against it rather than scattered.
|
|
|
|
One of them is left open on purpose. **Break-glass must not reintroduce a key that one place
|
|
holds.** The whole point of sealing a secret to the machine that needs it, asymmetrically, is that
|
|
no single place can open everything
|
|
([ADR 0048](../../02-DECISIONS/0048-a-provider-creates-the-credential-the-mesh-minted.md)); a
|
|
recovery path that keeps a master key would undo exactly that. How the vault lets an operator
|
|
recover a secret without becoming the thing the sealing was designed to prevent is a question this
|
|
design opens and does not yet answer.
|
|
|
|
## What this changes for a module
|
|
|
|
A module that today writes a password into its own composition — an embedded store's login, an
|
|
internal token — instead requires it from the vault and reads it where the mesh puts it. The change
|
|
is taken module by module, not as a flag day, and it is the same change in each: a value the module
|
|
authored becomes a value the vault provides. When it is done, the guarantee the as-is design already
|
|
makes for generated secrets — *nothing in the repository contains a credential* — holds for a
|
|
module's own secrets not by convention but because there is a provider whose job it is to keep them.
|