#!/bin/sh # script/example-app: build the image, and on it the example app in # deploy/example-app, then run the app's container with a volume for the # state files and check that the health check passes, that a request is # served through smallwebwaf, that a second one in a minute bans the # client, 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, 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. set -eu SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)" ROOT="$(cd "$SCRIPT_DIR/.." && pwd -P)" # Named after this run, so that runs in other clones on the same host # never touch each other's. NAME="$("$SCRIPT_DIR/projectname")-example-$$" IMAGE="$NAME-base" APP_IMAGE="$NAME-app" CONTAINER="$NAME" VOLUME="$NAME-state" cleanup() { docker rm --force "$CONTAINER" >/dev/null 2>&1 || true docker volume rm --force "$VOLUME" >/dev/null 2>&1 || true docker rmi --force "$APP_IMAGE" "$IMAGE" >/dev/null 2>&1 || true } fail() { echo "example-app: $*; the container's output:" >&2 docker logs "$CONTAINER" >&2 || true exit 1 } # wait_for ...: run the command every second until # it succeeds, for at most a minute. wait_for() { failure="$1" shift tries=0 until "$@"; do tries=$((tries + 1)) [ "$tries" -lt 60 ] || fail "$failure" sleep 1 done } healthy() { status="$(docker inspect --format '{{.State.Health.Status}}' "$CONTAINER")" [ "$status" = healthy ] } # logged : the container's output holds text. logged() { docker logs "$CONTAINER" 2>&1 | grep -qF "$1" } # start_container: run the app's container, with the state files on the # volume and a rate limit of one request a minute, and wait until it is # healthy. start_container() { docker run --detach --name "$CONTAINER" --publish 127.0.0.1::8080 \ --volume "$VOLUME:/var/lib/smallwebwaf" \ --env SWWAF_RATE_LIMIT_PER_MINUTE=1 \ "$APP_IMAGE" >/dev/null wait_for "the health check did not pass" healthy address="$(docker port "$CONTAINER" 8080/tcp)" } # refused: a request to the container gets 403, SWWAF_BAN_RESPONSE's # default. refused() { code="$(curl --silent --output /dev/null --write-out '%{http_code}' \ --max-time 10 "http://$address/")" || true [ "$code" = 403 ] } main() { cd "$ROOT" trap cleanup EXIT trap 'exit 1' HUP INT TERM docker build --no-cache -t "$IMAGE" . docker build --no-cache --build-arg SMALLWEBWAF_IMAGE="$IMAGE" \ -t "$APP_IMAGE" deploy/example-app docker volume create "$VOLUME" >/dev/null start_container echo "example-app: the health check passes" page="$(curl --fail --silent --show-error --max-time 10 "http://$address/")" || fail "no answer on port 8080" [ "$page" = "hello from the example app" ] || fail "port 8080 answered $page" wait_for "smallwebwaf logged no request it forwarded" logged '"action":"forward"' echo "example-app: smallwebwaf passes a request to the app and its answer back" refused || fail "a second request in a minute was not refused" wait_for "smallwebwaf logged no ban" logged '"action":"rate_limited"' echo "example-app: a second request in a minute bans the client" docker exec "$CONTAINER" sv stop smallwebwaf >/dev/null || fail "sv stop smallwebwaf failed" wait_for "smallwebwaf did not stop in order" logged '"msg":"stopped"' echo "example-app: sv stop stops smallwebwaf in order" docker stop "$CONTAINER" >/dev/null status="$(docker inspect --format '{{.State.ExitCode}}' "$CONTAINER")" [ "$status" = 0 ] || fail "docker stop left exit status $status" echo "example-app: docker stop stops the container in order" docker rm "$CONTAINER" >/dev/null start_container refused || fail "the new container let the banned client through" 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" } main "$@"