Graduate issues 067 and 068 to decisions and to-be designs
ADR 0084 (extends 0027) — a provision is served by a node-scoped provider the consumer selects, defaulting to co-location; a module may instead carry a private embedded instance that is not a provision. Design: 01-to-be/23-choosing-a-provider. ADR 0085 (extends 0031) — a secret is a provision and the vault is the module that provides it; a module's own local secret becomes an ordinary pair credential that rotates through the existing machinery, while the controller's provisioning-credential mint (0048) is unchanged. Design: 01-to-be/24-the-secrets-vault; doc 13 amended to cross-link the non-pair secret. Issues 067/068 marked resolved with amended-design set. ADR index regenerated; records and index checks pass. Claude-Session: https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
---
|
||||
layer: to-be
|
||||
status: designed
|
||||
code: []
|
||||
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.
|
||||
Reference in New Issue
Block a user