aboutsummaryrefslogtreecommitdiffstats
path: root/docs
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-11-02 22:46:48 +0100
committertv <tv@krebsco.de>2026-11-02 22:46:48 +0100
commite4331d13f313f439aff9cef1ea221c8adeeebb3a (patch)
tree38648722d73ddf9b1deea6c3b05430994b82f4e6 /docs
parent4ae3326ebca32252020b5899e4a3566d0a6b5ec7 (diff)
api: allow listening to Unix sockets
Diffstat (limited to 'docs')
-rw-r--r--docs/example.toml14
-rw-r--r--docs/operations.md54
-rw-r--r--docs/trust.md6
3 files changed, 58 insertions, 16 deletions
diff --git a/docs/example.toml b/docs/example.toml
index b6a6ad0..43fdaf1 100644
--- a/docs/example.toml
+++ b/docs/example.toml
@@ -52,8 +52,10 @@ node-key-file = "/var/lib/continuum/node.key"
# --- The HTTP API ------------------------------------------------------------------------
-# Address of the HTTP API. Plain HTTP: to expose it, put a TLS proxy in front.
-# bind-addr = "127.0.0.1:3000"
+# Where the HTTP API listens: TCP addresses, and Unix sockets (unix:PATH, made readable
+# and writable by the node's user and group only), as many as given. Plain HTTP: to
+# expose it, put a TLS proxy in front.
+# bind-addr = ["127.0.0.1:3000", "unix:/run/continuum/api.sock"]
# Users of the HTTP API (an htpasswd file with Argon2 hashes). Required (but with
# user-header, or relay-only: without either, no HTTP API).
@@ -65,9 +67,11 @@ htpasswd = "/etc/continuum/htpasswd"
# 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"]
+# 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
+# can connect over loopback too.
+# 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
diff --git a/docs/operations.md b/docs/operations.md
index 4f59208..da73fce 100644
--- a/docs/operations.md
+++ b/docs/operations.md
@@ -107,23 +107,59 @@ One node at a time, restarting each with its files. What to know:
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:
+password file). It then names the user in a header, which the node believes from where
+the proxy connects alone (`--user-header`, `--trusted-proxies`); 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.
+
+Where the proxy connects decides who can pose as it. Every user of a host connects
+from its loopback addresses, so trusting those (as `--trusted-proxies` does by
+default) trusts everyone on the host. A Unix socket is better: `--bind-addr` takes
+`unix:PATH` too, as many places as given, and makes the socket readable and writable
+by the node's user and group only, so only members of that group (the proxy's user,
+added to it) can connect at all. Trust it alone with `--trusted-proxies unix`, and keep
+a TCP address for the rest (a command-line tool, with `--htpasswd`):
+
+```sh
+continuum --bind-addr 127.0.0.1:3000,unix:/run/continuum/api.sock \
+ --user-header X-Remote-User --trusted-proxies unix --htpasswd htpasswd ...
+```
```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;
+ proxy_pass http://unix:/run/continuum/api.sock;
+}
+```
+
+The directory of the socket must exist and let the proxy's user through (with systemd,
+`RuntimeDirectory=continuum` and `RuntimeDirectoryMode=0750`, the proxy's user in the
+node's group). A socket left behind by an earlier run is replaced, and the node removes
+its own when it stops. With a proxy on another host, trust its address instead, and
+have the node listen where only that host reaches it.
+
+Where a socket won't do, the host's firewall can keep everyone but the proxy's user
+off the API's port, as Linux filters local connections by the user that opens them.
+With nftables, and nginx running as `www-data`:
+
+```
+table inet continuum {
+ chain output {
+ type filter hook output priority 0;
+ oifname "lo" tcp dport 3000 meta skuid != "www-data" reject
+ }
}
```
-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)).
+(with iptables: `-A OUTPUT -o lo -p tcp --dport 3000 -m owner ! --uid-owner www-data -j
+REJECT`). The loopback addresses can then stay trusted; root can still get past it, as it
+can past a socket's permissions. Every address of the API on loopback is trusted alike,
+so a command-line tool of another user goes through the proxy then.
+
+The names the proxy gives are the users of `--protocol-writers` and of who wrote what
+([`api.md`](api.md)).
What streams (`/stream`, the dashboard's events) says `X-Accel-Buffering: no`, so nginx
passes it on as it comes. The dashboard's events have a keepalive every 15 s; `/stream`
@@ -131,7 +167,7 @@ sends nothing while nothing changes, so nginx ends a quiet one after its
`proxy_read_timeout` (60 s): raise that for it, or have clients reconnect.
The API may also be served under a path of its own (`location /continuum/ {
-proxy_pass http://127.0.0.1:3000/; }`): its redirects (`Location`), the dashboard's
+proxy_pass http://unix:/run/continuum/api.sock:/; }`): its redirects (`Location`), the dashboard's
requests and the Swagger UI's are relative, so they stay under it.
## What to alert on
diff --git a/docs/trust.md b/docs/trust.md
index dc80045..165fc9b 100644
--- a/docs/trust.md
+++ b/docs/trust.md
@@ -36,8 +36,10 @@ it creates is).
**The HTTP API.** Every endpoint but the API description needs a user from
`--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
+requests from there can be any user. With the default loopback addresses, that is every
+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