diff --git a/script/example-app b/script/example-app index 52bd4c6..d9053d1 100755 --- a/script/example-app +++ b/script/example-app @@ -5,12 +5,14 @@ # served through smallwebwaf, that a second one in a minute bans the # client, that a probe for /.env bans another client, which its next # request bans for good, that `sv stop` stops smallwebwaf in order, that -# `docker stop` stops the container without having to kill it, and that -# a new container on the same volume still refuses the banned client. The -# containers run with SWWAF_LOOKUP_SOURCE=off, so that no address is sent -# to GeoJS. The containers, the volume 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. +# `docker stop` stops the container without having to kill it, that a +# new container on the same volume still refuses the banned client, and +# that a container with its state files on a new volume mounted at the +# directory SWWAF_STATE_DIR_FILE names becomes healthy. The containers +# run with SWWAF_LOOKUP_SOURCE=off, so that no address is sent to GeoJS. +# 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 SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)" @@ -23,10 +25,15 @@ IMAGE="$NAME-base" APP_IMAGE="$NAME-app" CONTAINER="$NAME" 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() { 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 } @@ -76,6 +83,27 @@ start_container() { 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 # default. refused() { @@ -140,6 +168,11 @@ main() { wait_for "smallwebwaf logged no request refused under the ban" \ logged '"action":"banned"' 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 "$@" diff --git a/share/smallwebwaf.run b/share/smallwebwaf.run index a13a17b..c3cb9b4 100755 --- a/share/smallwebwaf.run +++ b/share/smallwebwaf.run @@ -9,8 +9,25 @@ set -euo pipefail # reaches smallwebwaf itself. main() { 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 } +# 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 "$@"