From 0cc4bf0cb72b7393c367c8cf03dc65f4829f0c48 Mon Sep 17 00:00:00 2001 From: sneak Date: Sat, 3 Oct 2026 15:46:20 +0000 Subject: [PATCH] Deploy model: listen port, token files, state directory owner (closes #33) SWWAF_LISTEN_ADDR may set another port: the health check takes its port from it, and traefik's port label must name the same one. Its address part stays empty (:9000), so smallwebwaf keeps listening on every address, where traefik and the health check on 127.0.0.1 both reach it. A token file is made on the host owned by uid 65532 with mode 0400 and its directory mounted read-only; through upaas, that directory is one of the app's volume mounts. The run script of smallwebwaf makes the state directory and every file in it belong to the smallwebwaf user. Model: opus-5-5 --- README.md | 7 ++++++- SPEC.md | 56 +++++++++++++++++++++++++++++++++++-------------------- 2 files changed, 42 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 20b760c..651c23c 100644 --- a/README.md +++ b/README.md @@ -313,7 +313,12 @@ exec chpst -u app:app /usr/local/bin/app \ - Port 8080 is the only one the app must leave free: the health check, the metrics and ban management are all on it, under `/_smallwebwaf/`. The image's health check passes while `smallwebwaf` answers and the app accepts - connections. + connections. `SWWAF_LISTEN_ADDR` can move `smallwebwaf` to another port, which + the app then leaves free instead; the health check follows it, and traefik's + labels must point at it. The address part of `SWWAF_LISTEN_ADDR` stays empty + (for example `:9000`, never `127.0.0.1:9000`), so `smallwebwaf` keeps + listening on every address: traefik reaches it on the container's address, and + the health check on `127.0.0.1`. - `smallwebwaf` keeps its state files in `/var/lib/smallwebwaf`. Mount a volume there to keep bans and client history when a deploy replaces the container; without one, it still starts. diff --git a/SPEC.md b/SPEC.md index 918ed49..729b7ed 100644 --- a/SPEC.md +++ b/SPEC.md @@ -1170,8 +1170,9 @@ The image holds: - The `smallwebwaf` binary, and a user of its own, `smallwebwaf` (uid and gid 65532). - The service directory `/etc/service/smallwebwaf`, whose `run` script waits one - second (`sleep 1`), makes `SWWAF_STATE_DIR` belong to the `smallwebwaf` user, - and starts `smallwebwaf` as that user with runit's `chpst`. + second (`sleep 1`), makes `SWWAF_STATE_DIR` and every file in it belong to the + `smallwebwaf` user, and starts `smallwebwaf` as that user with runit's + `chpst`. - The directory `/var/lib/smallwebwaf` for the state files, and `/etc/smallwebwaf/rules.d` with the default rule file (see "Rule files"). - Port 8080 declared (`EXPOSE 8080`), and the health check described below @@ -1261,18 +1262,24 @@ The two processes: status into its directory under `/etc/service`. The health check: the image's `HEALTHCHECK` passes while `smallwebwaf` answers -`GET /_smallwebwaf/healthz` on `127.0.0.1:8080` and the app accepts connections -at the address in `SWWAF_UPSTREAM_URL`, and fails when either does not. The -container therefore shows as healthy only while both processes are up. An app -with a health check of its own can replace the image's `HEALTHCHECK` with one -that checks both. +`GET /_smallwebwaf/healthz` on `127.0.0.1`, at the port in `SWWAF_LISTEN_ADDR`, +and the app accepts connections at the address in `SWWAF_UPSTREAM_URL`, and +fails when either does not. The container therefore shows as healthy only while +both processes are up. An app with a health check of its own can replace the +image's `HEALTHCHECK` with one that checks both. Ports: `smallwebwaf` listens on port 8080 on every address and on no other port; its health check, metrics and ban management are all on that listener, under -`/_smallwebwaf/` (see "Admin endpoints"). The app must leave port 8080 free. It -listens on `127.0.0.1:8081` only, so that nothing outside the container reaches -it except through `smallwebwaf`: an app that listens on every address can be -reached around `smallwebwaf` by anything that reaches the container. +`/_smallwebwaf/` (see "Admin endpoints"). `SWWAF_LISTEN_ADDR` may set another +port: the image's health check takes its port from that setting, and traefik's +port label (`traefik.http.services..loadbalancer.server.port`) must name +the same port, and the app must leave that port free. The address part of +`SWWAF_LISTEN_ADDR` stays empty (for example `:9000`, never `127.0.0.1:9000`), +so `smallwebwaf` keeps listening on every address: traefik reaches it on the +container's address, and the health check on `127.0.0.1`. The app listens on +`127.0.0.1:8081` only, so that nothing outside the container reaches it except +through `smallwebwaf`: an app that listens on every address can be reached +around `smallwebwaf` by anything that reaches the container. State: `smallwebwaf` keeps its state files in `/var/lib/smallwebwaf` (`SWWAF_STATE_DIR`), a directory of its own beside the app's data, which the app @@ -1280,13 +1287,21 @@ keeps in directories of its own, such as `/var/lib/app`. Without a volume there, the files live in the container: they survive a restart of the container and are lost when a deploy replaces it. A volume mounted at `/var/lib/smallwebwaf`, named or a host directory, keeps them across deploys; the `run` script of -`smallwebwaf` makes it belong to the `smallwebwaf` user, so a host directory -mounted there needs no change of owner. It is a volume of its own, separate from -the app's, holds a few tens of MiB at most with the defaults (see "Persistent -state"), and needs no backup beyond whatever the host already does. The image -declares no volume, since every app image built on it would inherit it. -Milestone 2 (https://git.eeqj.de/sneak/smallwebwaf/issues/14) writes no state -files and needs no volume. +`smallwebwaf` makes it and every file in it belong to the `smallwebwaf` user, so +a host directory mounted there needs no change of owner, and files an earlier +owner left in it can be read and replaced. It is a volume of its own, separate +from the app's, holds a few tens of MiB at most with the defaults (see +"Persistent state"), and needs no backup beyond whatever the host already does. +The image declares no volume, since every app image built on it would inherit +it. Milestone 2 (https://git.eeqj.de/sneak/smallwebwaf/issues/14) writes no +state files and needs no volume. + +Tokens: a token given as a file (`SWWAF_ADMIN_TOKEN_FILE`, +`SWWAF_METRICS_TOKEN_FILE`) is out of the app's reach only while the +`smallwebwaf` user alone can read the file. The operator makes the file on the +host, owned by uid 65532, the `smallwebwaf` user, with mode `0400`, and mounts +the directory that holds it into the container read-only; the container sees the +same owner and mode. Forwarded headers: the app's TCP peer is `smallwebwaf` on `127.0.0.1`, and the `X-Forwarded-For` the app receives ends with traefik's address, which @@ -1305,7 +1320,8 @@ and runs the one container as it runs any app, with the app's traefik labels, environment variables and volumes. The labels route to port 8080 (`traefik.http.services..loadbalancer.server.port=8080`), any `SWWAF_` settings go with the app's environment variables, and the volume for -`/var/lib/smallwebwaf` goes beside the app's own. +`/var/lib/smallwebwaf` goes beside the app's own, as does the directory that +holds any token file. - The endpoints of `smallwebwaf` are reached through traefik like any other request, for example `https://app.example.invalid/_smallwebwaf/metrics` for a @@ -1504,7 +1520,7 @@ settings go with the app's environment variables, and the volume for stops the start. The app starts with the same environment variables as `smallwebwaf`, so it can read a token given as one; a token given as a file that only the `smallwebwaf` user can read (`SWWAF_ADMIN_TOKEN_FILE`, - `SWWAF_METRICS_TOKEN_FILE`) is out of the app's reach. + `SWWAF_METRICS_TOKEN_FILE`) is out of the app's reach (see "Deployment"). - GeoJS, the default lookup source: every new visitor's address goes to a third party, and a swarm of fresh addresses, when lookups peak, is when GeoJS may slow down or block `smallwebwaf`. Keeping answers for 7 days and asking about