aboutsummaryrefslogtreecommitdiffstats
path: root/docs
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-11-02 21:34:12 +0100
committertv <tv@krebsco.de>2026-11-02 21:34:12 +0100
commitf2b90bbcdb5347bfb68ee0e3343358e2a8e466dc (patch)
treef33dfaa441d1d9bec58ca7a68aa3172f2bdc838d /docs
parent7e3a474ad15131f5e246582df8d80f595a52da28 (diff)
api: allow auth delegation
Diffstat (limited to 'docs')
-rw-r--r--docs/api.md7
-rw-r--r--docs/dashboard.md6
-rw-r--r--docs/example.toml14
-rw-r--r--docs/openapi.yaml3
-rw-r--r--docs/operations.md22
-rw-r--r--docs/trust.md5
6 files changed, 47 insertions, 10 deletions
diff --git a/docs/api.md b/docs/api.md
index 5b4dac4..e813845 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -31,8 +31,9 @@ curl -u alice:secret -X POST 'localhost:3000/store/stats/visits?increment=1' #
* Bodies are JSON (`application/json`, the default), text (`text/plain`, stored as a
string), or a value of a type of its own (Types); answers are JSON, or as `Accept`
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). Users
- are all alike: each can read and write everything but protocol data (below).
+* 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).
* 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.
@@ -222,7 +223,7 @@ Load balancers can send requests to any ready node: `/ready` is the check.
Every change records the node that committed it (as its Automerge author) and, for a
write through the API, the user who wrote it (in its message, as a trailer like Git's
`Signed-off-by:`: `User: alice`), with when (by the writer's clock). User names are
-each node's own (`--htpasswd`), so who wrote it is that user at that node: `alice` on
+each node's own (`--htpasswd`, or its proxy's), so who wrote it is that user at that node: `alice` on
two nodes is one person only where their operators make it so, which nothing checks.
`GET /admin/changes` lists them,
newest first, with the paths each touched; `GET /admin/changes/settings` those that
diff --git a/docs/dashboard.md b/docs/dashboard.md
index 2d55db1..6a75036 100644
--- a/docs/dashboard.md
+++ b/docs/dashboard.md
@@ -14,10 +14,10 @@ cargo build --release --features mycelium,dashboard
```
It is at `/admin/dashboard` on the node's HTTP API, by default
-`http://127.0.0.1:3000/admin/dashboard`, and asks for a user from `--htpasswd`, as the
-rest of the API does. The API listens on localhost unless `--bind-addr` says otherwise;
+`http://127.0.0.1:3000/admin/dashboard`, and asks for a user from `--htpasswd` (or one
+a reverse proxy names, `--user-header`), as the rest of the API does. The API listens on localhost unless `--bind-addr` says otherwise;
to look at a node on another machine, forward the port (`ssh -L 3000:localhost:3000
-<machine>`) or put a TLS proxy in front ([`trust.md`](trust.md)).
+<machine>`) or put a TLS proxy in front ([`operations.md`](operations.md), Behind a reverse proxy).
Every node has its own dashboard, and shows the swarm as that node sees it: what it
knows of the others, what they report to it, and its own figures.
diff --git a/docs/example.toml b/docs/example.toml
index 95b6f52..67f2cfd 100644
--- a/docs/example.toml
+++ b/docs/example.toml
@@ -51,10 +51,20 @@ node-key-file = "/var/lib/continuum/node.key"
# bind-addr = "127.0.0.1:3000"
# Users of the HTTP API (an htpasswd file with Argon2 hashes). Required (but with
-# relay-only: without it, no HTTP API).
+# user-header, or relay-only: without either, no HTTP API).
htpasswd = "/etc/continuum/htpasswd"
-# HTTP API users (from htpasswd) who may write protocol data under /store/_continuum
+# A request header naming the user, set by a reverse proxy in front of the HTTP API that
+# authenticated the request itself (e.g. X-Remote-User, from nginx's $remote_user).
+# Believed only from trusted-proxies; other requests need htpasswd's basic
+# authentication. The proxy must set or clear it on every request.
+# user-header = "X-Remote-User"
+
+# Addresses (an IP address, or a range as ADDRESS/BITS) of the reverse proxies whose
+# user-header is believed.
+# trusted-proxies = ["127.0.0.1", "::1"]
+
+# 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.
diff --git a/docs/openapi.yaml b/docs/openapi.yaml
index 78912fa..5460a9c 100644
--- a/docs/openapi.yaml
+++ b/docs/openapi.yaml
@@ -7,7 +7,8 @@ info:
as JSON by path, that syncs over Mycelium, libp2p or Tor.
Every endpoint requires HTTP basic authentication (users from `--htpasswd`,
- Argon2 hashes), except `/health`, `/ready`, the Swagger UI (`/swagger-ui`) and
+ Argon2 hashes), or a user named by a trusted reverse proxy in front
+ (`--user-header`), except `/health`, `/ready`, the Swagger UI (`/swagger-ui`) and
this description (`/openapi.yaml`).
`/debug/*` exists only with `--debug-api`.
diff --git a/docs/operations.md b/docs/operations.md
index fe09537..be185f1 100644
--- a/docs/operations.md
+++ b/docs/operations.md
@@ -103,6 +103,28 @@ One node at a time, restarting each with its files. What to know:
switching ([`remakes.md`](remakes.md), Limits). Don't remake while the swarm runs two versions.
* **The dashboard** reloads by itself once its node serves another version of it.
+## Behind a reverse proxy
+
+The HTTP API speaks plain HTTP on `127.0.0.1:3000`; a reverse proxy in front adds TLS,
+and can authenticate users itself (single sign-on, client certificates, its own
+password file). It then names the user in a header, which the node believes from the
+proxy's address alone (`--user-header`, `--trusted-proxies`, by default this host's
+loopback addresses); requests from elsewhere still need `--htpasswd`, which becomes
+optional. The proxy must set or clear that header on every request, or a client could
+set it itself. With nginx:
+
+```nginx
+location / {
+ auth_basic "continuum";
+ auth_basic_user_file /etc/nginx/continuum.htpasswd;
+ proxy_set_header X-Remote-User $remote_user;
+ proxy_pass http://127.0.0.1:3000;
+}
+```
+
+and the node started with `--user-header X-Remote-User`. The names the proxy gives are
+the users of `--protocol-writers` and of who wrote what ([`api.md`](api.md)).
+
## What to alert on
From the metrics (`GET /admin/stats` as Prometheus, [`measurements.md`](measurements.md)):
diff --git a/docs/trust.md b/docs/trust.md
index 521a175..dc80045 100644
--- a/docs/trust.md
+++ b/docs/trust.md
@@ -34,7 +34,10 @@ secret like the swarm key: readable by its owner only (a node warns otherwise; a
it creates is).
**The HTTP API.** Every endpoint but the API description needs a user from
-`--htpasswd`. It listens on `127.0.0.1:3000` by default (`--bind-addr`) and speaks
+`--htpasswd`, or one a reverse proxy names (`--user-header`): the node believes that
+header from `--trusted-proxies` alone, but from those blindly, so whoever can send
+requests from such an address (another user of the same host, with the default
+loopback addresses) can be any user. 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