Apply review: two credentials, staged admin rotation, a ninth provider

The fact-check found mailu, whose user is its mailbox, so 0114 rotates
over two credentials rather than two logins, the adapter choosing what a
credential is. Also: minio keeps non-empty buckets; five backends take
their admin credential only at first init, so single-party rotation is
staged; postgres ownership moves to a non-login role; the harness keys by
consumer; rotation state lives with the vault. Consistency fixes across
0110-0113, 26 and 27; issue 103 resolved by mesh-host PR #22.
This commit is contained in:
jochen
2026-09-26 00:38:06 +02:00
parent 43f63ed41c
commit e387c4bd0e
16 changed files with 472 additions and 318 deletions
@@ -15,7 +15,7 @@ controller on 2026-09-25:
| kind | made by | used by |
|---|---|---|
| a credential between a consumer and a provider | the controller | 19 modules |
| a credential between a consumer and a provider | the controller | 16 modules, and 3 more for model access, counted below |
| a module's own secret (`own-secrets`) | the controller, as a random value nothing owns | 54 modules |
| a module's broker account | the controller, but only when a person runs a separate command; otherwise the random value above, which cannot work ([issue 095](../04-ISSUES/095-a-module-assigned-after-genesis-has-no-broker-account/00-report.md)) | 49 modules |
| a node's and the builder's broker accounts | the controller, each in its own code path | every node, the builder |
@@ -78,7 +78,7 @@ it first means reordering the whole installation and giving the vault a second w
requiring a database makes the database's provider require a secret for gitea, and the vault
answers it. The provider's own code does not change: it is handed a login and a password, as today;
- a module's **own secret**. `own-secrets` is retired;
- every **broker account** on the mesh's bus: a module's, a node agent's, the builder's, the
- every **broker account** on the mesh's bus: a module's, a node's host's, the builder's, the
controller's. The broker holding `mesh-broker` carries the mesh's bus
([ADR 0110](0110-a-seat-is-a-module-assignment-from-a-closed-set.md)), and its own provisioner creates
each account from the vault's secret, like any provider. The controller no longer creates accounts,
@@ -122,9 +122,11 @@ generated by genesis:
token.
Until the broker's provisioner runs, genesis creates the bus accounts it generated, with the broker's
admin, as the controller does today. Genesis seals all of it to the control-node's key, and when the vault
is installed the controller **delivers the values to the vault, recorded as the mesh's own**, not as an
operator's. Nobody has to be present for it.
admin, as the controller does today. Genesis seals each value twice: to the control-node's key, so that when the
vault is installed the controller **delivers the values to the vault, recorded as the mesh's own**, not
as an operator's, with nobody present; and to the operator key, as the break-glass copy
[ADR 0085](0085-a-secret-is-a-provision.md) keeps of every root secret. The first enrolment token reaches
the operator the same way.
That distinction matters: an operator's value is never replaced ([ADR 0092](0092-an-operator-delivers-a-pair-credential.md)),
and these are, because the vault can make their replacements. The broker's provisioner then adopts the
accounts genesis created. From then on the vault makes every shared secret, and genesis has made its
@@ -132,7 +134,9 @@ last one.
**Raising the vault or the broker again is a genesis act.** Moving the `mesh-vault` or `mesh-broker`
seat to a new assignment, or recovering either after it is lost, is done the way genesis did it: the
values it needs are delivered, not made by a vault that is not there. That is a break-glass procedure,
values it needs are delivered, not made by a vault that is not there. They come from the operator-sealed
copies, which the operator opens. The vault keeps a copy of every secret sealed to the operator key
(0085), so nothing the mesh relies on exists only inside the vault. That is a break-glass procedure,
stated and checked, never an ordinary assignment.
**A provider makes resources and data, and the mesh carries data back.** A provider's adapter may
@@ -149,14 +153,17 @@ not rotated by the vault: rotating it means an operator delivering a new one. A
issued is rotated by the module that holds the backend asking it again and delivering the new value to
the vault.
**Each recipient takes a new value one of two ways, marked on its requirement:**
**Each recipient takes a new value one of two ways, marked per recipient:**
| recipient takes it by | example | what happens on rotation |
|---|---|---|
| **applying** it | a provider setting a login's password; the broker's provisioner updating an account; a store's provisioner changing its own superuser | its provisioner applies the new value; it is never restarted for it |
| **reading it at start** | a consumer reading its password when it starts | the host recreates it, because a file it read at creation changed |
The marking is on each requirement, not on the secret, because one secret has recipients of both kinds.
The marking is per recipient, not per secret, because one secret has recipients of both kinds. A
provision's contract marks its provider's side, which applies. A consumer's side is read at start
unless its requirement says otherwise. The broker's contract marks the host's bus account the same
way: the broker's provisioner applies it, and the host reads it.
Every module in the catalogue reads its secrets at start, and none watches them
([research 016](../01-RESEARCH/016-how-a-credential-can-be-rotated/02-the-readers.md)). A secret a
backend takes only when it first initialises is marked applied, and its provider's provisioner makes
@@ -165,7 +172,7 @@ rotatable by the mesh**, and a rotation request is refused, saying why, rather t
that would carry on with the old value.
**How old and new change over is decided in [ADR
0114](0114-a-shared-credential-rotates-over-two-logins.md).** Three mechanisms were measured against
0114](0114-a-shared-credential-rotates-over-two-credentials.md).** Three mechanisms were measured against
every provider's code in [research
016](../01-RESEARCH/016-how-a-credential-can-be-rotated/00-overview.md): in place, as the controller's
`rotate` does today; two secrets on one login; and two logins over one resource. Its findings bound the
@@ -173,15 +180,15 @@ choice:
- every credential provider already re-applies a password in place, so today's rotation works, with a
window in which a consumer cannot authenticate;
- seven of eight name the consumer's resource after its login, and five destroy the resource when they
remove the login. **No mechanism may retire a login through today's remove**, because in those five
it deletes the consumer's data;
- only one backend holds two passwords on one login;
- every backend can give two logins the same rights over one resource, once the adapter separates the
resource from the login.
- eight of nine name the consumer's resource after its login, and five destroy the consumer's data when
they remove the login. **No mechanism may retire a login through today's remove**, because in those
five it deletes the consumer's data;
- one backend holds two passwords on one login, and two more hold several tokens;
- every provider can hold two credentials over one resource, eight as two logins and one as two tokens,
once the adapter separates the resource from the credential.
On those facts, 0114 rotates a credential two parties hold over two logins, rotates one a single
party holds in place, and separates retiring a login from removing a consumer.
On those facts, 0114 rotates a credential two parties hold over two credentials, rotates one a single
party holds in place, staged, and separates retiring a credential from removing a consumer.
## What this changes in earlier records
@@ -192,7 +199,9 @@ On acceptance, each of these is superseded or amended by this record, not edited
credential and seals nothing stands.
- [ADR 0085](0085-a-secret-is-a-provision.md) is amended: the vault makes every shared secret, own
secrets are retired, and its rejection of vault-only minting is answered by genesis delivering the
first secrets. "The vault stores no plaintext, ever" stands.
first secrets. Genesis seals its values to the control-node's key as well as to the operator key, so
the controller can deliver them unattended. "The vault stores no plaintext, ever" and the
operator-sealed break-glass copies stand.
- [ADR 0092](0092-an-operator-delivers-a-pair-credential.md) is amended: an operator delivers a secret
to the vault. Genesis's values reach the vault by delivery too, but are recorded as the mesh's own,
so 0092's rule that an operator's value is never replaced does not apply to them.
@@ -201,9 +210,14 @@ On acceptance, each of these is superseded or amended by this record, not edited
- [To-be 13](../03-DESIGN/01-to-be/13-credentials-and-their-rotation.md),
[to-be 21](../03-DESIGN/01-to-be/21-the-installation-in-full.md) and
[to-be 24](../03-DESIGN/01-to-be/24-the-secrets-vault.md) are amended: the vault makes a rotated value and each
requirement says whether its recipient applies it or reads it at start, while the changeover
mechanism stays as to-be 13 describes until its own record; the vault is installed as soon as the
requirement says whether its recipient applies it or reads it at start, and the changeover is
[ADR 0114](0114-a-shared-credential-rotates-over-two-credentials.md)'s; the vault is installed as soon as the
shared runtime base exists, and genesis delivers its secrets to it; the vault is the only maker.
- [To-be 07](../03-DESIGN/01-to-be/07-the-foundation.md) is amended: genesis seals its values to
the control-node's key as well as the operator key.
- [To-be 12](../03-DESIGN/01-to-be/12-a-module-repository.md), [to-be 16](../03-DESIGN/01-to-be/16-module-coverage.md)
and [to-be 18](../03-DESIGN/01-to-be/18-building-a-module.md) are amended: `own-secrets` is retired from the
manifest they describe.
- The [glossary](../00-META/glossary.md) gains *shared secret*, *recipient*, *applies* and *reads at
start*, and *reserved provision*, once this record is accepted.
- [Issue 103](../04-ISSUES/103-a-container-is-not-recreated-when-a-file-it-reads-changes/00-report.md)
@@ -217,7 +231,7 @@ On acceptance, each of these is superseded or amended by this record, not edited
- Resolution expands per-consumer requirements from a provision's contract. The contract declares
them, never the provider's code, so what a provider requires stays predictable from the catalogue.
- A data provider's adapter gains a return value. What a credential provider's adapter must change for
rotation is [ADR 0114](0114-a-shared-credential-rotates-over-two-logins.md)'s.
rotation is [ADR 0114](0114-a-shared-credential-rotates-over-two-credentials.md)'s.
- The broker's provisioner gains every bus account, and the controller loses five separate places it
generates a secret today.
- 54 modules move from own secrets to vault requirements. Six provider clients export a password
@@ -236,7 +250,7 @@ On acceptance, each of these is superseded or amended by this record, not edited
| A private key is made where it is used | A test per key: a node's sealing key never leaves the node, the operator's private key never enters the mesh, and the certificate authority's private key never leaves the controller's identity store. |
| Only the vault provides `secret` | The parser refuses a definition providing `secret` that cannot hold `mesh-vault`, and resolution refuses a pin on a `secret` requirement. |
| The controller and each node's host take the same path | A catalogue test: the controller's definition declares no own secret, only requirements. A controller test: a node's bus account is made by the vault and delivered sealed to that node; an enrolment token reaches the controller only as what verifies it. |
| Genesis's values reach the vault unattended | An installer test: genesis's values are sealed to the control-node's key and delivered by the controller when the vault is installed, with no operator step. |
| Genesis's values reach the vault unattended, and the operator keeps a copy | An installer test: each of genesis's values is sealed to the control-node's key and to the operator key; the controller delivers the first to the vault when it is installed, with no operator step; the operator's copy opens only with the operator key. |
| Moving the vault or broker is a procedure | A resolution test: an ordinary assignment moving `mesh-vault` or `mesh-broker` is refused, naming the procedure. |
| A backend-issued secret enters through the vault | A vault test: a value delivered as issued is provided like any other, and rotating it is refused as the vault's act. |
| A secret with no provisioner to apply it is not rotated by restart | A vault test: rotating a secret whose requirement is marked not rotatable by the mesh is refused, naming why. |