ADR 0113 and to-be 27: address the review of the vault rework
Two decisions taken with the author: - Genesis delivers and the vault adopts. The vault cannot run first — it is built on the runtime base the installation makes after the store, broker and controller, and it learns its work over the bus. Genesis generates the foundation's first shared secrets, seals them to the operator key, and delivers them to the vault through the path an operator's value takes; from then on the vault holds and rotates them. This answers ADR 0085's own reason for rejecting vault-only minting, which 0113 now names instead of stepping around. - Rotation re-confirms on every pass. An applier repeats its confirmation until acknowledged, so a lost message costs one pass; an applier that stops after applying locks readers out until its supervised restart, and that window is stated and shown, not claimed away. Fixes: - Scope: a shared secret is made by the vault; a private key (node sealing keys, the operator's key, the certificate authority) is made where it is used. The inventory adds the makers the first version missed: node and builder broker passwords, and enrolment tokens. - Broker accounts are created by the broker's provisioner, not the controller, so the controller never holds their plaintext; mesh-broker delivers amqp again — one broker per mesh — and only mesh-store delivers nothing. - secret is a reserved provision: only the mesh-vault holder may provide it, and no pin routes around it. - A secret's contract says whether a recipient applies it or reads it at start; appliers are never restarted for it, init-only secrets are applied, and confirmation is to-be 13's standard. - Operator secrets are one rule everywhere: a secret requirement answered by the vault (0112 no longer says otherwise). A data provider's adapter may return fields; the data-return check names a lab consumer. - 'Holder' now means a seat's holder only; a secret has recipients.
This commit is contained in:
@@ -92,13 +92,21 @@ and which assignment holds it, including seats nobody holds. An unheld seat is a
|
|||||||
has no X", not an error.
|
has no X", not an error.
|
||||||
|
|
||||||
**A seat delivers a provision only where the mesh has one answer for everyone.** That is a design
|
**A seat delivers a provision only where the mesh has one answer for everyone.** That is a design
|
||||||
decision about the provision, not about the seat. The artifact store, the npm registry, git and the
|
decision about the provision, not about the seat. The broker, the artifact store, the npm registry,
|
||||||
vault are each one per mesh by their own records, so their seats deliver them. The store and the
|
git and the vault are each one per mesh by their own records, so their seats deliver them. The store
|
||||||
broker are not: [to-be 23](../03-DESIGN/01-to-be/23-choosing-a-provider.md) has each node running its
|
is not: [to-be 23](../03-DESIGN/01-to-be/23-choosing-a-provider.md) has each node running its own
|
||||||
own stores, with a consumer served by the one on its own machine. So `mesh-store` and `mesh-broker`
|
stores, with a consumer served by the one on its own machine, and the foundation's store is the
|
||||||
keep guarding that the foundation's own server is singular, and deliver nothing. Were they to
|
controller's own memory, provider to nobody ([to-be 21](../03-DESIGN/01-to-be/21-the-installation-in-full.md)).
|
||||||
|
So `mesh-store` guards that the foundation's store is singular, and delivers nothing. Were it to
|
||||||
deliver, every database consumer on every node would be sent to the control-node's store.
|
deliver, every database consumer on every node would be sent to the control-node's store.
|
||||||
|
|
||||||
|
**A seat may reserve its provision.** Where a second provider would break a rule the provision exists
|
||||||
|
for, only the seat's holder may provide it at all: the parser refuses anyone else, and a pin cannot
|
||||||
|
choose anyone else. `secret` is the one reserved provision. The vault is one per mesh because a second
|
||||||
|
one *"would be a second place to lose"* ([ADR 0085](0085-a-secret-is-a-provision.md), as amended), and
|
||||||
|
a second `secret` provider is exactly that, whether a pin chose it or not. Every other delivered
|
||||||
|
provision may have second providers, which a pin can choose.
|
||||||
|
|
||||||
**The first set is the twelve seats already claimed, plus two.** Thirteen claims are in use, and
|
**The first set is the twelve seats already claimed, plus two.** Thirteen claims are in use, and
|
||||||
they name twelve seats because two alternative modules claim `the-resolver-configuration`. This
|
they name twelve seats because two alternative modules claim `the-resolver-configuration`. This
|
||||||
record admits every seat the catalogue and the controller claim today, so no module is refused by
|
record admits every seat the catalogue and the controller claim today, so no module is refused by
|
||||||
@@ -108,8 +116,8 @@ it:
|
|||||||
|---|---|---|---|---|
|
|---|---|---|---|---|
|
||||||
| `mesh-controller` | mesh | — | `mesh-controller` | [0079](0079-the-foundation-seats-are-named-after-their-servers.md) |
|
| `mesh-controller` | mesh | — | `mesh-controller` | [0079](0079-the-foundation-seats-are-named-after-their-servers.md) |
|
||||||
| `mesh-store` | mesh | — | `postgres` | [0079](0079-the-foundation-seats-are-named-after-their-servers.md) |
|
| `mesh-store` | mesh | — | `postgres` | [0079](0079-the-foundation-seats-are-named-after-their-servers.md) |
|
||||||
| `mesh-broker` | mesh | — | `lavinmq` | [0079](0079-the-foundation-seats-are-named-after-their-servers.md) |
|
| `mesh-broker` | mesh | `amqp` | `lavinmq` | [0079](0079-the-foundation-seats-are-named-after-their-servers.md) |
|
||||||
| `mesh-vault` | mesh | `secret` | nothing yet: `mesh-vault` claims it | this record, for [issue 106](../04-ISSUES/106-the-vault-claims-no-seat/00-report.md) |
|
| `mesh-vault` | mesh | `secret`, reserved | nothing yet: `mesh-vault` claims it | this record, for [issue 106](../04-ISSUES/106-the-vault-claims-no-seat/00-report.md) |
|
||||||
| `the-artifact-store` | mesh | `artifact-store` | `distribution` | [0075](0075-two-stores-and-which-provides-what.md) |
|
| `the-artifact-store` | mesh | `artifact-store` | `distribution` | [0075](0075-two-stores-and-which-provides-what.md) |
|
||||||
| `the-catalogue` | mesh | — | `mesh-catalog` | this record |
|
| `the-catalogue` | mesh | — | `mesh-catalog` | this record |
|
||||||
| `npm-package-registry` | mesh | `npm-package-registry` | `gitea` | [0109](0109-a-package-registry-seat-is-one-per-ecosystem.md) |
|
| `npm-package-registry` | mesh | `npm-package-registry` | `gitea` | [0109](0109-a-package-registry-seat-is-one-per-ecosystem.md) |
|
||||||
@@ -166,7 +174,8 @@ question real.
|
|||||||
| A claim outside the set is refused | Manifest-validation tests for an unknown seat, the wrong scope, and a delivering seat whose claimant does not provide. |
|
| A claim outside the set is refused | Manifest-validation tests for an unknown seat, the wrong scope, and a delivering seat whose claimant does not provide. |
|
||||||
| Every module in use claims a seat in the set | A controller test parses every catalogue manifest and fails on any refused claim. The lab's beds read the same manifests ([ADR 0089](0089-a-bed-reads-the-catalogue-it-proves.md)). |
|
| Every module in use claims a seat in the set | A controller test parses every catalogue manifest and fails on any refused claim. The lab's beds read the same manifests ([ADR 0089](0089-a-bed-reads-the-catalogue-it-proves.md)). |
|
||||||
| The holder answers for a provision its seat delivers | Resolution tests: two providers with the seat held; a pin overriding the seat; a second provider on the consumer's own machine, where the holder still answers; the seat unheld with two providers, and with **one** provider, both refused naming the seat. |
|
| The holder answers for a provision its seat delivers | Resolution tests: two providers with the seat held; a pin overriding the seat; a second provider on the consumer's own machine, where the holder still answers; the seat unheld with two providers, and with **one** provider, both refused naming the seat. |
|
||||||
| A seat delivers only a one-per-mesh provision | A controller unit test: `mesh-store` and `mesh-broker` deliver nothing, so a database consumer is still served by co-location. |
|
| A seat delivers only a one-per-mesh provision | A controller unit test: `mesh-store` delivers nothing, so a database consumer is still served by co-location, and `mesh-broker` delivers `amqp`. |
|
||||||
|
| A reserved provision has no other provider | The parser refuses a module providing `secret` without claiming `mesh-vault`, and resolution refuses a pin on a `secret` requirement. |
|
||||||
|
|
||||||
## References
|
## References
|
||||||
|
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ unresolved requirement and what could answer it, all at once.
|
|||||||
| **another module** | a database, a bucket, a vhost, a secret, a route | provisions and bindings |
|
| **another module** | a database, a bucket, a vhost, a secret, a route | provisions and bindings |
|
||||||
| **the node's host** | a directory, a port, facts about the machine | resource paths, `${port:}`, `${machine:}`, facts |
|
| **the node's host** | a directory, a port, facts about the machine | resource paths, `${port:}`, `${machine:}`, facts |
|
||||||
| **the mesh** | the module's identity and names, and the delivery of every answer | derived logins and generated names; the controller's delivery |
|
| **the mesh** | the module's identity and names, and the delivery of every answer | derived logins and generated names; the controller's delivery |
|
||||||
| **the operator, through the assignment** | a value a person chooses: a public name, a greeting, an external key | settings, carried literals |
|
| **the operator, through the assignment** | a value a person chooses that is not secret: a public name, a greeting, a number of workers | settings, carried literals |
|
||||||
|
|
||||||
A module provider is chosen as [ADR 0084](0084-which-provider-serves-a-consumer.md) and
|
A module provider is chosen as [ADR 0084](0084-which-provider-serves-a-consumer.md) and
|
||||||
[ADR 0110](0110-a-seat-is-a-module-assignment-from-a-closed-set.md) say: a pin, then the holder of a
|
[ADR 0110](0110-a-seat-is-a-module-assignment-from-a-closed-set.md) say: a pin, then the holder of a
|
||||||
@@ -77,14 +77,14 @@ A host provider is always the module's own node, because a host path or a port m
|
|||||||
other. An operator value is the assignment's, or the requirement's default, or unresolved.
|
other. An operator value is the assignment's, or the requirement's default, or unresolved.
|
||||||
|
|
||||||
**A person's value stays cheap.** An operator requirement's contract is a type and, optionally, a
|
**A person's value stays cheap.** An operator requirement's contract is a type and, optionally, a
|
||||||
default. It needs no provider module, no grant and no credential. An operator value that is secret,
|
default. It needs no provider module, no grant and no credential.
|
||||||
like an external API key, is still the operator's: the vault is where it is *kept*, as an
|
|
||||||
operator-delivered value ([ADR 0092](0092-an-operator-delivers-a-pair-credential.md)), not who
|
|
||||||
provides it.
|
|
||||||
|
|
||||||
**Every secret is made by the vault, and a provider answers with resources and data**
|
**Every secret is a `secret` requirement, answered by the vault**, with no exception by kind
|
||||||
([ADR 0113](0113-the-vault-makes-every-secret.md)). A provider that needs a secret for a consumer
|
([ADR 0113](0113-the-vault-makes-every-secret.md)). An external API key an operator chooses is no
|
||||||
requires it from the vault, like any consumer. The mesh carries every answer back.
|
different: the operator delivers it to the vault ([ADR 0092](0092-an-operator-delivers-a-pair-credential.md)),
|
||||||
|
and the module requires a `secret` like any other. A provider that needs a secret for a consumer
|
||||||
|
requires it from the vault, like any consumer, and answers with resources and data. The mesh carries
|
||||||
|
every answer back.
|
||||||
|
|
||||||
**A directory is a host provision.** Its contract is the owner and mode the module needs, including
|
**A directory is a host provision.** Its contract is the owner and mode the module needs, including
|
||||||
the owner its image expects ([ADR 0107](0107-persistent-data-is-a-directory-bind-never-a-named-volume.md)).
|
the owner its image expects ([ADR 0107](0107-persistent-data-is-a-directory-bind-never-a-named-volume.md)).
|
||||||
|
|||||||
@@ -6,34 +6,39 @@ deciders: jochen
|
|||||||
reconstructed: false
|
reconstructed: false
|
||||||
---
|
---
|
||||||
|
|
||||||
# 113. The vault makes every secret, a provider makes resources and data, and the mesh carries both
|
# 113. The vault makes every shared secret, a provider makes resources and data, and the mesh carries both
|
||||||
|
|
||||||
## Context
|
## Context
|
||||||
|
|
||||||
**A secret comes into being seven different ways today**, counted across the catalogue and the
|
**A shared secret comes into being many different ways today**, counted across the catalogue and the
|
||||||
controller on 2026-09-25:
|
controller on 2026-09-25:
|
||||||
|
|
||||||
| kind | made by | used by |
|
| kind | made by | used by |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| a credential between a consumer and a provider | the controller | 19 modules |
|
| a credential between a consumer and a provider | the controller | 19 modules |
|
||||||
| a module's own secret (`own-secrets`) | the controller, as a random value nothing owns | 54 modules |
|
| a module's own secret (`own-secrets`) | the controller, as a random value nothing owns | 54 modules |
|
||||||
| a broker account | the controller, but only when a person runs a separate command; otherwise the random value above, which cannot work ([issue 095](../04-ISSUES/095-a-module-assigned-after-genesis-has-no-broker-account/00-report.md)) | 49 modules |
|
| a module's broker account | the controller, but only when a person runs a separate command; otherwise the random value above, which cannot work ([issue 095](../04-ISSUES/095-a-module-assigned-after-genesis-has-no-broker-account/00-report.md)) | 49 modules |
|
||||||
|
| a node's and the builder's broker accounts | the controller, each in its own code path | every node, the builder |
|
||||||
|
| an enrolment token | the controller | every node joining |
|
||||||
| a `secret` from the vault | the controller mints it, and the vault only records it ([ADR 0085](0085-a-secret-is-a-provision.md), as amended) | 6 modules |
|
| a `secret` from the vault | the controller mints it, and the vault only records it ([ADR 0085](0085-a-secret-is-a-provision.md), as amended) | 6 modules |
|
||||||
| a value an operator accepts | a person ([ADR 0092](0092-an-operator-delivers-a-pair-credential.md)) | where accepted |
|
| a value an operator accepts | a person ([ADR 0092](0092-an-operator-delivers-a-pair-credential.md)) | where accepted |
|
||||||
| a licence for model access | a separate controller context with its own store | model consumers |
|
| a licence for model access | a separate controller context with its own store | model consumers |
|
||||||
| the mesh's root secrets | genesis, sealed to the operator key | the foundation |
|
| the foundation's root secrets | genesis, sealed to the operator key | the foundation |
|
||||||
|
|
||||||
**The vault was built to end the second row, and did not.** ADR 0085 says a module's own secret
|
**The vault was built to end the second row, and did not.** ADR 0085 says a module's own secret
|
||||||
*"stops being a generated value that nothing owns"*. 54 modules still use one, and 6 use the vault.
|
*"stops being a generated value that nothing owns"*. 54 modules still use one, and 6 use the vault.
|
||||||
The replacement was added and the old path was never retired. The same has happened to the broker
|
The replacement was added and the old path was never retired.
|
||||||
account, a special case of the second row that fails silently when the separate command is forgotten.
|
|
||||||
|
|
||||||
**Rotation has its own gaps.** To-be 13 makes rotation one command, all-or-nothing, and states the
|
**ADR 0085 considered and rejected making the vault the only maker**, because *"the controller must
|
||||||
window in which a consumer cannot authenticate: the provider has taken the new password, and the
|
mint in order to deliver any provision — the vault's own credential among them"*: the vault cannot
|
||||||
consumer has not yet restarted with it. A consumer restarts only if its definition remembered to say
|
make the credentials that exist before it does. That objection is real, and this record has to answer
|
||||||
so, and a container fed by an env-file is not recreated when that file changes, so it keeps the old
|
it rather than step around it.
|
||||||
value ([issue 103](../04-ISSUES/103-a-container-is-not-recreated-when-a-file-it-reads-changes/00-report.md)).
|
|
||||||
Nothing confirms that the new secret works.
|
**Rotation has gaps.** To-be 13 makes rotation one command, all-or-nothing, with a stated window in
|
||||||
|
which a consumer cannot authenticate. A consumer restarts only if its definition remembered to say so;
|
||||||
|
a container fed by an env-file is not recreated when that file changes
|
||||||
|
([issue 103](../04-ISSUES/103-a-container-is-not-recreated-when-a-file-it-reads-changes/00-report.md));
|
||||||
|
and some secrets are read only when a service first initialises, where a restart changes nothing.
|
||||||
|
|
||||||
**And providers cannot answer with data.** [ADR 0048](0048-a-provider-creates-the-credential-the-mesh-minted.md)
|
**And providers cannot answer with data.** [ADR 0048](0048-a-provider-creates-the-credential-the-mesh-minted.md)
|
||||||
left *"delivering provider-generated data back to a consumer"* to a separate decision. The analytics
|
left *"delivering provider-generated data back to a consumer"* to a separate decision. The analytics
|
||||||
@@ -41,86 +46,112 @@ provider's site id and the DNS provider's record have no way back, and say so in
|
|||||||
|
|
||||||
## Considered Options
|
## Considered Options
|
||||||
|
|
||||||
**1. Keep the controller minting, and tidy the seven paths.** Rejected. The paths are the problem:
|
**1. Keep the controller minting, and tidy the paths.** Rejected. The paths are the problem: each is
|
||||||
each is made, kept, rotated and audited differently, and tidying keeps all seven.
|
made, kept, rotated and audited differently, and tidying keeps them all.
|
||||||
|
|
||||||
**2. Every provider mints its own secrets, with one shared function in the SDK.** Rejected. It makes
|
**2. Every provider mints its own secrets, with one shared function in the SDK.** Rejected. Generation
|
||||||
generation uniform and leaves custody scattered: every provider's machine holds secrets the vault
|
becomes uniform, but custody stays spread over every provider's machine, so rotation, audit and the
|
||||||
never sees, so rotation, audit and the operator's break-glass copies cover only some of them. The
|
operator's break-glass copies cover only some secrets. Each SDK language needs its own implementation.
|
||||||
function would also need a conforming implementation in every language a provider is written in.
|
|
||||||
|
|
||||||
**3. The vault makes every secret, and a provider that needs one requires it, like any consumer.**
|
**3. Raise the vault first at genesis, so it makes even the first secrets.** Rejected. The vault is
|
||||||
Chosen. A provider serving a consumer requires a secret for that consumer from the vault. Secrets
|
built on the shared runtime base, which the installation makes only after the store, the broker and
|
||||||
become provisioning all the way down, with one maker at the bottom.
|
the controller exist, and the vault learns what to answer from the controller over the bus. Running
|
||||||
|
it first means reordering the whole installation and giving the vault a second way of being asked.
|
||||||
|
|
||||||
|
**4. The vault makes every shared secret; genesis delivers the first ones to it.** Chosen. It answers
|
||||||
|
0085's objection with a mechanism the mesh already has: a value delivered to the vault.
|
||||||
|
|
||||||
## Decision
|
## Decision
|
||||||
|
|
||||||
**The vault makes every secret in the mesh.** Generation, to a secret's contract, exists in the vault
|
**There are two kinds of secret, and each has one rule.**
|
||||||
and nowhere else. The controller mints nothing.
|
|
||||||
|
|
||||||
**A provider that needs a secret for a consumer requires it from the vault.** A provision's contract
|
- **A shared secret** is a value more than one party must hold: a password, a token, an API key. **The
|
||||||
declares it: *for each consumer, one secret*. Resolution expands that into one requirement per
|
vault makes every one.** Nothing else in the mesh generates a shared secret.
|
||||||
consumer, named for the consumer. So gitea requiring a database makes the database's provider require
|
- **A private key** is made where it is used and never leaves: a node's sealing key, the operator's
|
||||||
a secret named for gitea, and the vault answers it. The provider's own code does not change. It is
|
key, the mesh's certificate authority. This is not a second way of making secrets. A private key any
|
||||||
handed a login and a password, as it is today.
|
other party ever held would no longer be private.
|
||||||
|
|
||||||
**A secret has holders, and the vault delivers to each.** The database credential has two: the
|
**Every shared secret is a `secret` requirement, answered by the vault:**
|
||||||
provider, which creates the login with it, and the consumer, which presents it. The vault hands it to
|
|
||||||
the mesh, which delivers it to each holder sealed to that holder's node. Plaintext exists in the
|
|
||||||
vault while it is made and on each holder's machine, and nowhere else. The controller carries sealed
|
|
||||||
values it cannot open.
|
|
||||||
|
|
||||||
**Every other secret takes the same path:**
|
- a **credential between a consumer and a provider**. A provision's contract declares *for each
|
||||||
|
consumer, one secret*, and resolution expands it into one requirement per consumer. So gitea
|
||||||
|
requiring a database makes the database's provider require a secret for gitea, and the vault
|
||||||
|
answers it. The provider's own code does not change: it is handed a login and a password, as today;
|
||||||
|
- a module's **own secret**. `own-secrets` is retired;
|
||||||
|
- every **broker account**: a module's, a node's, the builder's. The broker delivers `amqp` through
|
||||||
|
its seat ([ADR 0110](0110-a-seat-is-a-module-assignment-from-a-closed-set.md)), and the broker's own
|
||||||
|
provisioner creates each account from the vault's secret, like any provider. The controller no
|
||||||
|
longer creates accounts, and there is no separate command to forget;
|
||||||
|
- an **enrolment token**;
|
||||||
|
- a **secret operator value**, such as an external API key, which the operator delivers to the vault
|
||||||
|
([ADR 0092](0092-an-operator-delivers-a-pair-credential.md)). A licence's credential is one of these.
|
||||||
|
What the licences context adds, refreshing a token, is provider behaviour, decided in its own record.
|
||||||
|
|
||||||
- a module's **own secret** is a `secret` requirement the vault answers. `own-secrets` is retired;
|
**Only the vault may provide `secret`.** A module providing it must hold the `mesh-vault` seat, and
|
||||||
- a **broker account** is the module's identity on the bus. Its name is the mesh's, its password is a
|
the parser refuses one that does not. A pin cannot route a `secret` requirement anywhere else, because
|
||||||
secret the vault makes, and the controller creates the account with it, as it creates accounts
|
there is nowhere else.
|
||||||
today. There is no separate command to forget;
|
|
||||||
- an **operator's value** that is secret is handed to the vault, which provides it like any other
|
|
||||||
secret. It is never replaced by rotation ([ADR 0092](0092-an-operator-delivers-a-pair-credential.md));
|
|
||||||
- a **licence's** credential is an operator's value the vault keeps. What the licences context adds,
|
|
||||||
refreshing a token and a manager holding the refresh credential, is provider behaviour, decided in
|
|
||||||
its own record.
|
|
||||||
|
|
||||||
**Genesis is the vault's first answer, not an exception.** Genesis raises the vault before anything
|
**A secret has recipients, and the vault delivers to each.** The database credential has two: the
|
||||||
else and asks it for the foundation's secrets: the store's superuser, the broker's admin and its hashed
|
provider, which *applies* it by creating the login, and the consumer, which *presents* it. The vault
|
||||||
form, and the vault's own broker account. The vault answers with the same code it always uses, before
|
hands the value to the mesh sealed to each recipient's node. The controller and the broker carry sealed
|
||||||
the bus exists. Genesis mints nothing itself.
|
values they cannot open.
|
||||||
|
|
||||||
**A provider makes resources and data, and the mesh carries data back.** A provider answers with its
|
**Genesis delivers, and the vault adopts.** The foundation's first shared secrets exist before the
|
||||||
contract's non-secret fields: a site id, a registered name. The mesh delivers them to the consumer as
|
vault can run: the store's superuser, the broker's admin in the hashed form the broker needs, the bus
|
||||||
resolved values. Who a consumer is stays the mesh's: a provider makes what a consumer is *given*,
|
accounts of the temporary controller and of the vault itself, and the first enrolment token. Genesis
|
||||||
never what it is *called* ([ADR 0049](0049-a-consumers-identity-fits-the-tightest-backend.md)).
|
generates these, seals them to the operator key as today, and **delivers them to the vault when the
|
||||||
|
vault is installed**, through the same path an operator's value takes. From then on the vault holds,
|
||||||
|
audits and rotates them. Unlike an operator's external key, the vault can make their replacements, so
|
||||||
|
they are delivered but replaceable. Genesis is the only thing besides the vault that ever generates a
|
||||||
|
shared secret, once, before the vault exists, and it hands them over.
|
||||||
|
|
||||||
|
**A provider makes resources and data, and the mesh carries data back.** A provider's adapter may
|
||||||
|
answer with its contract's non-secret fields: a site id, a registered name. The mesh delivers them to
|
||||||
|
the consumer as resolved values. Who a consumer is stays the mesh's: a provider makes what a consumer
|
||||||
|
is *given*, never what it is *called* ([ADR 0049](0049-a-consumers-identity-fits-the-tightest-backend.md)).
|
||||||
|
|
||||||
### Rotation
|
### Rotation
|
||||||
|
|
||||||
**It is asked of the vault**, by an operator or by the vault's own policy, such as the maximum age a
|
**It is asked of the vault**, by an operator or by the vault's policy, such as a maximum age in the
|
||||||
secret's contract sets.
|
secret's contract. A delivered value the vault cannot replace, such as an external API key, is not
|
||||||
|
rotated by the vault: rotating it means an operator delivering a new one.
|
||||||
|
|
||||||
**It is provider-first.** The vault delivers the new value first to the holders that *accept* it,
|
**A secret's contract says how each recipient takes a new value:**
|
||||||
such as the database, and waits for each to confirm it has applied it. Only then does it release the
|
|
||||||
value to the holders that *present* it, such as gitea. A consumer is never sent a value its provider
|
|
||||||
has not accepted, so the window shrinks to the consumer's own restart. A provider that does not
|
|
||||||
confirm holds the rotation: the consumer keeps the old value, which still works, and `status` shows the
|
|
||||||
rotation as waiting on that provider. It is never half-done and never silently abandoned.
|
|
||||||
|
|
||||||
**Restarts are derived, not declared.** The mesh knows which process reads which secret, because the
|
| recipient takes it by | example | what happens on rotation |
|
||||||
definition reads it through its requirement ([to-be 27](../03-DESIGN/01-to-be/27-a-module-requires-the-mesh-resolves.md)).
|
|---|---|---|
|
||||||
When a secret changes, the host restarts every process that reads it, and recreates a container whose
|
| **applying** it | a provider creating the login; the broker's provisioner updating an account; the store's own provisioner changing its superuser | its provisioner applies the new value; it is never restarted for it |
|
||||||
env-file carries it. No definition has to remember `restart-on` for a secret.
|
| **reading it at start** | a consumer reading its password when it starts | the host restarts it, or recreates a container whose env-file carries it |
|
||||||
|
|
||||||
**It is confirmed.** A rotated secret is shown as unconfirmed until each consumer has restarted with
|
A secret read only when a service first initialises cannot be rotated by a restart. Its contract marks
|
||||||
it and, where its definition declares a health check, passed it. Delivered is not the same as working,
|
it applied, and a provisioner makes the change. The host derives which recipients read a secret at
|
||||||
and the mesh says which one it knows.
|
start from the requirement their definition reads it through, so no definition declares a restart for
|
||||||
|
a secret.
|
||||||
|
|
||||||
|
**It is applier-first.** The vault delivers the new value first to the recipients that apply it. Each
|
||||||
|
applies it, verifies that the new value authenticates and the old one no longer does, and confirms.
|
||||||
|
**It repeats that confirmation on every reconcile pass until the vault acknowledges it**, so a lost
|
||||||
|
message costs one pass. Only after every applier has confirmed does the vault release the value to the
|
||||||
|
recipients that read it at start.
|
||||||
|
|
||||||
|
**The remaining window is stated.** If an applier applies the new value and its provisioner stops
|
||||||
|
before confirming, the recipients that present the secret are locked out until the provisioner runs
|
||||||
|
again, because the old value no longer works and they have not been sent the new one. A provisioner is
|
||||||
|
supervised and restarted when it exits, so the window is bounded by that restart. The mesh shows the
|
||||||
|
rotation as waiting on that applier for as long as it lasts, never as done.
|
||||||
|
|
||||||
|
**It is confirmed.** A rotation is shown as unconfirmed until every applier has confirmed, and every
|
||||||
|
recipient that reads at start has restarted with the new value and passed its health check, where its
|
||||||
|
definition declares one.
|
||||||
|
|
||||||
| step | who |
|
| step | who |
|
||||||
|---|---|
|
|---|---|
|
||||||
| asks | an operator, or the vault's policy |
|
| asks | an operator, or the vault's policy |
|
||||||
| makes the value | the vault |
|
| makes the value | the vault |
|
||||||
| carries it | the controller, sealed, provider first |
|
| carries it | the controller, sealed, appliers first |
|
||||||
| applies it on the provider | the provider's provisioner, which confirms |
|
| applies it | each applier's provisioner, which verifies and confirms on every pass until acknowledged |
|
||||||
| applies it on the consumer | the host, restarting or recreating what reads it |
|
| takes it at start | the host, restarting or recreating what reads it |
|
||||||
| confirms it works | the consumer's restart and health check, shown in `status` |
|
| confirms it | applier confirmations, then restarts and health checks, shown in `status` |
|
||||||
|
|
||||||
## What this changes in earlier records
|
## What this changes in earlier records
|
||||||
|
|
||||||
@@ -129,53 +160,61 @@ On acceptance, each of these is superseded or amended by this record, not edited
|
|||||||
- [ADR 0048](0048-a-provider-creates-the-credential-the-mesh-minted.md) is superseded: the controller
|
- [ADR 0048](0048-a-provider-creates-the-credential-the-mesh-minted.md) is superseded: the controller
|
||||||
no longer mints a provider's credential; the vault makes it. That a provider is handed its
|
no longer mints a provider's credential; the vault makes it. That a provider is handed its
|
||||||
credential and seals nothing stands.
|
credential and seals nothing stands.
|
||||||
- [ADR 0085](0085-a-secret-is-a-provision.md) is amended: the vault makes a module's own secret rather
|
- [ADR 0085](0085-a-secret-is-a-provision.md) is amended: the vault makes every shared secret, own
|
||||||
than recording one the controller minted, own secrets are retired, and genesis asks the vault for
|
secrets are retired, and its rejection of vault-only minting is answered by genesis delivering the
|
||||||
the root secrets. "The vault stores no plaintext, ever" stands.
|
first secrets. "The vault stores no plaintext, ever" stands.
|
||||||
- [ADR 0092](0092-an-operator-delivers-a-pair-credential.md) is amended: an operator delivers a secret
|
- [ADR 0092](0092-an-operator-delivers-a-pair-credential.md) is amended: an operator delivers a secret
|
||||||
to the vault.
|
to the vault, and genesis delivers the foundation's first secrets the same way.
|
||||||
- [To-be 13](../03-DESIGN/01-to-be/13-credentials-and-their-rotation.md) and
|
- [ADR 0043](0043-a-module-broker-account-is-scoped-by-emits-and-consumes.md) is amended: a broker
|
||||||
[to-be 24](../03-DESIGN/01-to-be/24-the-secrets-vault.md) are amended: rotation is provider-first,
|
account is created by the broker's provisioner, not the controller. Its scoping stands.
|
||||||
derived and confirmed, and the vault is the only maker.
|
- [To-be 13](../03-DESIGN/01-to-be/13-credentials-and-their-rotation.md),
|
||||||
|
[to-be 21](../03-DESIGN/01-to-be/21-the-installation-in-full.md) and
|
||||||
|
[to-be 24](../03-DESIGN/01-to-be/24-the-secrets-vault.md) are amended: rotation is applier-first,
|
||||||
|
derived and confirmed; genesis delivers its secrets to the vault; the vault is the only maker.
|
||||||
- [Issue 103](../04-ISSUES/103-a-container-is-not-recreated-when-a-file-it-reads-changes/00-report.md)
|
- [Issue 103](../04-ISSUES/103-a-container-is-not-recreated-when-a-file-it-reads-changes/00-report.md)
|
||||||
becomes a prerequisite: rotation cannot be trusted while a changed env-file leaves a container on
|
becomes a prerequisite: rotation cannot be trusted while a changed env-file leaves a container on
|
||||||
its old value.
|
its old value.
|
||||||
|
|
||||||
## Consequences
|
## Consequences
|
||||||
|
|
||||||
- **The vault is on the path of every new or rotated secret.** Today the controller is, and both run
|
- **The vault is on the path of every new or rotated shared secret.** Today the controller holds that
|
||||||
on the control-node, so no new single point of failure appears. It is stated rather than implied.
|
place, on the same node. A secret can no longer be made while the vault is down.
|
||||||
- Resolution expands per-consumer requirements from a provision's contract. The contract declares
|
- Resolution expands per-consumer requirements from a provision's contract. The contract declares
|
||||||
them, never the provider's code, so what a provider requires stays predictable from the catalogue.
|
them, never the provider's code, so what a provider requires stays predictable from the catalogue.
|
||||||
- The SDK's provider loop gains one thing: confirming that a rotation was applied. Adapters are
|
- The SDK's provider loop gains repeated confirmation of an applied rotation. A credential provider's
|
||||||
unchanged.
|
adapter is unchanged. A data provider's adapter gains a return value.
|
||||||
- The mesh gains the way back from provider to consumer, for confirmations and for data.
|
- The broker's provisioner gains every bus account, and the controller loses five separate places it
|
||||||
- 54 modules move from own secrets to vault requirements, and the broker account stops needing a
|
generates a secret today.
|
||||||
separate command.
|
- 54 modules move from own secrets to vault requirements.
|
||||||
- **What got harder:** a secret can no longer be made when the vault is down, where today the
|
- **What got harder:** a rotation waits for its appliers, and an applier that stops mid-rotation locks
|
||||||
controller makes one regardless. And a rotation waits for its provider. Both move failures from
|
presenters out until it restarts. Both are shown, not hidden, and the second is bounded by a
|
||||||
late and silent to early and visible, which is the trade this record makes throughout.
|
supervised restart rather than by someone noticing.
|
||||||
|
|
||||||
## How it is checked
|
## How it is checked
|
||||||
|
|
||||||
| Rule | Checked by |
|
| Rule | Checked by |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Only the vault makes secrets | A controller test: no code path mints a secret. A vault test: the one generation function is the only one, and genesis reaches it through the vault. |
|
| Only the vault generates a shared secret after genesis | A controller test: no code path generates a shared secret. An installer test: genesis generates exactly the foundation's first secrets and delivers them to the vault. |
|
||||||
| A provider's per-consumer secret comes from the vault | A resolution test: a consumer requiring a database expands to a secret requirement named for it, answered by the vault. |
|
| A private key is made where it is used | A test per key: a node's sealing key never leaves the node, and the operator's private key never enters the mesh. |
|
||||||
| A secret reaches each holder sealed to its node | A controller test: each holder's copy opens with that holder's node key and with no other, the controller's included. |
|
| Only the vault provides `secret` | The parser refuses a module providing `secret` without holding `mesh-vault`, and resolution refuses a pin on a `secret` requirement. |
|
||||||
|
| A provider's per-consumer secret comes from the vault | A resolution test: a consumer requiring a database expands to a secret requirement for it, answered by the vault and delivered to both recipients. |
|
||||||
|
| Values are carried sealed | A controller test: each recipient's copy opens with that recipient's node key and no other; neither the controller nor a message on the broker can open one. |
|
||||||
| Own secrets are retired | A catalogue test: no definition declares an own secret, with a declared list of exceptions that shrinks to empty. |
|
| Own secrets are retired | A catalogue test: no definition declares an own secret, with a declared list of exceptions that shrinks to empty. |
|
||||||
| A broker account needs no separate command | A resolution test: assigning a module that speaks on the bus yields its account. |
|
| Bus accounts come from the broker's provisioner | A resolution test: assigning a module that speaks on the bus yields its account, created by the broker's provisioner with no separate command. |
|
||||||
| Rotation is provider-first | A rotation test: the consumer is not sent the new value until the provider confirms; a provider that never confirms leaves the consumer on the old value, shown as waiting. |
|
| An irreplaceable delivered value is not rotated by the vault | A vault test: a rotation request on an operator's external key is refused, naming the operator as its source. |
|
||||||
| Restarts are derived | A host test: changing a secret restarts every process that reads it, and recreates a container whose env-file carries it, with no `restart-on` declared. |
|
| Rotation is applier-first and re-confirmed | A rotation test: presenters are not sent the new value until every applier confirms, and a confirmation lost in transit is repeated on the next pass. |
|
||||||
| Rotation is confirmed | A rotation test: the secret shows as unconfirmed until the consumer restarted and passed its health check. |
|
| Restarts are derived from how a secret is read | A host test: a secret read at start restarts its reader, and recreates a container whose env-file carries it; an applied secret restarts nothing. |
|
||||||
| A provider answers data back | A resolution test: the analytics provider's site id reaches its consumer as a resolved value. |
|
| Rotation is confirmed | A rotation test: an applier confirms only after the new value authenticates and the old one does not, and the rotation shows unconfirmed until every reader restarted and passed its health check. |
|
||||||
|
| A provider answers data back | A lab test with a consumer requiring analytics: the provider's site id reaches it as a resolved value. |
|
||||||
|
|
||||||
## References
|
## References
|
||||||
|
|
||||||
- [ADR 0048](0048-a-provider-creates-the-credential-the-mesh-minted.md): the decision this supersedes,
|
- [ADR 0048](0048-a-provider-creates-the-credential-the-mesh-minted.md): the decision this supersedes,
|
||||||
and the return path it left open
|
and the return path it left open
|
||||||
- [ADR 0085](0085-a-secret-is-a-provision.md), [ADR 0092](0092-an-operator-delivers-a-pair-credential.md),
|
- [ADR 0085](0085-a-secret-is-a-provision.md): the vault, and the objection this record answers
|
||||||
[ADR 0049](0049-a-consumers-identity-fits-the-tightest-backend.md): the vault, the operator, and identity
|
- [ADR 0092](0092-an-operator-delivers-a-pair-credential.md), [ADR 0049](0049-a-consumers-identity-fits-the-tightest-backend.md),
|
||||||
|
[ADR 0043](0043-a-module-broker-account-is-scoped-by-emits-and-consumes.md): delivered values,
|
||||||
|
identity, and broker accounts
|
||||||
- [ADR 0112](0112-a-module-definition-names-no-node-mesh-or-path.md), [to-be 27](../03-DESIGN/01-to-be/27-a-module-requires-the-mesh-resolves.md):
|
- [ADR 0112](0112-a-module-definition-names-no-node-mesh-or-path.md), [to-be 27](../03-DESIGN/01-to-be/27-a-module-requires-the-mesh-resolves.md):
|
||||||
everything a module needs is a requirement
|
everything a module needs is a requirement
|
||||||
- [Issue 095](../04-ISSUES/095-a-module-assigned-after-genesis-has-no-broker-account/00-report.md),
|
- [Issue 095](../04-ISSUES/095-a-module-assigned-after-genesis-has-no-broker-account/00-report.md),
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ python3 00-META/checks/index.py fail if stale
|
|||||||
- **0099** — [A step that runs once names what it reads, and runs again when it changed](0099-a-step-that-runs-once-names-what-it-reads.md)
|
- **0099** — [A step that runs once names what it reads, and runs again when it changed](0099-a-step-that-runs-once-names-what-it-reads.md)
|
||||||
- **0110** — [A seat is a module assignment from a closed set, and it may deliver a provision](0110-a-seat-is-a-module-assignment-from-a-closed-set.md)
|
- **0110** — [A seat is a module assignment from a closed set, and it may deliver a provision](0110-a-seat-is-a-module-assignment-from-a-closed-set.md)
|
||||||
- **0112** — [A module definition names no node, no mesh and no path: everything it needs is a requirement the mesh resolves](0112-a-module-definition-names-no-node-mesh-or-path.md) *(proposed)*
|
- **0112** — [A module definition names no node, no mesh and no path: everything it needs is a requirement the mesh resolves](0112-a-module-definition-names-no-node-mesh-or-path.md) *(proposed)*
|
||||||
- **0113** — [The vault makes every secret, a provider makes resources and data, and the mesh carries both](0113-the-vault-makes-every-secret.md) *(proposed)*
|
- **0113** — [The vault makes every shared secret, a provider makes resources and data, and the mesh carries both](0113-the-vault-makes-every-secret.md) *(proposed)*
|
||||||
|
|
||||||
### How it is built
|
### How it is built
|
||||||
|
|
||||||
|
|||||||
@@ -57,9 +57,10 @@ consumer's own machine does not take over for that consumer. That is not picking
|
|||||||
once, mesh-wide, by assigning the holder, rather than once per consumer by naming it
|
once, mesh-wide, by assigning the holder, rather than once per consumer by naming it
|
||||||
([ADR 0110](../../02-DECISIONS/0110-a-seat-is-a-module-assignment-from-a-closed-set.md),
|
([ADR 0110](../../02-DECISIONS/0110-a-seat-is-a-module-assignment-from-a-closed-set.md),
|
||||||
[26 — The seats](26-the-seats.md)). A named provider still wins over the seat, because a consumer
|
[26 — The seats](26-the-seats.md)). A named provider still wins over the seat, because a consumer
|
||||||
coupled to particular contents has said so. Only provisions the design makes one-per-mesh are
|
coupled to particular contents has said so, except for `secret`, which only the vault may provide.
|
||||||
delivered by a seat: the artifact store, a package registry, git and the vault. A database is not.
|
Only provisions the design makes one-per-mesh are delivered by a seat: the broker, the artifact store,
|
||||||
Node-local stores, served by co-location, are the rule above.
|
a package registry, git and the vault. A database is not. Node-local stores, served by co-location,
|
||||||
|
are the rule above.
|
||||||
|
|
||||||
**Ambiguity is refused, never resolved by picking.** If several providers of a kind exist, none is
|
**Ambiguity is refused, never resolved by picking.** If several providers of a kind exist, none is
|
||||||
named, none is co-located, and no seat delivers it, the requirement is unsatisfiable and is refused
|
named, none is co-located, and no seat delivers it, the requirement is unsatisfiable and is refused
|
||||||
|
|||||||
@@ -48,8 +48,8 @@ argued for is an entry nobody can explain.
|
|||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| `mesh-controller` | mesh | — | the controller |
|
| `mesh-controller` | mesh | — | the controller |
|
||||||
| `mesh-store` | mesh | — | the foundation's store |
|
| `mesh-store` | mesh | — | the foundation's store |
|
||||||
| `mesh-broker` | mesh | — | the foundation's broker |
|
| `mesh-broker` | mesh | `amqp` | the broker |
|
||||||
| `mesh-vault` | mesh | `secret` | the vault |
|
| `mesh-vault` | mesh | `secret`, reserved | the vault |
|
||||||
| `the-artifact-store` | mesh | `artifact-store` | the artifact registry |
|
| `the-artifact-store` | mesh | `artifact-store` | the artifact registry |
|
||||||
| `the-catalogue` | mesh | — | the catalogue |
|
| `the-catalogue` | mesh | — | the catalogue |
|
||||||
| `npm-package-registry` | mesh | `npm-package-registry` | the forge |
|
| `npm-package-registry` | mesh | `npm-package-registry` | the forge |
|
||||||
@@ -64,8 +64,8 @@ argued for is an entry nobody can explain.
|
|||||||
|
|
||||||
The controller holds this set in code, and a test asserts both its size and that every entry names
|
The controller holds this set in code, and a test asserts both its size and that every entry names
|
||||||
the record that made it a seat. **This table and [ADR 0110](../../02-DECISIONS/0110-a-seat-is-a-module-assignment-from-a-closed-set.md)
|
the record that made it a seat. **This table and [ADR 0110](../../02-DECISIONS/0110-a-seat-is-a-module-assignment-from-a-closed-set.md)
|
||||||
govern, and code that disagrees is what is wrong.** The implementation in progress predates two
|
govern, and code that disagrees is what is wrong.** The implementation in progress predates three
|
||||||
things here: the `mesh-vault` seat, and the rule that `mesh-store` and `mesh-broker` deliver
|
things here: the `mesh-vault` seat and its reservation, and the rule that `mesh-store` delivers
|
||||||
nothing. It is brought to this table before it merges.
|
nothing. It is brought to this table before it merges.
|
||||||
|
|
||||||
## A seat that delivers a provision
|
## A seat that delivers a provision
|
||||||
@@ -73,11 +73,18 @@ nothing. It is brought to this table before it merges.
|
|||||||
A seat that delivers a provision may only be held by a module that provides it, at the seat's scope.
|
A seat that delivers a provision may only be held by a module that provides it, at the seat's scope.
|
||||||
A mesh seat delivers a mesh-scoped provision.
|
A mesh seat delivers a mesh-scoped provision.
|
||||||
|
|
||||||
**A seat delivers a provision only where the mesh has one answer for everyone.** The artifact store,
|
**A seat delivers a provision only where the mesh has one answer for everyone.** The broker, the
|
||||||
the npm registry, git and the vault are each one per mesh by decision. The store and the broker are
|
artifact store, the npm registry, git and the vault are each one per mesh by decision. The store is
|
||||||
not: nodes run their own stores and a consumer uses the one on its machine
|
not: nodes run their own stores and a consumer uses the one on its machine
|
||||||
([23 — Choosing a provider](23-choosing-a-provider.md)). So their seats guard that the foundation's
|
([23 — Choosing a provider](23-choosing-a-provider.md)), and the foundation's store is the controller's
|
||||||
own server is singular, and route nobody.
|
own memory, provider to nobody. So `mesh-store` guards that the foundation's store is singular, and
|
||||||
|
routes nobody.
|
||||||
|
|
||||||
|
**The vault's provision is reserved.** Only the holder of `mesh-vault` may provide `secret` at all: a
|
||||||
|
module providing it without the seat is refused, and a pin cannot choose another provider, because
|
||||||
|
there is none. A second provider of secrets would be a second place secrets live, which is what the
|
||||||
|
vault being one per mesh exists to prevent. Every other delivered provision may have second
|
||||||
|
providers, which a pin can choose.
|
||||||
|
|
||||||
**Its holder answers for that provision.** A requirement for it resolves, in order, to:
|
**Its holder answers for that provision.** A requirement for it resolves, in order, to:
|
||||||
|
|
||||||
|
|||||||
@@ -71,9 +71,17 @@ Which module answers, in order:
|
|||||||
|
|
||||||
### Secrets: provisioning all the way down
|
### Secrets: provisioning all the way down
|
||||||
|
|
||||||
**The vault makes every secret, and it is the only thing that does**
|
**Two kinds of secret, one rule each** ([ADR 0113](../../02-DECISIONS/0113-the-vault-makes-every-secret.md)):
|
||||||
([ADR 0113](../../02-DECISIONS/0113-the-vault-makes-every-secret.md)). It holds the `mesh-vault` seat,
|
|
||||||
so every `secret` requirement resolves to it.
|
- **a shared secret**, a value more than one party must hold (a password, a token, an API key), is
|
||||||
|
made by the vault, and by nothing else;
|
||||||
|
- **a private key**, such as a node's sealing key, the operator's key or the mesh's certificate
|
||||||
|
authority, is made where it is used and never leaves. A private key anyone else held would no
|
||||||
|
longer be private.
|
||||||
|
|
||||||
|
**Every shared secret is a `secret` requirement, and only the vault provides `secret`.** The vault
|
||||||
|
holds the `mesh-vault` seat, and that provision is reserved to it: no other module may provide it, and
|
||||||
|
no pin can choose another provider ([26 — The seats](26-the-seats.md)).
|
||||||
|
|
||||||
**A provider that needs a secret for a consumer requires one, like any consumer.** A provision's
|
**A provider that needs a secret for a consumer requires one, like any consumer.** A provision's
|
||||||
contract declares it: *for each consumer, one secret*. Resolution expands that into one requirement
|
contract declares it: *for each consumer, one secret*. Resolution expands that into one requirement
|
||||||
@@ -82,16 +90,19 @@ per consumer, named for that consumer:
|
|||||||
1. gitea requires `postgres-database`;
|
1. gitea requires `postgres-database`;
|
||||||
2. the database provider, to serve gitea, requires a `secret` named for gitea;
|
2. the database provider, to serve gitea, requires a `secret` named for gitea;
|
||||||
3. the vault makes it and hands it to the mesh;
|
3. the vault makes it and hands it to the mesh;
|
||||||
4. the mesh delivers it to both holders, each sealed to its own node: the database's machine, to
|
4. the mesh delivers it to both of its **recipients**, each sealed to its own node: the database's
|
||||||
create the login, and gitea's, to present it;
|
machine, which *applies* it by creating the login, and gitea's, which *presents* it;
|
||||||
5. the database provider creates the login, exactly as it does today, and gitea connects.
|
5. the database provider creates the login, exactly as it does today, and gitea connects.
|
||||||
|
|
||||||
A module's own secret, a broker account's password and a secret operator value take the same path.
|
Every other shared secret takes the same path:
|
||||||
Nothing in the mesh makes a secret except the vault.
|
- a module's own secret;
|
||||||
|
- every broker account's password, where the broker's own provisioner creates the account;
|
||||||
|
- an enrolment token;
|
||||||
|
- a secret operator value, which the operator delivers to the vault.
|
||||||
|
|
||||||
**A provider makes resources and data.** Beyond secrets, a provider answers with its contract's
|
**A provider makes resources and data.** Beyond secrets, a provider's adapter may answer with its
|
||||||
non-secret fields: an analytics site id, a registered public name. The mesh carries them back to the
|
contract's non-secret fields: an analytics site id, a registered public name. The mesh carries them
|
||||||
consumer as resolved values.
|
back to the consumer as resolved values.
|
||||||
|
|
||||||
### The node's host
|
### The node's host
|
||||||
|
|
||||||
@@ -180,36 +191,54 @@ slug, under the same rules as a slug. This has to be settled before a second ins
|
|||||||
|
|
||||||
## Genesis
|
## Genesis
|
||||||
|
|
||||||
**Genesis is the vault's first answer, not an exception.** It raises the vault before anything else
|
**Genesis delivers, and the vault adopts.** The vault cannot run first: it is built on the shared
|
||||||
and asks it for the foundation's secrets: the store's superuser, the broker's admin in the hashed form
|
runtime base, which the installation makes only after the store, the broker and the controller exist
|
||||||
the broker needs, and the vault's own broker account. The vault answers with the same code it always
|
([21 — The installation in full](21-the-installation-in-full.md)), and it learns what to answer from
|
||||||
uses, before the bus exists, and seals the root secrets to the operator key as today
|
the controller over the bus. So genesis generates the foundation's first shared secrets itself:
|
||||||
([ADR 0085](../../02-DECISIONS/0085-a-secret-is-a-provision.md)). Genesis makes no secret itself, so
|
- the store's superuser;
|
||||||
there is one way a secret is made, from the first one onwards.
|
- the broker's admin, in the hashed form the broker needs;
|
||||||
|
- the bus accounts of the temporary controller and of the vault;
|
||||||
|
- the first enrolment token.
|
||||||
|
|
||||||
The vault can sit at the bottom because it requires nothing but a broker account: it keeps its data
|
It seals them to the operator key as today ([ADR 0085](../../02-DECISIONS/0085-a-secret-is-a-provision.md)).
|
||||||
on its own disk, not in the store. So the waterfall ends at the vault.
|
When the vault is installed, genesis **delivers them to it**, through the same path an operator's value
|
||||||
|
takes. From then on the vault holds, audits and rotates them. It can make their replacements, unlike
|
||||||
|
an operator's external key.
|
||||||
|
|
||||||
|
That is the one time anything but the vault generates a shared secret, and it ends by handing them
|
||||||
|
over. It is also the answer to the objection ADR 0085 had to the vault being the only maker: the
|
||||||
|
vault cannot make what exists before it, so what exists before it is delivered to it.
|
||||||
|
|
||||||
## Rotation
|
## Rotation
|
||||||
|
|
||||||
Rotating a secret is asked of the vault, by an operator or by the vault's own policy, such as a
|
Rotating a secret is asked of the vault, by an operator or by the vault's own policy, such as a
|
||||||
maximum age in the secret's contract ([ADR 0113](../../02-DECISIONS/0113-the-vault-makes-every-secret.md)).
|
maximum age in the secret's contract ([ADR 0113](../../02-DECISIONS/0113-the-vault-makes-every-secret.md)).
|
||||||
|
An operator's external key is not rotated by the vault, which cannot make its replacement: an
|
||||||
|
operator delivers a new one.
|
||||||
|
|
||||||
|
**A secret's contract says how each recipient takes a new value.** A recipient either *applies* it,
|
||||||
|
through a provisioner (a provider creating the login, the broker's provisioner updating an account,
|
||||||
|
the store's own provisioner changing its superuser), or *reads it at start*. A secret a service reads
|
||||||
|
only when it first initialises is marked applied, because a restart would change nothing.
|
||||||
|
|
||||||
1. **The vault makes the new value.**
|
1. **The vault makes the new value.**
|
||||||
2. **It is delivered to the holders that accept it first.** For a database credential that is the
|
2. **It goes first to the recipients that apply it.** Each applies it, verifies that the new value
|
||||||
database's provider, whose provisioner applies it and confirms it did.
|
authenticates and the old one no longer does, and confirms. It repeats that confirmation on every
|
||||||
3. **Only then is it released to the holders that present it**, such as gitea. A consumer is never
|
reconcile pass until the vault acknowledges it, so a lost message costs one pass.
|
||||||
sent a value its provider has not accepted, so the window in which it cannot log in shrinks to its
|
3. **Only then is it released to the recipients that read it at start**, such as gitea.
|
||||||
own restart. A provider that does not confirm holds the rotation: the consumer keeps the old value,
|
4. **The host restarts every such recipient**, and recreates a container whose env-file carries the
|
||||||
which still works, and the rotation shows as waiting on that provider.
|
secret. It knows which, because a definition reads a secret only through its requirement, so no
|
||||||
4. **The host restarts every process that reads the secret**, and recreates a container whose
|
definition declares a restart for a secret. An applying recipient is never restarted for it.
|
||||||
env-file carries it. It knows which, because a definition reads a secret only through its
|
This needs [issue 103](../../04-ISSUES/103-a-container-is-not-recreated-when-a-file-it-reads-changes/00-report.md)
|
||||||
requirement. No definition declares a restart for a secret. This needs
|
|
||||||
[issue 103](../../04-ISSUES/103-a-container-is-not-recreated-when-a-file-it-reads-changes/00-report.md)
|
|
||||||
fixed, or a container fed by an env-file keeps the old value.
|
fixed, or a container fed by an env-file keeps the old value.
|
||||||
5. **It is confirmed.** The rotation shows as unconfirmed until each consumer has restarted with the
|
5. **It is confirmed.** The rotation shows as unconfirmed until every applier has confirmed, and every
|
||||||
new value and passed its health check, where its definition declares one. Delivered and working
|
recipient that reads at start has restarted and passed its health check, where its definition
|
||||||
are shown as different things.
|
declares one. Delivered and working are shown as different things.
|
||||||
|
|
||||||
|
**The remaining window is stated.** An applier whose provisioner stops after applying and before
|
||||||
|
confirming leaves the recipients that read at start locked out: the old value no longer works, and
|
||||||
|
they have not been sent the new one. A provisioner is supervised and restarted when it exits, so the
|
||||||
|
window lasts until that restart. The rotation shows as waiting on that applier throughout, never as done.
|
||||||
|
|
||||||
## Refusing
|
## Refusing
|
||||||
|
|
||||||
@@ -250,12 +279,14 @@ Each phase ends at a check that holds, so none of them leaves a mechanism half-r
|
|||||||
the controller. The controller resolves requirements from the four providers, refuses as above,
|
the controller. The controller resolves requirements from the four providers, refuses as above,
|
||||||
and fills the one form. Old mechanisms keep working beside it. *Ends when* a definition written
|
and fills the one form. Old mechanisms keep working beside it. *Ends when* a definition written
|
||||||
entirely in the new form installs on a lab machine.
|
entirely in the new form installs on a lab machine.
|
||||||
2. **The vault makes every secret, and providers answer.** The vault holds its seat and is the only
|
2. **The vault makes every shared secret, and providers answer.** The vault holds its seat and its
|
||||||
maker; resolution expands per-consumer secret requirements; genesis asks the vault first; the mesh
|
reserved provision; resolution expands per-consumer secret requirements; genesis delivers the
|
||||||
carries providers' data back. [Issue 103](../../04-ISSUES/103-a-container-is-not-recreated-when-a-file-it-reads-changes/00-report.md)
|
foundation's first secrets to the vault; the broker's provisioner creates every bus account; the
|
||||||
is fixed first. *Ends when* no code path outside the vault makes a secret, the analytics and DNS
|
mesh carries providers' data back.
|
||||||
providers answer their consumers, and a database credential rotates provider-first, with the
|
[Issue 103](../../04-ISSUES/103-a-container-is-not-recreated-when-a-file-it-reads-changes/00-report.md)
|
||||||
consumer restarted by derivation and the rotation confirmed.
|
is fixed first. *Ends when* nothing outside the vault generates a shared secret after genesis, a lab
|
||||||
|
consumer of analytics receives its site id, and a database credential rotates applier-first, with
|
||||||
|
the consumer restarted by derivation and the rotation confirmed.
|
||||||
3. **Definitions move.** Every catalogue definition is rewritten, adopted and running assignments
|
3. **Definitions move.** Every catalogue definition is rewritten, adopted and running assignments
|
||||||
placed where their data already is. *Ends when* the list of definitions using an old form is
|
placed where their data already is. *Ends when* the list of definitions using an old form is
|
||||||
empty, and the old forms are removed.
|
empty, and the old forms are removed.
|
||||||
@@ -277,9 +308,10 @@ Each phase ends at a check that holds, so none of them leaves a mechanism half-r
|
|||||||
| A public name already held is refused | A resolution test: a second instance asking for a public name the first holds is refused, naming the first. |
|
| A public name already held is refused | A resolution test: a second instance asking for a public name the first holds is refused, naming the first. |
|
||||||
| Everything keyed by a module is keyed by its instance | A resolution test: two instances of one module on one node get two directories, two containers, two logins and separate settings. |
|
| Everything keyed by a module is keyed by its instance | A resolution test: two instances of one module on one node get two directories, two containers, two logins and separate settings. |
|
||||||
| A consumer waits for its provider | A resolution test with a provider that has not answered: shown as waiting, and nothing delivered. |
|
| A consumer waits for its provider | A resolution test with a provider that has not answered: shown as waiting, and nothing delivered. |
|
||||||
| Only the vault makes secrets | A controller test: no code path mints a secret, genesis included. |
|
| Only the vault generates a shared secret after genesis | A controller test: no code path generates one. An installer test: genesis generates exactly the foundation's first secrets and delivers them to the vault. |
|
||||||
| A provider's per-consumer secret comes from the vault | A resolution test: requiring a database expands to a secret requirement named for the consumer, answered by the vault and delivered to both holders. |
|
| Only the vault provides `secret` | The parser refuses another provider of it, and resolution refuses a pin on a `secret` requirement. |
|
||||||
| Rotation is provider-first, derived and confirmed | The rotation tests of [ADR 0113](../../02-DECISIONS/0113-the-vault-makes-every-secret.md): the consumer waits for the provider's confirmation, is restarted without a declared restart, and shows unconfirmed until healthy. |
|
| A provider's per-consumer secret comes from the vault | A resolution test: requiring a database expands to a secret requirement named for the consumer, answered by the vault and delivered to both recipients. |
|
||||||
|
| Rotation is applier-first, derived and confirmed | The rotation tests of [ADR 0113](../../02-DECISIONS/0113-the-vault-makes-every-secret.md): readers wait for every applier's repeated confirmation; an applied secret restarts nothing, and one read at start restarts its reader without a declared restart; the rotation shows unconfirmed until the new value authenticates, the old does not, and readers are healthy. |
|
||||||
| Refusal names everything at once | A resolution test with three unresolved requirements of different kinds: one refusal naming all three. |
|
| Refusal names everything at once | A resolution test with three unresolved requirements of different kinds: one refusal naming all three. |
|
||||||
| The old forms retire | The catalogue test listing definitions still using one. It must be empty before a form is removed. |
|
| The old forms retire | The catalogue test listing definitions still using one. It must be empty before a form is removed. |
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user