check / check (push) Waiting to run
GeoJS's geo.json is asked about every new visitor unless SWWAF_LOOKUP_SOURCE is off. A request waits for its client's first answer only while a country list or SWWAF_ADD_LOOKUP_HEADERS needs it; otherwise the answer reaches the client's history and ban notes when it comes. The AS number and name go beside the country in the request log, history, ban notes, alerts and lookups.json, with metrics by AS number; 64512 counts as unknown. A client's own X-Client-ASN and X-Client-Country never reach the app, whatever the setting says, and make example-app sends no address to GeoJS. Judgement call: AS numbers are written AS64496, as SPEC's settings write them. Judgement call: SWWAF_LOOKUP_TIMEOUT is added, default 1s, and cannot be off. Model: opus-5-5
146 lines
5.4 KiB
Bash
Executable File
146 lines
5.4 KiB
Bash
Executable File
#!/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 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.
|
|
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 <what fails> <command>...: 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 <text>...: a line of the container's output holds every text,
|
|
# in any order.
|
|
logged() {
|
|
lines="$(docker logs "$CONTAINER" 2>&1)"
|
|
for text in "$@"; do
|
|
lines="$(printf '%s\n' "$lines" | grep -F "$text")" || return 1
|
|
done
|
|
}
|
|
|
|
# start_container: run the app's container, with the state files on the
|
|
# volume, a rate limit of one request a minute and no client looked up,
|
|
# 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 \
|
|
--env SWWAF_LOOKUP_SOURCE=off \
|
|
"$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 ]
|
|
}
|
|
|
|
# refused_from <client> <path>: a request for path from client, as
|
|
# X-Forwarded-For names it, gets 403. smallwebwaf believes the header
|
|
# from docker's gateway, a private address.
|
|
refused_from() {
|
|
code="$(curl --silent --output /dev/null --write-out '%{http_code}' \
|
|
--max-time 10 --header "X-Forwarded-For: $1" "http://$address$2")" || 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"
|
|
|
|
refused_from 203.0.113.9 /.env || fail "a probe for /.env was not refused"
|
|
wait_for "smallwebwaf logged no ban for the probe" \
|
|
logged '"action":"banned"' '"rule_ids":["env-file"]'
|
|
refused_from 203.0.113.9 / || fail "the client of the probe was let through"
|
|
wait_for "the client's next request did not make its ban permanent" \
|
|
logged '"ban_expires":"permanent"'
|
|
echo "example-app: a probe for /.env bans the client, its next request for good"
|
|
|
|
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 "$@"
|