Feature 57 (config and provisioning): modules declare config and provisions, their files rendered from templates; synced with main (issues of feature 57, 874)
This commit is contained in:
+132
-17
@@ -76,17 +76,19 @@ Two boxes on the mesh today, both over X25519:
|
||||
What the vault uses:
|
||||
|
||||
- **Its answers to `get` are the ADR 0206 box, for every asker, a node included.** The asker sends its
|
||||
public key as SPKI PEM. A node's provisioner client (V4) needs no second key: its node's sealing key is
|
||||
X25519 already, so it sends that key's public half wrapped as SPKI and opens the answer with the private
|
||||
half wrapped as PKCS #8 (`TestANodesSealingKeyOpensTheAnswer` does exactly this). A raw 32-byte key is
|
||||
refused: a raw private key looks the same, and the vault takes no private key.
|
||||
public key as SPKI PEM. A node's provisioner client (V4) asks with a key of its own, made when it starts
|
||||
and kept in its memory alone: it runs under an account of its own and cannot read the node-engine's sealing
|
||||
key, which is root's. A node's sealing key would open an answer too, being X25519 (wrapped as SPKI and PKCS #8,
|
||||
`TestANodesSealingKeyOpensTheAnswer`), for the node-engine's own asks. A raw 32-byte key is refused: a raw
|
||||
private key looks the same, and the vault takes no private key.
|
||||
- **Its operator copies are the NaCl anonymous box**, to the operator key as it is made today, so the
|
||||
operator's recovery opens them with the key and the command it already has.
|
||||
- **The at-rest copy is the ADR 0206 box**, to the vault's own key.
|
||||
|
||||
The NaCl box stays on the node-engine only for what the controller seals into declarations, until the last
|
||||
module has moved (ADR 0349 §17). For the core's own credentials that travel in a declaration sealed by the
|
||||
vault (ADR 0349 §16), V4 adds opening the ADR 0206 box with the same sealing key to the node-engine.
|
||||
vault (ADR 0349 §16), the node-engine is to open the ADR 0206 box with the same sealing key; that is not built
|
||||
yet (issue 647 leaves it to its own issue).
|
||||
|
||||
## The seat's verb
|
||||
|
||||
@@ -102,15 +104,107 @@ bounded: rolled at 1 MiB into `handed.jsonl.1` … `.3`, the oldest dropped, eac
|
||||
journal and counted in `vault_status`. `secret_handed` reads it.
|
||||
|
||||
`vault_status` says what needs a person: operator copies still sealed to an earlier key, an operator key in
|
||||
the export the vault refused, and how often the record was rolled. **It is not yet a condition**: a health
|
||||
check is declared on a long-running resource (ADR 0240), and the vault is a bundle the tool runner loads, so
|
||||
nothing asks it on its own; until it runs as a resource of its own (V3's review) these reach a person
|
||||
through the journal and this tool only.
|
||||
the export the vault refused, and how often the record was rolled.
|
||||
|
||||
**Who may ask, today: nobody.** The rule (ADR 0349 §11) is a node's provisioner client under a bus account
|
||||
of its own, for what its node's declaration names, and a module under its own account (issue 569). Neither
|
||||
exists yet, so the running vault refuses every asker; the tests set a stand-in list. The real rule goes
|
||||
behind the `Askers` interface in `askers.go` (design 57's V4 and P3).
|
||||
**A failing re-seal is a condition** (ADR 0366, issue 738). The vault re-seals its operator copies to the
|
||||
operator key once a minute. `vault_health` answers unhealthy once that has failed on three passes in a row,
|
||||
saying what failed and why, and healthy again after one pass succeeds. The manifest declares it as the
|
||||
health of the vault's own runtime (`runtime-health`), which the controller puts on `mesh-vault.own-runtime`;
|
||||
the node-engine asks it every minute and the controller raises `module.mesh-vault.<machine>.unhealthy` from
|
||||
it, cleared on the first healthy answer. An operator key in the export the vault refuses fails the pass too.
|
||||
How often the record was rolled still reaches a person through the journal and `vault_status` only.
|
||||
|
||||
**Who may ask: a node's provisioner client, for what its plan names** (ADR 0349 §11, design 57 V4, issue 647;
|
||||
`askers.go`). The caller is `provisioner.<node>`, the client's own account (never the tool runner's, which an agent
|
||||
on that machine shares), and the secret is `own/<node>/<module>/<name>` for that same machine, where the
|
||||
controller's assignment `<node>.<module>` (the assignments mirror, which the manifest `reads`) carries the
|
||||
provisioner's plan and the plan's `secrets` name it; or a value a provider's software issued for one holder,
|
||||
`provision/<provider>/<provision>/<node>/<module>`, where that plan's `provisions` name the provision from that
|
||||
provider with a credential's `name` (issue 757; an entry naming only the provider serving a provision, issue 760,
|
||||
hands nothing). The vault cannot read a node's declaration, so the plan the
|
||||
controller carries beside it is what the declaration names, as the vault reads it. A provision's one credential,
|
||||
`provision/<provider>/<provision>`, is handed to the machines the vault made it for (below) while the holder there
|
||||
is handed to the provisioner: the provider's module, or a consumer whose plan still names it from that provider. A
|
||||
module not handed to the provisioner, another machine's secret or holder value, and every other caller are refused,
|
||||
and each refusal is recorded with why. A module's own account (issue 569) is not answered yet.
|
||||
|
||||
**The controller, for the core's own credentials, sealed only to their machine** (ADR 0349 §16, issue 763;
|
||||
`core.go`). The bus and mesh-provisioner need their files before any client runs, so their credentials travel in
|
||||
the declaration sealed by the vault to their machine, which the controller carries and cannot open. The caller
|
||||
`controller` is answered `own/<node>/<module>/<name>` only for a module on the core's fixed list (`nats`,
|
||||
`mesh-provisioner`, `mesh-provisioner-client`, the controller's list too), and only when the key it names is that
|
||||
machine's sealing key as its own node-engine stated it in its own name, in `move-in`'s first step (`sealing_key`).
|
||||
A machine whose engine stated none, any other key, any other module and any other name are refused and recorded.
|
||||
|
||||
## Taking values in: `accept`, `secret-ask`, `issued`, `rotate`
|
||||
|
||||
Design 57's V2 (issue 675; ADR 0349 clauses 8 to 10, ADR 0277 §22–§27 and ADR 0305 with the vault in the
|
||||
controller's place). `intake.go`.
|
||||
|
||||
**From the controller alone; the vault asks the desk and the provider itself.** The controller alone asks these
|
||||
verbs, as the runtime proves its caller. Each value comes in sealed to an X25519 key the vault made for that one
|
||||
call (raw, base64 — the NaCl anonymous box the desk's prompt and a provider's tool already seal to), and opens with
|
||||
the private half, which lives in this process only and is forgotten once used or after two minutes. A value sealed
|
||||
to any other key is refused, and a call is answered once.
|
||||
|
||||
**The vault's own account** (issue 723). The vault runs as `mesh-vault` in a runtime of its own (`runs-as`, ADR
|
||||
0259 §8), on a bus account of its own (`<node>.mesh-vault`, from its own secret `broker`). The bus lets that
|
||||
account call the desks' prompt (`node-launcher.secret` on any machine) and each issuing provider's tool in its own
|
||||
name, and nothing else outside its seat (mesh-controller's `internal/broker` grants, proven on a real server). So
|
||||
the prompt and the tool know the vault asks, and the controller carries neither the key nor the sealed value. The
|
||||
controller still asks them itself for a module filled the old way, until R1 (issue 682) removes that path; they
|
||||
answer both until then. Everything the vault keeps is its account's: its directories, the export it keeps, the
|
||||
grant files the controller still writes for it.
|
||||
|
||||
- `accept` — two steps: node, module, name (a module's own secret) or provider, provision (a provision's one
|
||||
credential), and `by`; then `call` and `sealed`. The controller's terminal seals the value it read to the call's
|
||||
key before it leaves the terminal. Kept with origin `accepted`; an accepted provision credential is never remade
|
||||
(`rotate` refuses it); a value a provider's software issued is not taken by `accept`.
|
||||
- `secret-ask` — one step: node, module, name, desk and `asked_by` (who asked the controller, as the bus named its
|
||||
caller). The ask is recorded in `asks.jsonl` before anything else, and refused while another ask for the same
|
||||
secret is open (two minutes at most) or when three were made in the last hour. The vault then asks the desk's
|
||||
prompt itself, naming who asked as the record has it, with `timeout_seconds` 20. The answer is how it ended
|
||||
(`given`, dismissed, timed-out, empty, refused, failed) and who asked; how each ask ended is recorded.
|
||||
`secret_asks` reads the record. The hand-act log stays the controller's: it records the act once the vault has
|
||||
taken the value.
|
||||
- `issued` — one step: provision, provider (machine), module and tool. The vault asks that tool on the provider's
|
||||
machine itself; the value is kept as the provision's one credential with origin `issued`. The same value again
|
||||
writes nothing, says nothing and keeps the generation (ADR 0305 §5).
|
||||
- `issued` for each holder apart (issue 749; ADR 0349 clause 11, ADR 0365 clause 4: the forge's read token per agent
|
||||
account). The controller names the provision's holders from that provider (`holders`, each a node and a consuming
|
||||
module), and the vault asks the tool with them. The tool answers either one value (`sealed`, kept as above) or one
|
||||
per holder (`holders`: node, module and `sealed` each, all sealed to the call's key). Each is kept as
|
||||
`provision/<provider node>/<provision>/<holder node>/<holder module>`, origin `issued`, and the same value again for
|
||||
a holder writes nothing for it. The answer is taken whole or not at all: a holder not asked about, one named twice, a
|
||||
value not sealed to the call or an empty one keeps nothing. A holder asked about and not answered is said
|
||||
(`not_answered`) and keeps nothing. **`get` hands a holder's value to that holder alone**, its machine's provisioner
|
||||
client (`provisioner.<node>`) or the module under an account of its own (`<node>.<module>`), whatever the rule for
|
||||
who may ask allows; every other caller is refused and the refusal recorded.
|
||||
`TestOnARealBusTwoHoldersOnTwoMachinesEachGetOnlyTheirOwnIssuedValueAndNoneCrossesInTheClear` proves it on a NATS
|
||||
server with a stand-in forge.
|
||||
- `rotate` — a module's own secret or a provision's credential, made anew. Refused for an `issued` value (the
|
||||
software rotates it, ADR 0305 §9) and an accepted provision credential (clause 8). From the controller alone,
|
||||
which knows whether the module's secrets are filled from the vault yet, and with a `why`, said in the vault's
|
||||
log. Who asked is the `by` the controller names, beside the controller; both are kept (`changed-by`,
|
||||
`changed-via`). The controller's `rotate <provision>` forwards here for a provider filled the new way (issue
|
||||
767): a shared value is made anew for every holder, never for one consumer alone; for a provider whose consumers
|
||||
keep a login each, `consumer` and `consumer_module` (`--consumer`, `--module`) name the one consumer whose own
|
||||
login is remade, and no other is touched.
|
||||
|
||||
Names in the store: `own/<node>/<module>/<name>`, `provision/<provider node>/<provision>` and, for a value issued for
|
||||
one holder, `provision/<provider node>/<provision>/<holder node>/<holder module>`. V3 copies the
|
||||
controller's rows in under these names.
|
||||
|
||||
**Which modules reach the vault this way.** The controller sends a module's verbs here only when its secrets are
|
||||
filled the new way (its machine's provisioner client asks the vault for them, V4); every other module keeps the
|
||||
controller's path, so no value lands where its machine never looks (ADR 0349 clause 17; the release lead's decision
|
||||
on issue 675).
|
||||
|
||||
**What the issuing provider's test is here.** `TestAnIssuingProviderAnswersOnlyTheVaultsCallSealedToTheVault` and
|
||||
`TestTheVaultAsksTheIssuingToolItselfAndOnlyASealedValueCrosses` use a stand-in provider that answers the vault's
|
||||
account (and, until R1, the controller) and no other caller, and only sealed to the call's key. plex's own tool
|
||||
answers the controller alone today; it must also answer `<node>.mesh-vault` when it moves with the media catalogue,
|
||||
after release 57.
|
||||
|
||||
## Events
|
||||
|
||||
@@ -149,11 +243,28 @@ and a holder's ledger file removed only through a person's delete, as here. The
|
||||
lives: the harness kept it in its process, so a restart forgot it (ADR 0230 §13); here it is in the ledger
|
||||
file.
|
||||
|
||||
**The credential a provider shares with its consumers** (ADR 0349 clause 8, ADR 0158 §4–§6, design 57 V5, issue
|
||||
677; `pair.go`). One value per provider assignment and provision, `provision/<provider>/<provision>`. Once a minute
|
||||
the vault reads every plan in the assignments mirror; each plan's `provisions` names the provider serving each
|
||||
provision its module requires (issue 760). A consumer binds when an entry appears and unbinds when it goes or its
|
||||
assignment goes; on either, the vault makes one new value for every holder together and says `secret-changed`, and
|
||||
each holder's client asks again. What the move brought in (V3) is adopted when every pair row of a provider and
|
||||
provision shares one generation stamp and one value (ADR 0158's shared form), with its origin, and nothing is
|
||||
remade. Rows without a stamp (ADR 0048, a login per consumer) are never made one value (ADR 0158 §11): `rotate` of
|
||||
the provision remakes each consumer's own apart, and each consumer's machine is handed only its own row,
|
||||
`pair/<provider>/<provision>/<consumer>/<module>[/<local>]` (the provider's machine all of them). A provider the
|
||||
vault holds no row of gets its first value in the form each plan entry names (`credential-form`, from the provider's
|
||||
offer, issue 768): `shared`, one value for every holder; `pair`, a login of its own for each consumer bound; `issued`
|
||||
or `given` are never made. No form named, plans that disagree, or a form it does not know: nothing is made. An accepted or issued value is never remade, only
|
||||
its holders recorded. A provision whose plans name a credential per holder (issue 757) has no shared value. **No
|
||||
value is made that a holder would never get:** while the provider's assignment, or a consumer the move brought in,
|
||||
is still filled the old way (no plan), every make, remake and rotation is held back and the reason said in the
|
||||
log. Who holds each value is kept in the store's `shared/` directory, never a value.
|
||||
|
||||
## What comes later
|
||||
|
||||
Taking values in (`secret accept`, `secret-ask`, `issued`, `rotate`) is V2; the controller's secrets move
|
||||
in with V3; nodes ask with V4; pair credentials are minted here with V5. Until V3 the vault's store is
|
||||
empty and the controller still serves every secret.
|
||||
Taking values in is built (above); the controller's secrets move in with V3; nodes' provisioner clients ask with V4,
|
||||
for each module handed to the provisioner; providers answer their consumers with B1–B3.
|
||||
|
||||
## Tests
|
||||
|
||||
@@ -161,4 +272,8 @@ empty and the controller still serves every secret.
|
||||
|
||||
`vault_test.go` is ADR 0349's vault test list as far as this package goes: no bucket holds a secret; no
|
||||
private key is accepted; every secret has an operator-sealed copy; `get` answers only an allowed asker,
|
||||
sealed to its key; `secret-changed` carries no value.
|
||||
sealed to its key; `secret-changed` carries no value. `intake_test.go` is ADR 0349's `secret-ask` test as far as
|
||||
the vault answers it (recorded before the prompt, one open ask per secret and three an hour, the asker named as
|
||||
the bus named it, the typed value opened only here), the issuing provider's test with a stand-in provider (ADR 0305
|
||||
§13's checks that belong to the vault: nothing rewritten for a value held, `rotate` refused, no other way in), and
|
||||
accept and rotate. `followups_test.go` holds V1's review follow-ups.
|
||||
|
||||
Reference in New Issue
Block a user