ADR 0113 and to-be 27: rotation overlaps old and new credentials
Decided with the author. A credential is never changed in place: each consumer has two logins, both derived by the mesh, and uses one at a time. An applier adds the new login beside the old through the adapter's existing create, and confirms both work; only then are readers released to the new one and restarted by derivation; only when every reader has confirmed is the old login retired through the existing remove. It closes the three cases review found in applier-first rotation: an offline reader keeps working on the old login until it returns; a bus account's owner keeps its bus until it has moved; a provisioner restarted mid-rotation is still delivered both values. Nobody is ever without a credential that works, which replaces to-be 13's all-or-nothing rule with a stronger one. No consumer module changes. The alternation is the provider loop's. A provider's adapter gains one duty, giving both logins the same rights over the consumer's data — in postgres, membership of one role that owns it. The mesh derives two logins per consumer, both within ADR 0049's limit, which 0113 now names among what it amends. Every rule has a check: overlap, offline reader, bus account, restarted provisioner, equal rights, login length, and confirmation only once the old login is gone.
This commit is contained in:
@@ -153,39 +153,55 @@ the old value. The host derives which recipients read a secret at start from the
|
||||
definition reads it through, so no definition declares a restart for a secret. A provider's
|
||||
per-consumer secrets are applied, never read at start, so the host never restarts a provider for one.
|
||||
|
||||
**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.
|
||||
**Old and new overlap: nobody is ever without a credential that works.** A credential is never
|
||||
changed in place. The new one is added beside the old, every reader moves to it, and only then is the
|
||||
old one removed. There is one mechanism, the same for every provider:
|
||||
|
||||
**Open: keeping readers from being locked out.** Review found three cases this rule does not survive:
|
||||
1. **The vault makes the new value.**
|
||||
2. **Each applier adds it beside the old.** A consumer has two logins, both derived by the mesh, and it
|
||||
uses one at a time. The provider's loop creates the other with the new value, through the adapter's
|
||||
existing create, and leaves the one in use untouched. It verifies that the new login works and the
|
||||
old one still does, and confirms. It repeats that confirmation on every reconcile pass until the
|
||||
vault acknowledges it, so a lost message costs one pass.
|
||||
3. **Only then is the new login released to the readers.** A reader receives the new login and its
|
||||
value together. The host restarts it, or recreates a container whose env-file carries it.
|
||||
4. **Each reader confirms**, by restarting with the new login and passing its health check, where its
|
||||
definition declares one.
|
||||
5. **Only when every reader has confirmed is the old login retired.** Each applier removes it, through
|
||||
the adapter's existing remove, and verifies that it no longer authenticates.
|
||||
|
||||
- a reader whose machine is offline when an applier has already applied the new value is locked out
|
||||
until it returns, where to-be 13 would have refused the rotation and kept the old value working;
|
||||
- a bus account's owner can be locked out permanently, because the confirmation and the new value
|
||||
travel over the bus it has just lost;
|
||||
- a provisioner restarted mid-rotation no longer knows the old value, so it cannot verify that the old
|
||||
value has stopped working.
|
||||
**What overlap closes:**
|
||||
|
||||
Two answers are recorded, and one must be chosen before this record is accepted. **Overlap:** an
|
||||
applier keeps the old and new credential valid together until every reader has confirmed the new one,
|
||||
for instance by alternating between two derived logins with the adapter's existing create and remove,
|
||||
which needs no change on the consumer's side. Or **re-confirm with safeguards:** a pre-check that every
|
||||
reader is reachable before any applier starts, plus a special path for bus accounts.
|
||||
- a reader whose machine is offline keeps the old login, which still works, until it returns and
|
||||
moves; the rotation shows as waiting on that reader, and nobody is locked out;
|
||||
- a bus account's owner keeps its old account until it has confirmed the new one over the bus it still
|
||||
has, so no party can lose the bus it would hear the new value on;
|
||||
- a provisioner restarted mid-rotation is still delivered both values until the old is retired, so it
|
||||
can verify either.
|
||||
|
||||
**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.
|
||||
**What overlap costs.**
|
||||
|
||||
- **Consumer modules: nothing.** A consumer reads one login at a time and changes it when it restarts.
|
||||
- **Providers: one duty.** Both of a consumer's logins must have the same rights over its data,
|
||||
because the consumer's data was written under one login and is read under the other. In postgres,
|
||||
both are members of one role that owns the data. That is the adapter's part, and the only place
|
||||
overlap touches provider code. The alternation itself is the provider loop's, so every provider gets
|
||||
it by using the harness.
|
||||
- **The mesh:** it derives two logins per consumer, and both must still fit the tightest backend
|
||||
([ADR 0049](0049-a-consumers-identity-fits-the-tightest-backend.md)).
|
||||
- **Secrets with no applier**, such as a module's own secret read only by itself, have no second party
|
||||
to overlap with. They are delivered and the reader restarted, where their contract allows rotation at
|
||||
all.
|
||||
|
||||
| step | who |
|
||||
|---|---|
|
||||
| asks | an operator, or the vault's policy |
|
||||
| makes the value | the vault |
|
||||
| carries it | the controller, sealed, appliers first |
|
||||
| applies it | each applier's provisioner, which verifies and confirms on every pass until acknowledged |
|
||||
| takes it at start | the host, restarting or recreating what reads it |
|
||||
| confirms it | applier confirmations, then restarts and health checks, shown in `status` |
|
||||
| adds the new login beside the old | each applier's provisioner, confirming on every pass until acknowledged |
|
||||
| moves each reader | the host, restarting or recreating what reads the secret |
|
||||
| confirms each reader | its restart and health check |
|
||||
| retires the old login | each applier's provisioner, once every reader has confirmed |
|
||||
| shows progress | `status`: waiting on which applier or reader, never done until the old is retired |
|
||||
|
||||
## What this changes in earlier records
|
||||
|
||||
@@ -202,11 +218,14 @@ On acceptance, each of these is superseded or amended by this record, not edited
|
||||
so 0092's rule that an operator's value is never replaced does not apply to them.
|
||||
- [ADR 0043](0043-a-module-broker-account-is-scoped-by-emits-and-consumes.md) is amended: a broker
|
||||
account is created by the broker's provisioner, not the controller. Its scoping stands.
|
||||
- [ADR 0049](0049-a-consumers-identity-fits-the-tightest-backend.md) is amended: the mesh derives two
|
||||
logins per consumer, and both fit the tightest backend.
|
||||
- [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; the vault is installed as soon as the shared runtime base exists, and genesis
|
||||
delivers its secrets to it; the vault is the only maker.
|
||||
[to-be 24](../03-DESIGN/01-to-be/24-the-secrets-vault.md) are amended: rotation overlaps old and new
|
||||
instead of being all-or-nothing, with restarts derived and each step confirmed; the vault is
|
||||
installed as soon as the shared runtime base exists, and genesis delivers its secrets to it; the vault
|
||||
is the only maker.
|
||||
- The [glossary](../00-META/glossary.md) gains *shared secret*, *recipient*, *applies* and *reads at
|
||||
start*, and *reserved provision*, once this record is accepted.
|
||||
- [Issue 103](../04-ISSUES/103-a-container-is-not-recreated-when-a-file-it-reads-changes/00-report.md)
|
||||
@@ -219,17 +238,19 @@ On acceptance, each of these is superseded or amended by this record, not edited
|
||||
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
|
||||
them, never the provider's code, so what a provider requires stays predictable from the catalogue.
|
||||
- The SDK's provider loop gains repeated confirmation of an applied rotation. A credential provider's
|
||||
adapter is unchanged. A data provider's adapter gains a return value.
|
||||
- The SDK's provider loop gains the alternation of two logins per consumer and repeated confirmation
|
||||
of each step. A credential provider's adapter gains one duty, giving both logins the same rights over
|
||||
the consumer's data. A data provider's adapter gains a return value. No consumer module changes.
|
||||
- The broker's provisioner gains every bus account, and the controller loses five separate places it
|
||||
generates a secret today.
|
||||
- 54 modules move from own secrets to vault requirements. Six provider clients export a password
|
||||
generator nothing uses any more; it is removed, so no module can quietly start minting again.
|
||||
- The installation changes order: the vault is installed as soon as the shared runtime base exists,
|
||||
before any other module built on it.
|
||||
- **What got harder:** a rotation waits for its appliers, and how a reader is kept from being locked
|
||||
out while it does is still open (above). A secret some services read only at first start can no
|
||||
longer be "rotated" by a restart that quietly changes nothing; it is refused instead.
|
||||
- **What got harder:** a rotation lasts until its slowest reader has moved, so a reader offline for a
|
||||
week keeps the old login valid for a week. That is shown, and it is the price of never locking anyone
|
||||
out. A provider briefly holds two logins per consumer. A secret some services read only at first
|
||||
start can no longer be "rotated" by a restart that quietly changes nothing; it is refused instead.
|
||||
|
||||
## How it is checked
|
||||
|
||||
@@ -246,9 +267,14 @@ On acceptance, each of these is superseded or amended by this record, not edited
|
||||
| Own secrets are retired | A catalogue test: no definition declares an own secret, with a declared list of exceptions that shrinks to empty. |
|
||||
| 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. |
|
||||
| An operator's value is never rotated by the vault, and genesis's values are | Vault tests: a rotation request on an operator's external key is refused, naming the operator; the same request on a value genesis delivered makes a replacement. |
|
||||
| Rotation is applier-first | A rotation test: readers are not sent the new value until every applier confirms. How lock-out is prevented is open, and its check is written when that is decided. |
|
||||
| Old and new overlap | A rotation test: after an applier adds the new login, both authenticate; readers are released only after it confirms; the old login is removed only after every reader confirms, and then no longer authenticates. |
|
||||
| An offline reader is never locked out | A rotation test with one reader's node offline: it keeps authenticating with the old login throughout, the rotation shows waiting on it, and completes when it returns. |
|
||||
| A bus account's owner keeps the bus | A rotation test on a node agent's bus account: the agent stays connected on the old account until it has confirmed the new one. |
|
||||
| A restarted provisioner can still verify | A rotation test restarting the applier's provisioner mid-rotation: it is delivered both values and confirms. |
|
||||
| Both logins have the same rights | A provider test per credential provider: data written under one of a consumer's logins is read and changed under the other. |
|
||||
| Both logins fit the tightest backend | A controller test: the two derived logins for the longest node and module names fit the limit ADR 0049 sets. |
|
||||
| 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. |
|
||||
| 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. |
|
||||
| Rotation is confirmed | A rotation test: the rotation shows unconfirmed until every reader has restarted with the new login and passed its health check, and the old login is retired. |
|
||||
| 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
|
||||
|
||||
Reference in New Issue
Block a user