Deploy model: listen port, token files, state directory owner (closes #33)
check / check (push) Successful in 2m37s

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
This commit was merged in pull request #41.
This commit is contained in:
2026-10-04 01:42:43 +02:00
parent d76715b0df
commit 983192ace3
2 changed files with 42 additions and 21 deletions
+6 -1
View File
@@ -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.
+36 -20
View File
@@ -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.<name>.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.<name>.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