novox/hq 04-ISSUES/022: a consumer is a module on a machine, not a machine. The mesh now writes <node>.<module>.secret and the provisioners name the role and the access key after both. The fixtures here write what the mesh writes, so they move with it — that is the whole point of them, and a fixture that kept the old shape would agree with the bug rather than catch it. The object-store assertions are the ones that mattered most: one store holds every bucket behind one endpoint, so isolation is a policy rather than a property. One access key per machine meant every module on a node shared it, and the policy confining each consumer to its own bucket confined none of them.
48 lines
2.3 KiB
Markdown
48 lines
2.3 KiB
Markdown
# Provisioners
|
|
|
|
The half that makes a credential real.
|
|
|
|
The mesh generates a password, seals it to the machine that must accept it, and never holds the
|
|
value — so it cannot tell PostgreSQL, or MinIO, or a broker, to start accepting it. Something on
|
|
that machine reads what arrived and makes it true. That something is a provisioner, and it belongs
|
|
to the module that ships the software, not to the mesh.
|
|
|
|
**What the mesh owns is the contract.** A provider module declares:
|
|
|
|
```json
|
|
{
|
|
"provides": [{"name": "database", "scope": "mesh"}],
|
|
"receives": {"database": "/var/lib/postgres/grants/mesh.json"},
|
|
"grants": {"database": "/var/lib/postgres/grants"}
|
|
}
|
|
```
|
|
|
|
and is then given, by the host, from an ordinary declaration:
|
|
|
|
| | |
|
|
|---|---|
|
|
| `mesh.json` | every consumer, what it asked for, and where its credential is |
|
|
| `<node>.<module>.secret` | one consumer's password, alone in the file, sealed in transit and written in plain by the host. Named after both, because a consumer is a module on a machine and a node routinely runs several (`novox/hq` 04-ISSUES/022) |
|
|
|
|
Two files rather than one because the mesh discarded the plaintext and cannot compose a document
|
|
containing it. The consequence is a good one: the readable half stays readable, and the secret
|
|
half changes only when the secret does.
|
|
|
|
**A provisioner reconciles; it is not told what changed.** It runs after every declaration and
|
|
must reach the same state from wherever it starts. That means, in order:
|
|
|
|
1. every consumer in the manifest has what it asked for, with the password it was given — set
|
|
every time, not only on creation, or a rotation reports success and changes nothing
|
|
2. **everything this provisioner made that is no longer asked for is removed.** A consumer that
|
|
goes away otherwise leaves a working login behind for ever, and nothing says so
|
|
|
|
Step 2 is the half usually missing, and it is the same rule the host follows about removing what
|
|
it declared and no longer declares.
|
|
|
|
The reference implementation lives in `mesh-control/examples/postgres-provisioner`, because that
|
|
is where the contract is defined and where the language is already set up to read it. The lab's
|
|
job is the other half: raising a real PostgreSQL and proving that what the mesh delivered becomes
|
|
a login that works, a rotation that takes effect, and a revocation that bites.
|
|
|
|
Set `MESH_LAB_PROVISIONER` to a built one to run those.
|