aboutsummaryrefslogtreecommitdiffstats
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
commitaea80cf1bfa39f17dbbd0b4782a71fca81e09f74 (patch)
treec8fac48a7ee77757695bd69f11aa9f9c5e93751c
parent13cba9b0be736433d8ac44c0ef046cb001918a07 (diff)
docs: tokens through a proxy; token formats later
operations.md: an nginx proxy passing requests with a token on to the node and asking the others for its own login. future-work: other token formats. Auth plan M2.
-rw-r--r--docs/operations.md64
-rw-r--r--src/access/token.rs3
2 files changed, 53 insertions, 14 deletions
diff --git a/docs/operations.md b/docs/operations.md
index fae3b6c..2fd8e7f 100644
--- a/docs/operations.md
+++ b/docs/operations.md
@@ -105,12 +105,32 @@ One node at a time, restarting each with its files. What to know:
## 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 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.
+The HTTP API speaks plain HTTP on `127.0.0.1:3000`; a reverse proxy in front adds TLS.
+Let the node check who users are, as it does: its `--htpasswd`, and the tokens of
+logging in on any node of the swarm ([`access.md`](access.md), Tokens). The proxy then only
+passes requests on, their `Authorization` header with them:
+
+```sh
+continuum --bind-addr unix:/run/continuum/api.sock --htpasswd htpasswd ...
+```
+
+```nginx
+location / {
+ proxy_pass http://unix:/run/continuum/api.sock;
+}
+```
+
+Without `--user-header`, no header names a user, so nothing a client sends through the
+proxy can.
+
+### The proxy authenticating
+
+A proxy can authenticate users itself instead (single sign-on through OpenID Connect,
+client certificates, its own password file), and name 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
@@ -126,14 +146,32 @@ continuum --bind-addr 127.0.0.1:3000,unix:/run/continuum/api.sock \
```
```nginx
-location / {
- auth_basic "continuum";
- auth_basic_user_file /etc/nginx/continuum.htpasswd;
- proxy_set_header X-Remote-User $remote_user;
- proxy_pass http://unix:/run/continuum/api.sock;
+# A request with a token (`Authorization: Bearer`, from logging in on any node of the
+# swarm: access.md, Tokens) goes to the node as it is, which checks the token; any
+# other must pass nginx's own login.
+map $http_authorization $continuum_auth {
+ "~^Bearer " off;
+ default "continuum";
+}
+
+server {
+ # ...
+ location / {
+ auth_basic $continuum_auth;
+ auth_basic_user_file /etc/nginx/continuum.htpasswd;
+ # Empty for a request with a token: then nginx sends no such header.
+ proxy_set_header X-Remote-User $remote_user;
+ proxy_pass http://unix:/run/continuum/api.sock;
+ }
}
```
+Without the `map`, nginx answers a request with a token `401` itself, as `auth_basic`
+takes only a password, and the dashboard's subscriptions and tokens from other nodes
+fail. With it, a client that sets the header itself on a request with a token gets
+nothing from it: nginx sends `X-Remote-User` only as it sets it, empty there, and the
+node takes no user from an empty one.
+
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
@@ -158,8 +196,8 @@ REJECT`). The loopback addresses can then stay trusted; root can still get past
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)).
+The names the proxy gives are the users of the registry ([`access.md`](access.md)), 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, and says it's alive while nothing changes: the dashboard's
diff --git a/src/access/token.rs b/src/access/token.rs
index 5d82aa6..bfbce62 100644
--- a/src/access/token.rs
+++ b/src/access/token.rs
@@ -11,7 +11,8 @@
//!
//! Not Biscuit or UCAN (November 2026): a token only names a user, narrowing is asked
//! of a node (which issues a narrower one), and Biscuit would bring about a hundred
-//! crates, older versions of several of ours among them (Ed25519, SHA-2, protobuf).
+//! crates, older versions of several of ours among them (Ed25519, SHA-2, protobuf):
+//! `future-work.md`, Authentication across nodes.
use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD as BASE64URL};
use serde::{Deserialize, Serialize};