aboutsummaryrefslogtreecommitdiffstats
path: root/docs
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-11-07 22:35:09 +0100
committertv <tv@krebsco.de>2026-11-07 22:35:09 +0100
commitc82a62186a8e0a46fd41e08345a2fa3e54ddffe2 (patch)
tree068e5bc12c7d13a51f7ba4a4e3e9723cbe33cc7e /docs
parenta8ee17b7513c0367b5a9e5a30ae290fe53dad88b (diff)
access: authorization at the API
Every request is judged by the registry the node holds: store reads pruned to what the user may read, writes refused where it may not, admin endpoints by protocol capabilities. Without a registry, as before (--protocol-writers for protocol data). Auth plan M1.
Diffstat (limited to 'docs')
-rw-r--r--docs/access.md50
-rw-r--r--docs/api.md11
-rw-r--r--docs/dashboard.md6
-rw-r--r--docs/example.toml8
-rw-r--r--docs/openapi.yaml4
-rw-r--r--docs/trust.md18
6 files changed, 80 insertions, 17 deletions
diff --git a/docs/access.md b/docs/access.md
index 8cb1ef9..042e0d5 100644
--- a/docs/access.md
+++ b/docs/access.md
@@ -40,3 +40,53 @@ Its genesis node records the first users, with the swarm ID, in its first change
A swarm nobody could manage doesn't start: with none of them with `["_continuum"]`,
`--genesis` is refused, naming both options.
+
+## At the HTTP API
+
+Every request is checked against the registry, as the node serving it holds it: a
+change to it counts on a node as soon as it has reached it.
+
+* **The store** (`/store`, `/stream`): writing a path (`PUT`, `POST`, `DELETE`) takes
+ `write` on it; `?prune=true` deletes maps left empty only up to the highest one the
+ user may write. Reading takes `read`: of a place the user may read only partly, it
+ sees the keys on the way to what it may read, as a directory one may pass through
+ but not list (a capability on `servers/eu` shows `servers` with `eu` alone in it),
+ and nothing else; a list on the way is left out whole, as its items have no paths of
+ their own. Native forms (`text/plain` of a map, a counter as its own type) only of
+ what it may read whole. What a write answers with is what was written. A stream
+ shows what the user could read when it opened.
+* **Protocol data** is in the store too, under `_continuum`: `read` and `write` on it
+ as on any path.
+* **Looking at the node** (`/admin/stats`, `GET /peers`, the dashboard): any user with
+ a capability.
+* **Remakes** (`/admin/remakes`): `read` on `_continuum/remakes` to look, `write` to
+ propose or abort one. **Forgetting a peer** (`DELETE /peers/…`): `write` on
+ `_continuum/forgotten`.
+* **Everything else an operator does** (`/admin/config`, `/admin/log`,
+ `/admin/document`, `/admin/changes`, `/admin/epochs`, `/admin/quarantine`, `/debug`):
+ `read` on `_continuum` to look, `write` on it to act.
+
+A user the node lets in but the registry doesn't have may do nothing (`403`).
+
+`[]` and `["_continuum"]` are two capabilities on purpose: protocol data acts on the
+whole swarm (its members, its remakes, its users), so it is given apart, never by
+accident with the data. A user who may do both has both.
+
+## An existing swarm
+
+A swarm whose document has no registry (one from before it, `_continuum/users`
+missing) goes on as before: every user a node lets in may read and write the swarm's
+data and use the admin endpoints, and only those in `--protocol-writers` write
+protocol data. It gets a registry once a protocol writer writes one, through the store:
+
+```sh
+curl -u alice --request PUT --header 'Content-Type: application/json' \
+ --data '{"since": 0, "by": "user:alice", "disabled": false,
+ "caps": {"1": {"path": [], "rights": ["read", "write"]},
+ "2": {"path": ["_continuum"], "rights": ["read", "write"]}}}' \
+ http://localhost:3000/store/_continuum/users/alice
+```
+
+From that write on, every node serves only the users the registry has, so the first
+entry must be the writer's own, with `["_continuum"]`, or nobody may change the
+registry again; add the other users the same way.
diff --git a/docs/api.md b/docs/api.md
index cdc722e..5486378 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -33,7 +33,11 @@ curl -u alice:secret -X POST 'localhost:3000/store/stats/visits?increment=1' #
asks. Numbers in JSON are stored as integers where they fit, as floats otherwise.
* Every request needs a user from the node's `--htpasswd` (basic authentication), or
named by a reverse proxy in front that authenticated it (`--user-header`;
- [`operations.md`](operations.md), Behind a reverse proxy). Users are all alike: each can read and write everything but protocol data (below).
+ [`operations.md`](operations.md), Behind a reverse proxy). What each may do, the swarm's user
+ registry says, by path ([`access.md`](access.md)): `403` where it doesn't let the user, and
+ of a part it may only partly read, a read shows the keys on the way to what it may.
+ In a swarm without a registry, users are all alike: each can read and write
+ everything but protocol data (below).
* A key with `/` in it, or an empty key, can be written inside a value at its parent,
but not named in a path: read it through the parent.
@@ -238,8 +242,9 @@ who wrote what, until a remake drops the history ([`remakes.md`](remakes.md)).
`_continuum` holds what the nodes coordinate through: the swarm ID, the member registry,
remakes and votes ([`architecture.md`](architecture.md), The document). It is part of the store and shows
-in `GET /store`, but isn't application data: only users in `--protocol-writers` may
-write it, for repairs. Keep application data out of it, and leave it out when reading
+in `GET /store`, but isn't application data: only users given it may write it
+([`access.md`](access.md); in a swarm without a user registry, those in `--protocol-writers`), for
+repairs. Keep application data out of it, and leave it out when reading
the whole store if it's in the way.
## Large stores
diff --git a/docs/dashboard.md b/docs/dashboard.md
index 3ac587b..9879fb5 100644
--- a/docs/dashboard.md
+++ b/docs/dashboard.md
@@ -273,8 +273,10 @@ to the whole swarm like any other:
* **`+` on a list's row** inserts an item at an index (`-1`: at the end); the items
from there on move up.
-Protocol data (`_continuum`) is written only by users in `--protocol-writers`, and epoch
-metadata and the swarm ID by nobody ([`trust.md`](trust.md)). A key containing `/`, or an empty
+Each user sees and edits what its capabilities let it ([`access.md`](access.md)): of a part it
+may only partly read, the keys on the way to what it may. Protocol data (`_continuum`)
+is written only by users given it (in a swarm without a user registry, those in
+`--protocol-writers`), and epoch metadata and the swarm ID by nobody ([`trust.md`](trust.md)). A key containing `/`, or an empty
key, is listed but can't be opened.
In the protocol data, values and keys get the same readings as in the figures: the
aliases of node IDs (a member's entry, a vote, a coordinator), times and sizes.
diff --git a/docs/example.toml b/docs/example.toml
index 26be818..da808ec 100644
--- a/docs/example.toml
+++ b/docs/example.toml
@@ -80,9 +80,11 @@ htpasswd = "/etc/continuum/htpasswd"
# trusted-proxies = ["127.0.0.1", "::1", "unix"]
# HTTP API users (from htpasswd or user-header) who may write protocol data under /store/_continuum
-# (remake records, members, forget records), e.g. to repair a stuck remake. Such writes
-# act on the whole swarm and are logged. Epoch metadata and the swarm ID are never
-# writable.
+# (remake records, members, forget records), e.g. to repair a stuck remake, in a swarm
+# without a user registry. Such writes act on the whole swarm and are logged. Epoch
+# metadata and the swarm ID are never writable. With a registry its capabilities decide
+# instead (docs/access.md); on a genesis node, those of htpasswd's users named here may
+# do everything in the new swarm's registry.
# protocol-writers = ["alice"]
# With genesis: the new swarm's first users, each may do everything, protocol data and
diff --git a/docs/openapi.yaml b/docs/openapi.yaml
index 0193495..49e0042 100644
--- a/docs/openapi.yaml
+++ b/docs/openapi.yaml
@@ -168,7 +168,7 @@ paths:
'401':
$ref: '#/components/responses/Unauthorized'
'403':
- description: Protocol data (`_continuum`) and the user isn't in `protocol-writers`, or epoch metadata or the swarm ID (`_continuum/epochs`, `_continuum/swarm`, never writable).
+ description: The user may not write here (its capabilities, docs/access.md; in a swarm without a user registry, protocol data `_continuum` and the user isn't in `protocol-writers`), or epoch metadata or the swarm ID (`_continuum/epochs`, `_continuum/swarm`, never writable).
'409':
description: The path crosses an existing non-map value, or with `retype=false` the write would change a type (the message says where).
'415':
@@ -290,7 +290,7 @@ paths:
'401':
$ref: '#/components/responses/Unauthorized'
'403':
- description: Protocol data (`_continuum`) and the user isn't in `protocol-writers`, or epoch metadata or the swarm ID (`_continuum/epochs`, `_continuum/swarm`, never writable).
+ description: The user may not write here (its capabilities, docs/access.md; in a swarm without a user registry, protocol data `_continuum` and the user isn't in `protocol-writers`), or epoch metadata or the swarm ID (`_continuum/epochs`, `_continuum/swarm`, never writable).
'404':
description: No value at this path.
'500':
diff --git a/docs/trust.md b/docs/trust.md
index 4435f99..bcf9bc7 100644
--- a/docs/trust.md
+++ b/docs/trust.md
@@ -40,12 +40,12 @@ requests from there can be any user. With the default loopback addresses, that i
user of the host; with only the API's Unix socket (`--trusted-proxies unix`), those its
file's permissions let in: the node's user and group ([`operations.md`](operations.md), Behind a
reverse proxy). It listens on `127.0.0.1:3000` by default (`--bind-addr`) and speaks
-plain HTTP: to expose it, put a TLS proxy in front. All users may read and write user
-data and use the admin endpoints (remakes, forgetting peers, exports, log filters).
-There are no read-only users: whoever may open the dashboard may also edit and delete
-the document's data from it, and its writes reach the whole swarm.
-Only the users in `--protocol-writers` may write protocol data through the store; epoch
-metadata and the swarm ID are never writable. The `/debug` endpoints exist only with
+plain HTTP: to expose it, put a TLS proxy in front. What each user may do is the
+swarm's: its user registry, in the document, gives each capabilities by path, to read
+and to write, protocol data apart ([`access.md`](access.md)); a user the registry doesn't have
+may do nothing. In a swarm without a registry (one from before it), all users may read
+and write user data and use the admin endpoints, and only those in `--protocol-writers`
+write protocol data. Epoch metadata and the swarm ID are never writable. The `/debug` endpoints exist only with
`--debug-api`.
**The configuration.** Whoever can write a node's configuration file or its options can
@@ -65,6 +65,9 @@ node acts on what the document says:
* **The member registry**: each node writes its own entry, but nothing stops a member
from writing others'. The registry decides the topology and, through the alive
stamps, the electorate of a remake.
+* **The user registry**: written through the API only by users given protocol data,
+ but a member can write any entry into its own document, and every node then serves
+ users as it says; the checks at the API bind honest nodes only.
* **Forget records**: any member can make the swarm forget a peer (until it says HELLO
again).
* **Remakes**: any member can propose one, vote, and, as coordinator, decide. Votes
@@ -114,7 +117,8 @@ Not against a hostile member, but against honest ones getting in each other's wa
([`remakes.md`](remakes.md)).
* A snapshot every node builds itself and checks against the committed epoch, and
downloads checked against their hashes.
-* Protocol data writable through the API only by `--protocol-writers`, and logged.
+* Protocol data writable through the API only by users given it ([`access.md`](access.md)), or
+ in a swarm without a registry by `--protocol-writers`, and logged.
## Not yet