110 lines
5.5 KiB
Markdown
110 lines
5.5 KiB
Markdown
---
|
|
layer: as-is
|
|
status: implemented
|
|
code: [hal, mesh-controller, mesh-catalog, mesh-host]
|
|
updated: 2026-09-21
|
|
decisions:
|
|
- 02-DECISIONS/0011-managed-files-are-generated-never-edited.md
|
|
- 02-DECISIONS/0009-modules-and-the-graph.md
|
|
- 02-DECISIONS/0085-a-secret-is-a-provision.md
|
|
---
|
|
|
|
# Configuration and secrets
|
|
|
|
Every value a module reads at runtime comes from a file on a node's disk. Every one of those
|
|
files is **generated**.
|
|
|
|
## The rule
|
|
|
|
A managed file is derived from the mesh database. A synchroniser rewrites it when the values
|
|
behind it change. The write path is the mesh operation that owns the value; the file is an
|
|
output ([ADR 0011](../../02-DECISIONS/0011-managed-files-are-generated-never-edited.md)).
|
|
|
|
An edit to a managed file survives until the next synchronisation and is then overwritten
|
|
silently, taking whatever it was fixing with it — bringing back the bug the edit had removed,
|
|
with a delay, and with no error to connect the two events.
|
|
|
|
There is a way to ask whether a given file is managed. That question has to be asked, because
|
|
the answer is not visible from the file.
|
|
|
|
## What is managed
|
|
|
|
Generated environment files for each module, service definitions in each module's runtime
|
|
directory, managed configuration files a module declares, and node-level settings. The list is
|
|
not a category — it is whatever a synchroniser claims, which is why the question is asked of
|
|
the tooling rather than answered from a rule.
|
|
|
|
## How a value is decided
|
|
|
|
Values resolve by precedence, highest first:
|
|
|
|
1. **A database override** — set deliberately, or written by a provisioner.
|
|
2. **The value already in the generated file** — preserved for anything without an override.
|
|
3. **A generated value** — a random secret, a template composed from other variables, or a
|
|
value pulled from the node's own record.
|
|
4. **The manifest default.**
|
|
|
|
Two consequences follow, and both are subtle enough to have caused confusion.
|
|
|
|
The second rule is what keeps a generated password **stable** across regenerations. It is not
|
|
an oversight; without it every regeneration would issue a new secret and break whatever holds
|
|
the old one.
|
|
|
|
The same rule means that **changing a manifest's default does not change anything already
|
|
using it**. The existing file's value wins. The new default reaches only installations that
|
|
never had one.
|
|
|
|
Removing a declaration is worse than changing it: the old override row and the file it produced
|
|
are both left behind. Configuration is additive in practice, whatever the manifest says.
|
|
|
|
## Secrets
|
|
|
|
Generated secrets are produced by the mesh, never authored. Provisioned credentials arrive as
|
|
database overrides written by the provisioner and are marked as such, so they can be
|
|
distinguished from a deliberate override and cleaned up when the grant is removed
|
|
([ADR 0009](../../02-DECISIONS/0009-modules-and-the-graph.md)).
|
|
|
|
Nothing in the repository contains a credential. The repository has no per-node content at all,
|
|
which is what makes that guarantee structural rather than a matter of care.
|
|
|
|
Two known weaknesses, both recorded rather than resolved:
|
|
|
|
**Generated environment files were world-readable.** The mode passed at write time only applies
|
|
when the file is created, so regeneration left the previous mode in place. The class of bug is
|
|
worth remembering beyond the instance: a permission set at creation is not a permission
|
|
maintained.
|
|
|
|
**Rotation was not a mesh operation** in the mesh being replaced, and doing it by hand took
|
|
services down. On the mesh that exists now it is: `rotate <provision>` discards a pair credential
|
|
and delivers both ends in one send, and a module's own secret is such a pair credential when the
|
|
module takes it from the vault — which, at the time of writing, one module does (redis).
|
|
|
|
## The vault, as it runs
|
|
|
|
Since 2026-09-21 the mesh runs `mesh-vault`, a foundation module installed at genesis beside the
|
|
adopted store and broker. It provides `secret`: a module that requires one receives a pair
|
|
credential the controller minted, and the vault's ledger records the holder and the value's
|
|
fingerprint, notices a rotation, and answers over the mesh by fingerprint only. It holds no value.
|
|
|
|
Genesis makes the store's superuser and the broker's administrator rather than copying the
|
|
template's, keeps them at the paths the store and broker modules declare as their own secrets, and
|
|
makes an **operator sealing key** before the first secret is accepted: its private half is a file
|
|
beside the produced bundle, which the operator carries off the machine, and its public half is
|
|
what the mesh records. Every secret a module holds for itself and every pair credential is sealed
|
|
to that key as well as to its node. The export of those copies is written beside the key at the
|
|
end of genesis and kept by the vault on its own disk; the operator recovers any secret from it, off
|
|
the mesh, with `secret recover`. Secrets made before the key existed, or sealed to a replaced key,
|
|
are listed as such rather than passed off as recoverable. The produced bundle and the installer's
|
|
transcript carry no credential in the clear.
|
|
|
|
Two things this does not yet do: a module with several own secrets cannot take them all from the
|
|
vault (issue 069), and an operator cannot hand a value into a pair credential (issue 070).
|
|
|
|
## Node-level and mesh-level values
|
|
|
|
A node-level value applies to everything on one node. A mesh-level value applies everywhere and
|
|
is read by every node — the broker's location is the canonical example, and a wrong one is how
|
|
a mesh fails to form.
|
|
|
|
Neither is a file that anyone edits. Both are database records that produce files.
|