aboutsummaryrefslogtreecommitdiffstats
path: root/docs
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-11-08 16:27:35 +0000
committertv <tv@krebsco.de>2026-11-08 17:40:49 +0100
commit5f69ecf6284a8061a2b8fae5b8f98d67a67dad5f (patch)
tree4af21471a3b7d76ba61cbe60455385774708aacb /docs
parentd0b773065c441d3e153d9d53bca20ead655b17e7 (diff)
remake: turning a running swarm strict, keeping what an operator signed
Until a swarm is strict, the user a change names is its node's word, so any member could have planted registry entries, policy, bans or user-signed values as any user, plain or signed by a key it put there itself; writing the strict policy trusted all of it as it was. A remake of a new kind, `strict`, turns a swarm strict instead: * Its content is a manifest (access::strict): the swarm, the strict policy, and each value kept at the places values are signed, by the SHA-256 of its JSON, signed by an operator (ssh-keygen -Y sign -n continuum-strict). * Every node builds the next epoch as compaction does, keeping at the signed places only the values the manifest names, as they were at the base, and writing its policy (RemakeKind::protocol, the one kind that changes protocol data). Writes after the base reach the new epoch only by replays, judged by the strict policy. * A node votes for one only with its manifest signed by one of --strict-keys, keys given out of the document; without any, for none (logged once, `Not voting for a strict remake`), unless --follow-strict-remakes. A coordinator proposes only what it would vote for (403). Once a majority commits one, every node switches, as with any remake: refusing to switch would only cut a node off. * GET /admin/strict-manifest gives the manifest keeping everything held now, the values for review and the heads they are at; continuum-signed strict shows them, signs once confirmed and proposes at those heads. The sim a_swarm_turning_strict_through_a_remake: a member plants a registry entry as a manager; the operator's manifest leaves it out; a and b, given the key, commit; c, without, follows; the entry is gone and the policy strict everywhere.
Diffstat (limited to 'docs')
-rw-r--r--docs/access.md37
-rw-r--r--docs/example.toml10
-rw-r--r--docs/openapi.yaml34
-rw-r--r--docs/remakes.md5
4 files changed, 82 insertions, 4 deletions
diff --git a/docs/access.md b/docs/access.md
index 5941bdb..9bd99cc 100644
--- a/docs/access.md
+++ b/docs/access.md
@@ -380,8 +380,11 @@ API changes nothing (`409`). Values there before the policy stay as they are unt
replaced: a registry whose entries hold the users' keys turns strict as it is. As it is
means trusted as it is: whatever entries, policy and bans it holds then count, plain
values and envelopes alike, checked or not, whoever wrote them before (under a policy
-that isn't strict, a member may write as any user). Look them over before turning
-strict (`continuum-signed verify` on each entry). A value there that isn't an envelope
+that isn't strict, a member may write as any user). So a swarm is best strict from its
+start (`--bootstrap-keys`, A new swarm, above); one already running turns strict
+through a remake (Turning a swarm strict, below), which keeps only what an operator
+looked over. Writing the strict policy into it directly trusts it as it is: look it
+over first (`continuum-signed verify` on each entry). A value there that isn't an envelope
starts its chain anew: any envelope its user once signed for that path replacing
nothing (`prev` empty) can be put there again. Without the policy, signed values may
be written all the same, and plain ones too.
@@ -397,6 +400,36 @@ replacing or deleting what holds one (a map made where there is none yet is fine
a member writing to its document directly gets nothing there past the others: the
registry, the policy and the bans change only as a user who may change them signed.
+### Turning a swarm strict
+
+A running swarm turns strict through a remake of its own kind, `strict`: compaction
+that keeps, at the places values are signed (the registry, `_continuum/access`, the
+bans, the paths listed in `userSigned`), only the values an operator looked over and
+signed, and sets the strict policy. What was written after its base reaches the new
+epoch only by replays, which every node judges by the strict policy: a value planted
+before, plain or signed by a key its planter put there itself, is gone unless the
+operator kept it.
+
+Every node is given the keys whose signature it takes on such a remake, out of the
+document (`--strict-keys`, `authorized_keys` lines): one an operator holds. A node
+votes for a strict remake only with its manifest signed by one of them; without any,
+it votes for none (logged, `Not voting for a strict remake`), unless
+`--follow-strict-remakes`. Once a majority commits one, every node switches, as with
+any remake.
+
+```sh
+continuum-signed strict
+```
+
+shows the values it would keep (`GET /admin/strict-manifest`: every value at a signed
+place now, by its digest, and the policy with `"values": "signed"`), signs the
+manifest once confirmed (`ssh-keygen -Y sign -n continuum-strict`, with a key of
+`--strict-keys`), and proposes the remake at the heads it was made at (`{"kind":
+"strict", "spec": {"manifest": "<its line>\n<its signature>"}, "baseHeads": […]}`). To
+keep less, take the manifest from `GET /admin/strict-manifest`, leave out of its `keep`
+what should go, and sign that instead. Reading it needs reading `_continuum`, proposing
+writing `_continuum/remakes`.
+
## Values sealed for their readers
Anyone may read the whole document: every member holds it. A value only some may read
diff --git a/docs/example.toml b/docs/example.toml
index cce8780..2c3a2ab 100644
--- a/docs/example.toml
+++ b/docs/example.toml
@@ -105,6 +105,16 @@ htpasswd = "/etc/continuum/htpasswd"
# together at most.
# key-logins-per-user = 100
+# SSH keys, as authorized_keys lines (ssh-ed25519 <base64> [comment]), whose signature
+# on a strict remake's manifest this node takes: the values kept when the swarm turns
+# strict (docs/access.md). Without, it votes for no strict remake, unless
+# follow-strict-remakes.
+# strict-keys = ["ssh-ed25519 AAAA… alice"]
+
+# Without strict-keys: vote for strict remakes all the same, taking the manifests as
+# the members proposing them sign them.
+# follow-strict-remakes = false
+
# Where the reverse proxies whose user-header is believed connect from: IP addresses (or
# ranges, as ADDRESS/BITS), and unix for the API's Unix sockets (bind-addr). On a host
# with other users, leave out the addresses and have the proxy use a Unix socket: others
diff --git a/docs/openapi.yaml b/docs/openapi.yaml
index de62b59..bb017f0 100644
--- a/docs/openapi.yaml
+++ b/docs/openapi.yaml
@@ -1643,6 +1643,30 @@ paths:
'503':
$ref: '#/components/responses/Error'
+ /admin/strict-manifest:
+ get:
+ tags: [Remakes]
+ summary: A manifest turning the swarm strict
+ description: 'Keeping every value at the places values are signed as it is now (by its digest), with the policy and `"values": "signed"`, for the caller to look over, sign (`ssh-keygen -Y sign -n continuum-strict`) and propose as a `strict` remake at `heads` (`baseHeads`). Needs reading `_continuum`.'
+ responses:
+ '200':
+ description: The manifest's line, the values it keeps, the heads they are at.
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ manifest: { type: string }
+ values:
+ type: array
+ items:
+ type: object
+ properties:
+ path: { type: array, items: { type: string } }
+ value: {}
+ heads: { type: array, items: { type: string } }
+ namespace: { type: string }
+
/admin/epochs:
get:
tags: [Remakes]
@@ -2878,16 +2902,24 @@ components:
properties:
kind:
type: string
- enum: [compact, replace, import]
+ enum: [compact, replace, import, strict]
default: compact
spec:
description: |
`compact`: none. `replace`: `{document: <store root object>, rules}`.
`import`: `{document: <base64 of an Automerge document>, rules}`; its
`_continuum` and history are left out, Automerge types are kept.
+ `strict`: `{manifest: "<its line>\n<its signature>"}`, as
+ `GET /admin/strict-manifest` gives the line, signed by a key of
+ `--strict-keys` (`403` from a node that takes no such manifest).
oneOf:
- type: 'null'
- type: object
+ required: [manifest]
+ additionalProperties: false
+ properties:
+ manifest: { type: string }
+ - type: object
required: [document]
additionalProperties: false
properties:
diff --git a/docs/remakes.md b/docs/remakes.md
index b8e0fa6..fc938c7 100644
--- a/docs/remakes.md
+++ b/docs/remakes.md
@@ -9,8 +9,11 @@ A remake moves the whole swarm to a new document, the next *epoch*. Use one to:
* **import** (`import`): new content from an Automerge document, e.g. a backup made
with `GET /admin/document`. Unlike `replace`, Automerge types survive (text,
counters, bytes, timestamps, floats).
+* **strict** (`strict`): the same as `compact`, turning the swarm strict, keeping only
+ the signed values an operator's manifest names ([`access.md`](access.md), Turning a
+ swarm strict).
-Everything else is the same for all three kinds. All endpoints need the node's basic
+Everything else is the same for all the kinds. All endpoints need the node's basic
auth; examples leave it out.
## How a remake runs