diff --git a/Dockerfile b/Dockerfile index 0da84e0..eafb6d2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -222,8 +222,8 @@ RUN groupadd --system --gid 65532 sww \ sww # The state files' directory, SWW_STATE_DIR by default, where a volume -# is mounted to keep them across deploys. The run script gives it to the -# sww user at each start of waf. +# is mounted to keep them across deploys. At each start of waf, the run +# script makes SWW_STATE_DIR if it is missing and gives it to the sww user. RUN mkdir /var/lib/sww # The default rule file, in SWW_RULES_DIR by default, where a Dockerfile diff --git a/SPEC.md b/SPEC.md index fdf7196..3c862ee 100644 --- a/SPEC.md +++ b/SPEC.md @@ -2507,11 +2507,11 @@ It sets no `ENTRYPOINT` or `USER` of its own. The container must start - The image's `run` script is `/etc/service/sww/run`. It starts `waf` when `SWW_COMMAND` is `waf`, and `rproxy` when it is `rproxy`, unset or empty. It runs either as the `sww` user (uid and gid 65532), with runit's `chpst`. For - `waf`, it first makes `SWW_STATE_DIR` and every file in it belong to that - user. It also makes `SWW_CACHE_DIR` if it is missing, and gives it and every - file in it to that user. For `rproxy` it changes no owner. With any other - value, it starts nothing, and stops with a message naming `SWW_COMMAND` and - its value. + `waf`, it first makes `SWW_STATE_DIR` if it is missing, gives it and every + file in it to that user, and sets its mode to `0700`. It also makes + `SWW_CACHE_DIR` if it is missing, and gives it and every file in it to that + user. For `rproxy` it changes no owner. With any other value, it starts + nothing, and stops with a message naming `SWW_COMMAND` and its value. Every `run` script, the app's included, is a bash script. It starts with `#!/usr/bin/env bash` and `set -euo pipefail`, and puts its code in a `main` diff --git a/docs/image.md b/docs/image.md index 3928693..27f179c 100644 --- a/docs/image.md +++ b/docs/image.md @@ -19,7 +19,8 @@ way. - The service directory `/etc/service/sww`, with the `run` script `share/sww.run`. - `/var/lib/sww` for `waf`'s state files, and `/etc/sww/rules.d` with the - default rule file `share/rules.d/00-default.rules`. + default rule file `share/rules.d/00-default.rules`. The `run` script makes + `SWW_STATE_DIR` when it is missing. - `EXPOSE 8080`, and the `HEALTHCHECK` that runs `sww healthcheck`. ## Pins diff --git a/docs/settings.md b/docs/settings.md index 945ba3c..1d4e69e 100644 --- a/docs/settings.md +++ b/docs/settings.md @@ -53,7 +53,7 @@ the two (see "The image's command" below). ## State and cache files - `SWW_STATE_DIR` (default `/var/lib/sww`): the directory of the state files, an - absolute path that exists and can be written. + absolute path that can be written. The image's `run` script makes it. - `SWW_STATE_WRITE_DELAY` (default `10s`): how long `bans.json` waits to be written after a ban is made, lifted or made permanent. A duration. - `SWW_STATE_COUNTER_INTERVAL` (default `15m`): how often every state and cache diff --git a/script/example-app b/script/example-app index a85b930..37e77ac 100755 --- a/script/example-app +++ b/script/example-app @@ -18,8 +18,8 @@ # client; # - that a waf container with its state files on a new volume mounted at # the directory SWW_STATE_DIR_FILE names becomes healthy; -# - that one whose SWW_STATE_DIR does not exist logs waf's message -# about it. +# - that the run script makes a SWW_STATE_DIR that does not exist, owned +# by sww with mode 0700, and that waf then becomes healthy. # waf runs with SWW_LOOKUP_SOURCE=off, so that no address is sent to # GeoJS. The containers, the network, the volumes and both images are removed # however the script ends. Building the app needs network access, for nixpkgs' @@ -159,6 +159,12 @@ owner_of() { docker exec "$1" stat --format '%U' "$2" } +# owner_and_mode_of : print who owns dir there, and its +# mode in octal. +owner_and_mode_of() { + docker exec "$1" stat --format '%U %a' "$2" +} + main() { cd "$ROOT" trap cleanup EXIT @@ -251,9 +257,11 @@ main() { --env SWW_STATE_DIR=/srv/missing \ --env SWW_LOOKUP_SOURCE=off \ "$IMAGE" >/dev/null - wait_for "waf did not report the missing state directory" \ - logged "$WAF" "SWW_STATE_DIR cannot be written" - echo "example-app: waf reports a state directory that does not exist" + wait_for "waf's health check did not pass" healthy "$WAF" + [ "$(owner_and_mode_of "$WAF" /srv/missing)" = "sww 700" ] || + fail "the run script did not make SWW_STATE_DIR for sww with mode 0700" + echo "example-app: the run script makes a missing SWW_STATE_DIR for sww" \ + "with mode 0700" } main "$@" diff --git a/share/sww.run b/share/sww.run index 14781f8..af0d6fa 100755 --- a/share/sww.run +++ b/share/sww.run @@ -12,7 +12,7 @@ main() { case "$command" in rproxy) ;; waf) - give_state_dir_to_sww + make_state_dir_for_sww make_cache_dir_for_sww ;; *) @@ -24,28 +24,27 @@ main() { exec chpst -u sww:sww /usr/local/bin/sww "$command" } -# give_state_dir_to_sww gives the state directory and every file in -# it to the sww user, so that a volume mounted there needs no change -# of owner; chown -R changes a symbolic link itself, never what it points -# to. When SWW_STATE_DIR, in either form, cannot be read or is not the -# absolute path of a directory, no owner is changed, and waf starts and -# stops with a message naming the setting. A change of owner that fails, as -# on a volume mounted read-only, does not end the script either: waf -# reports a directory it cannot write. -give_state_dir_to_sww() { +# make_state_dir_for_sww makes the state directory, SWW_STATE_DIR in +# either form, when it is missing, gives it and every file in it to the +# sww user, and sets its mode to 0700. So a volume mounted there needs no +# change of owner; chown -R changes a symbolic link itself, never what it +# points to. When the setting cannot be read or is not an absolute path, +# nothing is changed, and waf starts and stops with a message naming the +# setting. A step that fails, as on a volume mounted read-only, does not +# end the script either: waf reports a directory it cannot write. +make_state_dir_for_sww() { local dir - if dir="$(setting SWW_STATE_DIR /var/lib/sww)" && - [[ "$dir" == /* && -d "$dir" ]]; then - chown -R sww:sww "$dir" || true + if dir="$(setting SWW_STATE_DIR /var/lib/sww)" && [[ "$dir" == /* ]]; then + { mkdir -p -- "$dir" && chown -R sww:sww "$dir" && + chmod 0700 "$dir"; } || true fi } # make_cache_dir_for_sww makes the cache directory, SWW_CACHE_DIR in -# either form, and gives it and every file in it to the sww user, as -# give_state_dir_to_sww does the state directory. Unlike that one, it is -# made when it is missing, since the cache files need no volume. As there, -# a setting that cannot be read or is not an absolute path changes -# nothing, and a failure does not end the script. +# either form, when it is missing, and gives it and every file in it to +# the sww user. As for the state directory, a setting that cannot be read +# or is not an absolute path changes nothing, and a failure does not end +# the script. make_cache_dir_for_sww() { local dir if dir="$(setting SWW_CACHE_DIR /var/cache/sww)" && [[ "$dir" == /* ]]; then