Merge pull request 'ADR 0158: a provider with one credential shares it with every consumer, and the vault remakes it for all at once' (#246) from decision/0158-a-provider-with-one-credential-shares-it into main
This commit was merged in pull request #246.
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
---
|
||||
topic: the mesh
|
||||
status: accepted
|
||||
date: 2026-10-01
|
||||
deciders: jochen
|
||||
reconstructed: false
|
||||
extends: 02-DECISIONS/0048-a-provider-creates-the-credential-the-mesh-minted.md
|
||||
---
|
||||
|
||||
# 158. A provider with one credential shares it with every consumer, and the vault remakes it for all of them at once
|
||||
|
||||
## Context
|
||||
|
||||
[ADR 0048](0048-a-provider-creates-the-credential-the-mesh-minted.md) gave every consumer of a
|
||||
provision its own credential: the mesh mints one per pair, the provider's own code creates the
|
||||
login, and rotating one consumer's touches nothing else. That is right for a database, a broker, an
|
||||
object store — software that can hold many logins.
|
||||
|
||||
The media software on the home server cannot. A download client has one web password; an indexer
|
||||
has one API key; each of the library managers has one key in its configuration; the media server
|
||||
holds one token issued elsewhere. There is no login per consumer to create, so
|
||||
[ADR 0113](0113-the-vault-makes-every-secret.md)'s only remaining form applied: the value is
|
||||
*accepted*. On 2026-10-01 the home server held forty-seven accepted own secrets and twelve accepted
|
||||
pair credentials, every one rotatable only by a person changing the software by hand and accepting
|
||||
the new value, and one pair credential sat *made* and wrong because nobody could accept the real one.
|
||||
The operator asked for every password in the vault and rotatable, and for a library manager's
|
||||
definition to receive the download client's credential and address through provisioning like
|
||||
anything else (filed as the forge's issue 243 on this repository).
|
||||
|
||||
The address half already works: the library manager requires the download client's API provision,
|
||||
the provider serves scheme, port and user name, and the binding carries them. Only the credential
|
||||
half had no form.
|
||||
|
||||
## Considered Options
|
||||
|
||||
1. **Keep accepting.** Honest about what the software can do and what the mesh cannot, and it is
|
||||
the state the home server was in: nothing rotates, a consumer added later needs a person, and an
|
||||
unknown predecessor password stays unknown for ever.
|
||||
2. **Put a login per consumer in front of the software.** A proxy that holds the one credential and
|
||||
issues many. A second service per provider, with its own credential to keep, to make the mesh's
|
||||
model fit software that does not share it.
|
||||
3. **Let the provider say its one credential is the credential.** An offer names which of the
|
||||
provider's own secrets *is* what every consumer receives. The vault keeps one record, sealed to
|
||||
the provider's machine, every current consumer's machine and the operator, and because it stores
|
||||
no plaintext it cannot seal an existing value to a later consumer — so it **remakes the value
|
||||
for all of them at once** whenever the set of consumers changes or a rotation is asked. The
|
||||
provider takes it the way an own secret is taken ([ADR 0114](0114-a-shared-credential-rotates-over-two-credentials.md),
|
||||
issue 180); consumers read it at start.
|
||||
|
||||
## Decision
|
||||
|
||||
**Option 3.** A provider whose software holds one credential shares that credential, and the mesh
|
||||
owns its whole lifecycle.
|
||||
|
||||
- **The offer says so.** `{"name": "download-client-api", "credential": {"own": "password"}}` on a
|
||||
provider's `provides` entry names one of its own secrets as the credential of that provision. The
|
||||
named own secret must say how it is taken (`taken: at-start` or `taken: applied`); an offer
|
||||
naming an undeclared or untaken secret is refused at parse.
|
||||
- **One record, many seals.** The vault keeps one value per (provider assignment, provision). It is
|
||||
sealed to the provider's machine, to each consumer's machine that currently binds the provision,
|
||||
and to the operator. Every consumer's binding file carries the provider's one user name and the
|
||||
secret file carries the shared value; the shape a consumer reads is the pair credential's, so a
|
||||
consumer's definition does not know whether its credential is shared.
|
||||
- **Remade for all, together.** When a consumer binds or unbinds, or `secret rotate` is asked on the
|
||||
provider's own secret, the vault makes a new value and seals it to every current holder in one
|
||||
act, and the mesh sends every holding machine. The provider restarts on the new value or applies
|
||||
it at start; each consumer restarts on it. There is no window between two credentials, because
|
||||
there is one credential; there is the restart, stated as the cost below.
|
||||
- **An accepted shared value is sealed to everyone the moment it is accepted.** `secret accept` on
|
||||
the provider's own secret is the one moment the mesh holds the plaintext, and it seals copies for
|
||||
every current consumer then. It is not remade afterwards ([ADR 0113](0113-the-vault-makes-every-secret.md)):
|
||||
a consumer that binds later is refused until the value is accepted again, in words that say so.
|
||||
- **A value the software issues itself stays accepted.** A token the media server obtains from its
|
||||
vendor cannot be set by the mesh; its provision keeps the accepted form until a module can deliver a
|
||||
value it did not mint to the vault, which this record does not build.
|
||||
- **Nothing changes for software that holds many logins.** ADR 0048's form stays the default; this
|
||||
is the form for an offer that says it has one credential.
|
||||
|
||||
## Consequences
|
||||
|
||||
- The media stack's six providers stop needing a person per consumer. A library manager binding
|
||||
the download client gets a working credential the mesh made, and an unknown predecessor password
|
||||
is replaced by one the mesh knows, recoverable with the operator's key.
|
||||
- **Adding or removing a consumer restarts every consumer of that provision and the provider.**
|
||||
That is the price of one credential, and it is paid when a definition binds, not at an hour of
|
||||
nobody's choosing. It is stated in the plan's words when it happens.
|
||||
- Rotation of a shared credential is [ADR 0114](0114-a-shared-credential-rotates-over-two-credentials.md)'s
|
||||
single-party form across several machines: in place, all holders sent together. The staged form
|
||||
for a backend that takes its credential once is still not built, and a provider whose own secret
|
||||
says `applied` refuses rotation by name until it is.
|
||||
- The vault can name who holds a shared value — the copies are the record — so *who has this* stays
|
||||
a query, as design 13 requires.
|
||||
- The accepted count on the home server becomes a list that shrinks, provider by provider, as each
|
||||
one's start applies the file.
|
||||
|
||||
## How this is checked
|
||||
|
||||
| Rule | Checked by |
|
||||
|---|---|
|
||||
| An offer may name one of its own secrets as its credential; an undeclared or untaken secret is refused at parse | manifest tests |
|
||||
| A consumer of a shared provision receives the provider's value as its pair credential, under the provider's one user name | resolver and declaration tests |
|
||||
| The record is sealed to the provider, every current consumer and the operator; a consumer binding or unbinding remakes it for all | inventory tests against a raised store |
|
||||
| Rotating the provider's own secret remakes every holder's copy, and an accepted value is sealed to current consumers once and not remade | inventory tests |
|
||||
| Live: a library manager on the home server binds the download client with a value the mesh made, the client takes it at start, and a rotation through the console reaches both | done by hand after the media catalogue's providers apply the file at start |
|
||||
|
||||
## References
|
||||
|
||||
- [ADR 0048](0048-a-provider-creates-the-credential-the-mesh-minted.md) — extended: the per-consumer form stays the default; this is the form for one credential
|
||||
- [ADR 0113](0113-the-vault-makes-every-secret.md), [ADR 0114](0114-a-shared-credential-rotates-over-two-credentials.md) — the accepted form and the single-party rotation this rests on
|
||||
- [Issue 180](../04-ISSUES/180-a-modules-own-secret-cannot-be-rotated/00-report.md) — the `taken` word and the rotation this reuses
|
||||
- [Design 24 — The secrets vault](../03-DESIGN/01-to-be/24-the-secrets-vault.md), [Design 13 — Credentials and their rotation](../03-DESIGN/01-to-be/13-credentials-and-their-rotation.md)
|
||||
- The forge's issue 243 on this repository, where the operator's ask and the home server's count were recorded
|
||||
@@ -171,6 +171,7 @@ python3 00-META/checks/index.py fail if stale
|
||||
- **0154** — [The mesh's own verbs are the mesh-controller seat's tools, and which verbs those are](0154-the-meshs-own-verbs-are-the-controller-seats-tools.md)
|
||||
- **0156** — [An artifact is what a build produces, the artifact store serves every kind, and its seat is named for its scope](0156-an-artifact-is-what-a-build-produces-and-the-store-is-named-for-its-scope.md)
|
||||
- **0157** — [A build says what it does on the bus, as it happens](0157-a-build-says-what-it-does-on-the-bus-as-it-happens.md)
|
||||
- **0158** — [A provider with one credential shares it with every consumer, and the vault remakes it for all of them at once](0158-a-provider-with-one-credential-shares-it-with-every-consumer.md)
|
||||
|
||||
### Its tiers, from the bottom up
|
||||
|
||||
|
||||
@@ -145,5 +145,7 @@ and the operator, and sends the machine, so the module starts again on it. A sec
|
||||
is refused with the word to write, because a credential rotated under software that never reads it
|
||||
again is the fault of issue 179 made deliberately; an applied one is refused until the staged form is
|
||||
built; an accepted one is refused as ADR 0113 says. `rotate` is a verb on the controller's seat with
|
||||
both shapes, so the console asks for either. *How it is checked:* the tests named in issue 180, and a
|
||||
both shapes, so the console asks for either. A provider that shares its one credential with every
|
||||
consumer ([ADR 0158](../../02-DECISIONS/0158-a-provider-with-one-credential-shares-it-with-every-consumer.md))
|
||||
rotates the same way, with every holder's copy remade and every holding machine sent together. *How it is checked:* the tests named in issue 180, and a
|
||||
live rotation through the console of a secret a module reads at start.
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
layer: to-be
|
||||
status: implemented
|
||||
code: [mesh-catalog, mesh-controller, mesh-host]
|
||||
updated: 2026-09-21
|
||||
updated: 2026-10-01
|
||||
decisions:
|
||||
- 02-DECISIONS/0158-a-provider-with-one-credential-shares-it-with-every-consumer.md
|
||||
- 02-DECISIONS/0094-a-module-may-hold-several-secrets-from-one-provider.md
|
||||
- 02-DECISIONS/0092-an-operator-delivers-a-pair-credential.md
|
||||
- 02-DECISIONS/0085-a-secret-is-a-provision.md
|
||||
@@ -127,6 +128,22 @@ credential a provider grants; the export names each entry by the node and module
|
||||
the name they know it by, and says whether it is a module's own secret or a pair credential, so
|
||||
recovery addresses both alike.
|
||||
|
||||
### A provider with one credential
|
||||
|
||||
*Decided 2026-10-01 ([ADR 0158](../../02-DECISIONS/0158-a-provider-with-one-credential-shares-it-with-every-consumer.md)); to be built.*
|
||||
|
||||
Software that holds one credential — a download client's web password, an indexer's one API key —
|
||||
cannot give each consumer a login, so ADR 0048's form does not fit it and its values were accepted
|
||||
by hand. An offer may now say `"credential": {"own": "<secret>"}`: the provider's own secret *is* the
|
||||
credential every consumer of that provision receives, in the shape of an ordinary pair credential,
|
||||
under the provider's one user name. The vault keeps one value per provider assignment and provision,
|
||||
sealed to the provider's machine, each consumer's machine and the operator; because it holds no
|
||||
plaintext it remakes the value for every holder at once when a consumer binds or unbinds or a
|
||||
rotation is asked, and the mesh sends every holding machine together. The provider takes it as it
|
||||
says it takes its own secret (`taken`, issue 180); consumers read it at start. An accepted value is
|
||||
sealed to the consumers of the moment and not remade; a consumer that binds later waits for the next
|
||||
acceptance. *How it is checked:* the rows of ADR 0158's table, once built.
|
||||
|
||||
## Beyond generate and hold
|
||||
|
||||
Owning a secret means owning more than its creation. The mesh being migrated onto has a working
|
||||
|
||||
Reference in New Issue
Block a user