ADR 0094: a module may hold several secrets from one provider; issue 069 resolved; design 24 amended
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
---
|
||||
topic: the tiers
|
||||
status: accepted
|
||||
date: 2026-09-21
|
||||
deciders: jochen
|
||||
reconstructed: false
|
||||
extends: 02-DECISIONS/0085-a-secret-is-a-provision.md
|
||||
---
|
||||
|
||||
# 94. A module may hold several secrets from one provider, each a pair of its own
|
||||
|
||||
## Context
|
||||
|
||||
[ADR 0085](0085-a-secret-is-a-provision.md) makes a module's own secret a provision: the module
|
||||
requires `secret` from the vault and reads the pair credential minted for that consumer↔vault
|
||||
pair. A pair has one credential, a module requires a provision once, and so a module received
|
||||
one value. Read against the catalogue, nine modules hold two or more secrets besides their
|
||||
broker account; seven of them hold genuinely independent values with independent lifetimes — a
|
||||
root certificate, its key and that key's password; an admin password beside an API token. None
|
||||
derives from another, so "one value, derivation the module's business" answers nothing
|
||||
([issue 069](../04-ISSUES/069-one-secret-provision-yields-one-value/00-report.md)).
|
||||
|
||||
## Considered Options
|
||||
|
||||
1. **One value per module; the module derives the rest.** Rejected: the values are independent.
|
||||
2. **Require the provision several times.** Rejected: `requires` is a list of names, and a
|
||||
requirement is matched by name everywhere.
|
||||
3. **The `secrets` map names several files under local names, and each local name is a pair
|
||||
credential of its own.** Adopted.
|
||||
|
||||
## Decision
|
||||
|
||||
A module's `secrets` entry for a requirement may be a path, as before, or an object of local
|
||||
names to paths. Each local name is its own pair credential, keyed on it beside the provision,
|
||||
the consumer node, the consumer module and the provider; its own file on the consumer, referred
|
||||
to as `${secret:<local name>}`; its own holder at the provider, named the consumer's identity
|
||||
with the local name after it; and rotated apart from the others. A local name may not be one of
|
||||
the module's own secrets or something it requires, so what a placeholder means is never
|
||||
ambiguous. The plain shape is unchanged, and every credential that exists is the one it was.
|
||||
|
||||
The holder's suffix is not a login any backend checks — a secret is not a login — so the
|
||||
identity limit that binds a database role or an access key does not apply to it.
|
||||
|
||||
## Consequences
|
||||
|
||||
The ten modules that could not move onto the vault can. What got harder: `rotate secret` for a
|
||||
consumer rotates every local name it holds from that provider; rotating one of several is a
|
||||
finer command than the mesh has, and waits for a case that needs it.
|
||||
|
||||
## How it is checked
|
||||
|
||||
Manifest tests read both shapes, write them back, and refuse a colliding or unusable local
|
||||
name. A resolver test asserts two local names are two needs, two files with two credentials,
|
||||
and two holders at the provider. An inventory test asserts two local names are two rows, that
|
||||
rotating one leaves the other, and that the provider is told both. The vault bed installs a
|
||||
consumer that keeps two secrets and asserts two values delivered, two holders in the vault's
|
||||
ledger, and both rotated by one command.
|
||||
|
||||
## References
|
||||
|
||||
- [issue 069](../04-ISSUES/069-one-secret-provision-yields-one-value/00-report.md)
|
||||
- [ADR 0085](0085-a-secret-is-a-provision.md), [ADR 0049](0049-a-consumers-identity-fits-the-tightest-backend.md)
|
||||
- [`03-DESIGN/01-to-be/24-the-secrets-vault.md`](../03-DESIGN/01-to-be/24-the-secrets-vault.md)
|
||||
@@ -113,6 +113,7 @@ python3 00-META/checks/index.py fail if stale
|
||||
- **0078** — [The store and the broker are ordinary modules](0078-the-store-and-broker-are-modules.md)
|
||||
- **0079** — [The foundation seats are named after their servers](0079-the-foundation-seats-are-named-after-their-servers.md)
|
||||
- **0092** — [An operator delivers a pair credential, and the mesh never replaces it](0092-an-operator-delivers-a-pair-credential.md)
|
||||
- **0094** — [A module may hold several secrets from one provider, each a pair of its own](0094-a-module-may-hold-several-secrets-from-one-provider.md)
|
||||
|
||||
### What runs on them, and how it gets there
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ status: implemented
|
||||
code: [mesh-catalog, mesh-controller, mesh-host]
|
||||
updated: 2026-09-21
|
||||
decisions:
|
||||
- 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
|
||||
- 02-DECISIONS/0031-the-control-plane-authenticates-nobody.md
|
||||
@@ -48,8 +49,12 @@ A module that needs a secret for its own use requires a `secret` provision, exac
|
||||
a database from the store. The vault generates the value — or takes custody of one an operator
|
||||
delivered, through `secret accept … --provider`, which seals it to both ends and records the pair
|
||||
as accepted ([ADR 0092](../../02-DECISIONS/0092-an-operator-delivers-a-pair-credential.md)) — and
|
||||
the credential belongs to the consumer↔vault pair. An accepted pair is the one exception to what
|
||||
follows: the mesh cannot make its replacement, so it is neither remade when a key changes nor
|
||||
the credential belongs to the consumer↔vault pair. A module that needs several values names them
|
||||
as local names under its `secrets` entry, and each is a pair of its own — keyed on the local
|
||||
name, delivered as its own file, held at the vault as its own holder
|
||||
([ADR 0094](../../02-DECISIONS/0094-a-module-may-hold-several-secrets-from-one-provider.md)).
|
||||
*How it is checked:* manifest, resolver and inventory tests on two local names; the vault bed's
|
||||
two-secret consumer. An accepted pair is the one exception to what follows: the mesh cannot make its replacement, so it is neither remade when a key changes nor
|
||||
rotated; both are refused aloud, and accepting a new value is the rotation. *How it is checked:*
|
||||
an inventory test accepts, reads back unchanged, and asserts the two refusals name the remedy. Because it is an ordinary pair
|
||||
credential, **everything already built for pair credentials applies to it unchanged**: it rotates
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
---
|
||||
status: located
|
||||
status: resolved
|
||||
opened: 2026-09-20
|
||||
located-in: [mesh-controller internal/catalogue (requires/secrets), mesh-controller internal/inventory (secret key)]
|
||||
fixed-by:
|
||||
amended-design:
|
||||
fixed-by: ADR 0094; mesh-controller feat/several-secrets (secrets: under local names, the pair keyed on the local name, migration 0027); proven by the vault bed
|
||||
amended-design: 03-DESIGN/01-to-be/24-the-secrets-vault.md
|
||||
---
|
||||
|
||||
# One `secret` provision yields one value, and a module may need several
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
is a manifest-vocabulary decision, and it moves the pair key: the credential must be keyed on
|
||||
the local name, not the provision name, or the second pair overwrites the first.
|
||||
|
||||
**Located in:** the manifest's `requires`/`secrets` vocabulary (the catalogue parser) and the pair
|
||||
credential's key (the controller's secret store). Not fixed here: the key change touches every
|
||||
existing pair and belongs in a feature of its own, with a lab run against the vault bed.
|
||||
**Located in:** the manifest's `secrets` vocabulary (the catalogue parser) and the pair
|
||||
credential's key (the controller's secret store). Fixed as
|
||||
[ADR 0094](../../02-DECISIONS/0094-a-module-may-hold-several-secrets-from-one-provider.md): the
|
||||
key gains the local name, empty for every existing pair, so nothing that exists changed.
|
||||
|
||||
Reference in New Issue
Block a user