Issue 180: a module's own secret rotates when it is read at start; the applied form stays open (controller PR 183); design 13 and ADR 0114 carry the word
This commit is contained in:
@@ -163,6 +163,14 @@ value, the requirement is marked not rotatable by the mesh, and a rotation is re
|
|||||||
**The number of parties decides, never the provider.** The resolver knows it from the requirement's
|
**The number of parties decides, never the provider.** The resolver knows it from the requirement's
|
||||||
recipients, leaving out the vault's custody copy, so no definition declares it.
|
recipients, leaving out the vault's custody copy, so no definition declares it.
|
||||||
|
|
||||||
|
> **Progressive insight — 2026-10-01.** The number of parties is the resolver's to know; *which form*
|
||||||
|
> a single party's credential takes is not, and cannot be: whether a module reads its secret when it
|
||||||
|
> starts or applies it once to a backend is a fact about the software, visible nowhere in the graph.
|
||||||
|
> So the definition declares that half — `taken: at-start` or `taken: applied` on an own secret — and
|
||||||
|
> a secret that declares neither is not rotated, refused with the word to write (issue 180). The
|
||||||
|
> read-at-start form is built; the staged form for an applied credential is not. The decision stands;
|
||||||
|
> the sentence above was one fact short.
|
||||||
|
|
||||||
### Until an adapter can
|
### Until an adapter can
|
||||||
|
|
||||||
**An adapter that cannot yet ensure a second credential says so.** The two-party credentials it applies
|
**An adapter that cannot yet ensure a second credential says so.** The two-party credentials it applies
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ code:
|
|||||||
- mesh-controller internal/inventory/secrets.go
|
- mesh-controller internal/inventory/secrets.go
|
||||||
- mesh-controller cmd/mesh-controller/rotate.go
|
- mesh-controller cmd/mesh-controller/rotate.go
|
||||||
- mesh-controller examples/postgres-provisioner
|
- mesh-controller examples/postgres-provisioner
|
||||||
updated: 2026-09-21
|
updated: 2026-10-01
|
||||||
decisions:
|
decisions:
|
||||||
- 02-DECISIONS/0001-mesh-brokers-nodes-host-agents-think.md
|
- 02-DECISIONS/0001-mesh-brokers-nodes-host-agents-think.md
|
||||||
- 02-DECISIONS/0009-modules-and-the-graph.md
|
- 02-DECISIONS/0009-modules-and-the-graph.md
|
||||||
@@ -136,3 +136,14 @@ rotates, and is queried for who holds it, through exactly the machinery describe
|
|||||||
The rotation a module's own secret lacks is not a second mechanism; it is this one, pointed at a
|
The rotation a module's own secret lacks is not a second mechanism; it is this one, pointed at a
|
||||||
secret the vault provides. What this page proves for a database password holds, by construction,
|
secret the vault provides. What this page proves for a database password holds, by construction,
|
||||||
for a secret from the vault.
|
for a secret from the vault.
|
||||||
|
|
||||||
|
*Built 2026-10-01, the read-at-start half ([issue 180](../../04-ISSUES/180-a-modules-own-secret-cannot-be-rotated/00-report.md),
|
||||||
|
[ADR 0114](../../02-DECISIONS/0114-a-shared-credential-rotates-over-two-credentials.md)).* An own secret
|
||||||
|
says how the module takes it — `taken: at-start` or `taken: applied` on its entry — and the mesh
|
||||||
|
rotates only the first: `secret rotate <node> <module> <name>` makes it anew, seals it to the machine
|
||||||
|
and the operator, and sends the machine, so the module starts again on it. A secret that says neither
|
||||||
|
is refused with the word to write, because a credential rotated under software that never reads it
|
||||||
|
again is the fault of issue 179 made deliberately; an applied one is refused until the staged form is
|
||||||
|
built; an accepted one is refused as ADR 0113 says. `rotate` is a verb on the controller's seat with
|
||||||
|
both shapes, so the console asks for either. *How it is checked:* the tests named in issue 180, and a
|
||||||
|
live rotation through the console of a secret a module reads at start.
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
---
|
||||||
|
status: resolved
|
||||||
|
opened: 2026-10-01
|
||||||
|
located-in: [mesh-controller cmd/mesh-controller/secret.go (accept and now rotate), mesh-controller internal/inventory/secrets.go (a module's own secret), mesh-controller internal/catalogue/manifest.go (how an own secret is taken), the controller's seat (rotate was not a verb)]
|
||||||
|
fixed-by: mesh-controller PR 183 (`secret rotate`, the `taken` word on an own secret, `rotate` on the controller's seat with two shapes); the staged form for an applied secret stays open below
|
||||||
|
amended-design: [03-DESIGN/01-to-be/13-credentials-and-their-rotation.md, 02-DECISIONS/0114-a-shared-credential-rotates-over-two-credentials.md]
|
||||||
|
---
|
||||||
|
|
||||||
|
# 180 — A module's own secret cannot be rotated, and nothing rotates from the console
|
||||||
|
|
||||||
|
## What was observed
|
||||||
|
|
||||||
|
Filed first in the forge's tracker on this repository (its issue 231, 2026-09-30), after a module's
|
||||||
|
API token was printed by accident on the home server and the only way to change it was to generate
|
||||||
|
a value by hand and `secret accept` it. The report said there was no rotation at all. That was half
|
||||||
|
right: `rotate <provision>` has existed for a pair credential since design 13, pushing both ends
|
||||||
|
together; what did not exist was any rotation of a **module's own secret** — a token, an application
|
||||||
|
secret, an administrator — and any way to ask for either through the console
|
||||||
|
([ADR 0152](../../02-DECISIONS/0152-the-operators-surface-is-a-module-the-console.md)).
|
||||||
|
|
||||||
|
## Why this is here
|
||||||
|
|
||||||
|
[ADR 0114](../../02-DECISIONS/0114-a-shared-credential-rotates-over-two-credentials.md) decided how a
|
||||||
|
single party's credential rotates: in place, in one of two forms. **Read at start** — the vault
|
||||||
|
delivers the new value as current and the party is started again. **Applied** — the party's own
|
||||||
|
code applies the value to a backend that takes it once, so the new value must be staged beside the
|
||||||
|
current one until the party confirms it. Neither form was built, and nothing said which form a given
|
||||||
|
secret needed. That last gap is the dangerous one: [issue 179](../179-an-adopted-identity-providers-admin-never-took-the-minted-secret/00-report.md)
|
||||||
|
is what a value looks like when the mesh believes it was taken and the software never read it. A
|
||||||
|
rotation that made that happen on purpose would be worse than no rotation.
|
||||||
|
|
||||||
|
## Resolved, 2026-10-01 — the read-at-start form, and the verb
|
||||||
|
|
||||||
|
**An own secret says how it is taken.** In a definition, `"own-secrets": {"api-token": {"path": …,
|
||||||
|
"taken": "at-start"}}` says the module reads the file when it starts; `"taken": "applied"` says its
|
||||||
|
own code applies the value to a backend; a path alone says neither. A definition that says neither
|
||||||
|
is not rotated by the mesh, and the refusal names the word to write.
|
||||||
|
|
||||||
|
**`secret rotate <node> <module> <name>`** makes the secret anew the way the first mint did, seals it
|
||||||
|
to the machine and to the operator, and sends the machine, so the module starts again on the new
|
||||||
|
value, under the same `restart-on` that any changed file triggers. Said in the log with who asked and
|
||||||
|
when, never the value. An applied secret is refused by name, until the staged form exists. A value
|
||||||
|
given to the mesh rather than made by it is refused as [ADR 0113](../../02-DECISIONS/0113-the-vault-makes-every-secret.md)
|
||||||
|
says, with the way out: change it where it lives, then accept the new value.
|
||||||
|
|
||||||
|
**`rotate` is a verb on the controller's seat** ([ADR 0154](../../02-DECISIONS/0154-the-meshs-own-verbs-are-the-controller-seats-tools.md)),
|
||||||
|
with the two shapes the mesh has: a pair credential by provision and consuming machine, or an own
|
||||||
|
secret by machine, module and name. The console can ask for either.
|
||||||
|
|
||||||
|
Nothing in the catalogue says `taken` yet: the word ships one release ahead of its first use, and the
|
||||||
|
first definitions to say it follow once this controller runs.
|
||||||
|
|
||||||
|
*How it is checked:* the manifest form and its refusals; the rotation against a raised store —
|
||||||
|
rotates a secret taken at start, refuses an applied one, an undeclared one, an accepted one and an
|
||||||
|
unknown name, each in its own words; the verb's two shapes; and, live, a secret rotated through the
|
||||||
|
console on a module that reads it at start, the module restarted, and the module working.
|
||||||
|
|
||||||
|
## Open — the applied form
|
||||||
|
|
||||||
|
The staged rotation ADR 0114 decided for an applied secret is not built: the vault delivering the
|
||||||
|
new value beside the current one, the module's own code switching the backend and confirming, and
|
||||||
|
only then the new value current. It needs a word in the module's protocol for *confirm*, and it is
|
||||||
|
the form the identity provider's administrator and every database's superuser need. The request's
|
||||||
|
second half — re-issuing a pair credential through the provider's own code rather than by re-minting
|
||||||
|
and pushing — is the same shape from the provider's side, and sits with it.
|
||||||
Reference in New Issue
Block a user