Files
hq/03-DESIGN/01-to-be/24-the-secrets-vault.md
T

9.4 KiB

layer, status, code, updated, decisions
layer status code updated decisions
to-be implemented
mesh-catalog
mesh-controller
mesh-host
2026-09-21
02-DECISIONS/0092-an-operator-delivers-a-pair-credential.md
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). The store and the broker are ordinary modules too (ADR 0078). 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), 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); 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, through secret accept … --provider, which seals it to both ends and records the pair as accepted (ADR 0092) — and the credential belongs to the consumer↔vault pair. An accepted pair is the one exception to what follows: the mesh cannot make its replacement, so it is neither remade when a key changes nor rotated; both are refused aloud, and accepting a new value is the rotation. How it is checked: an inventory test accepts, reads back unchanged, and asserts the two refusals name the remedy. 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). 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 foundation module, one per mesh

The vault is an ordinary module — built, assigned and upgraded like any other — and it is part of the foundation: installed at genesis, the way the store and broker are raised first and then adopted as modules (ADR 0078). A mesh has root secrets, so a mesh has a vault; it is not something a mesh opts into (ADR 0085, amended). 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.

There is one vault per mesh, on the control-node, reached from any node the way identity is. Node scoping (ADR 0084, 23) exists because a store holds data a consumer is coupled to; a vault holds nothing a consumer is coupled to, and a second one would be a second place to lose.

The root secrets, and the operator key

The mesh has secrets of its own that no module requires from anybody: the store's superuser, the broker's administrator, the controller's credentials to its contexts, the sealing key of every node. Until now each was sealed to the node that uses it and to nothing else, so a node whose key was gone took them with it — and at genesis they were not even secret, the foundation being raised with fixed credentials that were then carried in.

The vault's second job is to hold these, and it does so without holding a value:

  • An operator sealing key. A keypair made once, by the operator, whose private half is written to a file the operator keeps off the mesh and whose public half the mesh records. It is made before anything else is minted, so that everything is sealed to it.
  • A second seal. Every secret a module holds for itself — minted or accepted — is sealed to its node as before and, in addition, to the operator key. What the mesh stores is one more blob it cannot open. A secret made before the key existed has no such copy and cannot get one, the plaintext being gone; the mesh says which those are rather than letting the export pass for complete.
  • The export. All copies sealed to the mesh's current operator key, the key's public half and its fingerprint, and two honest lists beside them: what is sealed to an earlier key the mesh has since replaced, openable with that key alone, and what has no operator copy at all. As one document. The vault declares that it keeps this, and the mesh writes it onto the vault's own disk as an ordinary declared file — ciphertext to the machine that holds it and to the bus it crossed. The same document can be written out by the operator to keep beside the key.
  • Recovery. The operator, holding the private key, opens one secret from the export or from the store and gets it as a file, never on a terminal unless asked for. It needs the key and a blob; the mesh has the blobs and no key, the operator the key and no blob until given one. Nothing in this reintroduces a place that can open everything, which is the property ADR 0048 was built to keep — so the break-glass question the first version of this design left open is answered by it.

Genesis makes the operator key first and mints real root secrets in place of the fixed ones the foundation is raised with, so the mesh is handed over with nothing well-known in it. The installer does this (21); what it runs is described in the as-is (00-as-is/06).

A module's vault-provided secret — the pair credential of 13 — is sealed to the operator the same way, as is every credential a provider grants; the export names each entry by the node and module that hold it and the name they know it by, and says whether it is a module's own secret or a pair credential, so recovery addresses both alike.

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.

Break-glass is answered above, and by the property it had to keep: it does not reintroduce a key that one place holds. The vault keeps blobs sealed to the operator; the operator keeps a key with nothing to open until handed a blob. Locating and verifying are the vault's tools, by fingerprint; backing up is the export.

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.