smallwebwaf starts with SWWAF_STATE_DIR given as a file on a new volume (closes #137)
check / check (push) Canceled after 0s

The run script gave ${SWWAF_STATE_DIR:-/var/lib/smallwebwaf} to the
smallwebwaf user and never read SWWAF_STATE_DIR_FILE, so a new volume
at the directory that file names stayed root's and smallwebwaf could
not write it. The run script now reads the setting as smallwebwaf does:
the variable, or what is in the file. When both are set or the file
cannot be read, it leaves the owner alone, and smallwebwaf stops with
its message naming the setting. script/example-app checks that a
container with its state files on a new volume at /srv/state, named in
a file only the smallwebwaf user can read, becomes healthy.

Model: opus-5-5
This commit is contained in:
2026-10-20 09:20:08 +00:00
parent 7a9471df28
commit 95cfbdc03b
2 changed files with 58 additions and 8 deletions
+40 -7
View File
@@ -5,12 +5,14 @@
# served through smallwebwaf, that a second one in a minute bans the # served through smallwebwaf, that a second one in a minute bans the
# client, that a probe for /.env bans another client, which its next # client, that a probe for /.env bans another client, which its next
# request bans for good, that `sv stop` stops smallwebwaf in order, that # request bans for good, that `sv stop` stops smallwebwaf in order, that
# `docker stop` stops the container without having to kill it, and that # `docker stop` stops the container without having to kill it, that a
# a new container on the same volume still refuses the banned client. The # new container on the same volume still refuses the banned client, and
# containers run with SWWAF_LOOKUP_SOURCE=off, so that no address is sent # that a container with its state files on a new volume mounted at the
# to GeoJS. The containers, the volume and both images are removed however # directory SWWAF_STATE_DIR_FILE names becomes healthy. The containers
# the script ends. Building the app needs network access, for nixpkgs' # run with SWWAF_LOOKUP_SOURCE=off, so that no address is sent to GeoJS.
# binary cache. script/check does not run this. # The containers, the volumes and both images are removed however the
# script ends. Building the app needs network access, for nixpkgs' binary
# cache. script/check does not run this.
set -eu set -eu
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)" SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)"
@@ -23,10 +25,15 @@ IMAGE="$NAME-base"
APP_IMAGE="$NAME-app" APP_IMAGE="$NAME-app"
CONTAINER="$NAME" CONTAINER="$NAME"
VOLUME="$NAME-state" VOLUME="$NAME-state"
# The last container's: its state files, away from the default path, and
# the file that names their directory.
MOVED_VOLUME="$NAME-moved-state"
SETTINGS_VOLUME="$NAME-settings"
cleanup() { cleanup() {
docker rm --force "$CONTAINER" >/dev/null 2>&1 || true docker rm --force "$CONTAINER" >/dev/null 2>&1 || true
docker volume rm --force "$VOLUME" >/dev/null 2>&1 || true docker volume rm --force "$VOLUME" "$MOVED_VOLUME" "$SETTINGS_VOLUME" \
>/dev/null 2>&1 || true
docker rmi --force "$APP_IMAGE" "$IMAGE" >/dev/null 2>&1 || true docker rmi --force "$APP_IMAGE" "$IMAGE" >/dev/null 2>&1 || true
} }
@@ -76,6 +83,27 @@ start_container() {
address="$(docker port "$CONTAINER" 8080/tcp)" address="$(docker port "$CONTAINER" 8080/tcp)"
} }
# start_moved_container: run the app's container with the state files on
# a new volume at /srv/state, which SWWAF_STATE_DIR_FILE names in a file
# only the smallwebwaf user can read, as under "Settings given as files"
# in README.md, and wait until it is healthy. smallwebwaf can write the
# new volume only once the run script has given it to that user.
start_moved_container() {
docker volume create "$MOVED_VOLUME" >/dev/null
docker volume create "$SETTINGS_VOLUME" >/dev/null
docker run --rm --volume "$SETTINGS_VOLUME:/settings" \
--entrypoint bash "$IMAGE" -c 'echo /srv/state > /settings/state-dir &&
chown smallwebwaf:smallwebwaf /settings/state-dir &&
chmod 0400 /settings/state-dir'
docker run --detach --name "$CONTAINER" \
--volume "$MOVED_VOLUME:/srv/state" \
--volume "$SETTINGS_VOLUME:/etc/smallwebwaf/settings:ro" \
--env SWWAF_STATE_DIR_FILE=/etc/smallwebwaf/settings/state-dir \
--env SWWAF_LOOKUP_SOURCE=off \
"$APP_IMAGE" >/dev/null
wait_for "the health check did not pass" healthy
}
# refused: a request to the container gets 403, SWWAF_BAN_RESPONSE's # refused: a request to the container gets 403, SWWAF_BAN_RESPONSE's
# default. # default.
refused() { refused() {
@@ -140,6 +168,11 @@ main() {
wait_for "smallwebwaf logged no request refused under the ban" \ wait_for "smallwebwaf logged no request refused under the ban" \
logged '"action":"banned"' logged '"action":"banned"'
echo "example-app: a new container on the same volume keeps the ban" echo "example-app: a new container on the same volume keeps the ban"
docker rm --force "$CONTAINER" >/dev/null
start_moved_container
echo "example-app: the run script gives the state files' directory named" \
"by SWWAF_STATE_DIR_FILE to the smallwebwaf user"
} }
main "$@" main "$@"
+18 -1
View File
@@ -9,8 +9,25 @@ set -euo pipefail
# reaches smallwebwaf itself. # reaches smallwebwaf itself.
main() { main() {
sleep 1 sleep 1
chown -R smallwebwaf:smallwebwaf "${SWWAF_STATE_DIR:-/var/lib/smallwebwaf}" local dir
if dir="$(state_dir)"; then
chown -R smallwebwaf:smallwebwaf "${dir:-/var/lib/smallwebwaf}"
fi
exec chpst -u smallwebwaf:smallwebwaf /usr/local/bin/smallwebwaf exec chpst -u smallwebwaf:smallwebwaf /usr/local/bin/smallwebwaf
} }
# state_dir prints SWWAF_STATE_DIR as smallwebwaf reads it: the variable,
# or what is in the file SWWAF_STATE_DIR_FILE names. It fails when both
# are set or the file cannot be read; the state directory then keeps its
# owner, and smallwebwaf stops with a message naming the setting.
state_dir() {
if [[ ! -v SWWAF_STATE_DIR_FILE ]]; then
printf '%s\n' "${SWWAF_STATE_DIR:-}"
elif [[ ! -v SWWAF_STATE_DIR ]]; then
cat -- "$SWWAF_STATE_DIR_FILE" 2>/dev/null
else
return 1
fi
}
main "$@" main "$@"