77 lines
5.6 KiB
Markdown
77 lines
5.6 KiB
Markdown
---
|
|
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.
|
|
|
|
*Done live, 2026-10-01 10:13 UTC:* the search module on the home server, the first definition to say
|
|
`taken: at-start` whose value the mesh had made. Asked through the console; the controller said it was
|
|
rotated and sent the machine; twenty-seven seconds later the secret file carried a new write time, the
|
|
server container had restarted on it, and the module answered. The two secrets filed in the forge's
|
|
report, the automation module's token and admin password, were refused: both had been accepted by hand
|
|
during adoption, and that refusal is the one ADR 0113 asks for — something outside the mesh may hold
|
|
an accepted value, so replacing it is a person's act. Modules whose source is pinned to a commit are
|
|
not rebuilt by a merge; their new definition was registered by asking for a build of main through the
|
|
console, which since [issue 176](../176-the-consoles-build-tool-neither-waits-nor-registers/00-report.md)
|
|
registers what it hears.
|
|
|
|
## 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.
|