Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e3b159db07 |
@@ -16,7 +16,8 @@ commands that do this together:
|
||||
counters and history. One `servewaf` serves many sites.
|
||||
|
||||
The site keeps answering while `servewaf` is down, slow or restarting. `rproxy`
|
||||
waits 2 seconds for an answer, and then passes the request to the app.
|
||||
waits at most 2 seconds for `servewaf`'s answer, and without one passes the
|
||||
request to the app.
|
||||
|
||||
`rproxy` requires one setting, `SWRP_WAF_BACKEND`, which says where `servewaf`
|
||||
is. In a container of its own, it also needs `SWRP_UPSTREAM_URL`, which says
|
||||
@@ -214,9 +215,9 @@ gave it, and `SWRP_DEFAULT_DISPOSITION` plays no part.
|
||||
## What happens to a request
|
||||
|
||||
A request goes through fifteen steps, in this order. Once `servewaf` refuses a
|
||||
request, the steps after that are skipped, apart from step 15, and nothing of
|
||||
the request reaches the app. "What happens to a request" in [`SPEC.md`](SPEC.md)
|
||||
gives each step in full, under the same numbers.
|
||||
request, the steps after that are skipped, apart from the report of step 15, and
|
||||
nothing of the request reaches the app. "What happens to a request" in
|
||||
[`SPEC.md`](SPEC.md) gives each step in full, under the same numbers.
|
||||
|
||||
`rproxy` takes the first two:
|
||||
|
||||
@@ -242,8 +243,9 @@ gives each step in full, under the same numbers.
|
||||
`rproxy` takes the rest, with `servewaf` counting at the end:
|
||||
|
||||
14. The request is passed to the app.
|
||||
15. After the answer, `rproxy` sends `servewaf` a report. `servewaf` counts the
|
||||
byte limits and adds the request to the client's history.
|
||||
15. After the answer, `rproxy` sends `servewaf` a report. For an allowed
|
||||
request, `servewaf` counts the byte limits and adds the request to the
|
||||
client's history.
|
||||
|
||||
A refused visitor is answered with `SWWAF_BAN_RESPONSE`, `403` by default (see
|
||||
"Ban lengths, response and scope" in [`SPEC.md`](SPEC.md)). A `block` rule or a
|
||||
|
||||
@@ -1579,8 +1579,8 @@ It sets no `ENTRYPOINT` or `USER` of its own. The container must start
|
||||
`SMALLWEBWAF_COMMAND` names, `rproxy` when it is unset or empty, as the
|
||||
`smallwebwaf` user (uid and gid 65532) with runit's `chpst`. For `servewaf`,
|
||||
it first makes `SWWAF_STATE_DIR` and every file in it belong to that user. For
|
||||
`rproxy` it changes no owner. With any other value, `smallwebwaf` prints its
|
||||
commands and exits, and the health check fails.
|
||||
`rproxy` it changes no owner. With any other value, it starts nothing, and
|
||||
stops with a message naming `SMALLWEBWAF_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`
|
||||
@@ -1629,10 +1629,13 @@ says what it checks:
|
||||
- With `SMALLWEBWAF_COMMAND=servewaf`, it passes while `servewaf` answers
|
||||
`GET /_smallwebwaf/healthz` on `127.0.0.1`, at the port in
|
||||
`SWWAF_LISTEN_ADDR`.
|
||||
- Otherwise it passes while `rproxy` answers that request on `127.0.0.1`, at the
|
||||
port in `SWRP_LISTEN_ADDR`, and the app accepts connections at
|
||||
`SWRP_UPSTREAM_URL`. It does not ask `servewaf`, so that the site stays in
|
||||
traefik's routes while `servewaf` is down.
|
||||
- With `SMALLWEBWAF_COMMAND` unset, empty or `rproxy`, it passes while `rproxy`
|
||||
answers that request on `127.0.0.1`, at the port in `SWRP_LISTEN_ADDR`, and
|
||||
the app accepts connections at `SWRP_UPSTREAM_URL`. It does not ask
|
||||
`servewaf`, so that the site stays in traefik's routes while `servewaf` is
|
||||
down.
|
||||
- With any other value, it fails with a message naming `SMALLWEBWAF_COMMAND` and
|
||||
its value.
|
||||
|
||||
traefik sends a container no requests until it shows as healthy. So the check
|
||||
runs every second from the container's start until it first passes, for up to a
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
// servewaf` start with SWWAF_, every one of them has a default, and most can be
|
||||
// given as a file instead. Those of `smallwebwaf rproxy` start with SWRP_. Each
|
||||
// of them but SWRP_WAF_BACKEND has a default, and none can be given as a file,
|
||||
// since rproxy never touches disk.
|
||||
// since rproxy never touches disk. SMALLWEBWAF_COMMAND chooses which of the two
|
||||
// the image runs.
|
||||
package config
|
||||
|
||||
import (
|
||||
@@ -370,6 +371,7 @@ var (
|
||||
errDBPathUnused = errors.New("only file reads it")
|
||||
errOver64K = errors.New(
|
||||
"is more than 64K, the most servewaf gets of a body")
|
||||
errNotCommand = errors.New("is not rproxy or servewaf")
|
||||
errNotDurationAboveZero = errors.New(
|
||||
"is not a duration above zero, such as 1h or 7d")
|
||||
errNotNumberAboveZero = errors.New(
|
||||
@@ -570,6 +572,22 @@ func FromEnvironment(lookupEnv func(string) (string, bool)) (*Config, error) {
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// Command reads only SMALLWEBWAF_COMMAND, the command the image runs and its
|
||||
// health check checks: rproxy when it is unset or empty, or servewaf. It cannot
|
||||
// be given as a file. Any other value is an error that names the setting.
|
||||
func Command(lookupEnv func(string) (string, bool)) (string, error) {
|
||||
command, _ := lookupEnv("SMALLWEBWAF_COMMAND")
|
||||
|
||||
switch command {
|
||||
case "", "rproxy":
|
||||
return "rproxy", nil
|
||||
case "servewaf":
|
||||
return command, nil
|
||||
default:
|
||||
return "", fmt.Errorf("SMALLWEBWAF_COMMAND: %q %w", command, errNotCommand)
|
||||
}
|
||||
}
|
||||
|
||||
// ServeWAFListenAddr reads only SWWAF_LISTEN_ADDR, which may be given as a
|
||||
// file, as FromEnvironment does. The health check in servewaf's container needs
|
||||
// no other setting, so it reads no other, nor a file that another names.
|
||||
|
||||
@@ -21,11 +21,13 @@ var errHealthEndpoint = errors.New("the health endpoint answered")
|
||||
|
||||
// HealthCheck is the container's health check. In servewaf's container, where
|
||||
// SMALLWEBWAF_COMMAND is servewaf, it returns 0 while servewaf answers its health
|
||||
// endpoint on 127.0.0.1, at the port in SWWAF_LISTEN_ADDR. In any other container
|
||||
// it returns 0 while rproxy answers its health endpoint on 127.0.0.1, at the port
|
||||
// in SWRP_LISTEN_ADDR, and the app accepts connections at the address in
|
||||
// SWRP_UPSTREAM_URL. Otherwise it writes why to stderr and returns 1. It reads no
|
||||
// other setting, nor a file that another names, so neither can fail it.
|
||||
// endpoint on 127.0.0.1, at the port in SWWAF_LISTEN_ADDR. In rproxy's container,
|
||||
// where SMALLWEBWAF_COMMAND is rproxy, unset or empty, it returns 0 while rproxy
|
||||
// answers its health endpoint on 127.0.0.1, at the port in SWRP_LISTEN_ADDR, and
|
||||
// the app accepts connections at the address in SWRP_UPSTREAM_URL. Otherwise,
|
||||
// any other value of SMALLWEBWAF_COMMAND included, it writes why to stderr and
|
||||
// returns 1. It reads no other setting, nor a file that another names, so neither
|
||||
// can fail it.
|
||||
// args are the arguments after `healthcheck`; it takes none, and given
|
||||
// one it names it on stderr and returns 1 without checking anything.
|
||||
func HealthCheck(
|
||||
@@ -42,12 +44,17 @@ func HealthCheck(
|
||||
ctx, cancel := context.WithTimeout(ctx, healthCheckTimeout)
|
||||
defer cancel()
|
||||
|
||||
check := checkRProxyAndApp
|
||||
if command, _ := lookupEnv("SMALLWEBWAF_COMMAND"); command == "servewaf" {
|
||||
check = checkServeWAF
|
||||
command, err := config.Command(lookupEnv)
|
||||
|
||||
switch {
|
||||
case err != nil:
|
||||
err = fmt.Errorf("invalid setting: %w", err)
|
||||
case command == "servewaf":
|
||||
err = checkServeWAF(ctx, lookupEnv)
|
||||
default:
|
||||
err = checkRProxyAndApp(ctx, lookupEnv)
|
||||
}
|
||||
|
||||
err := check(ctx, lookupEnv)
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintln(stderr, "unhealthy:", err)
|
||||
|
||||
|
||||
@@ -103,8 +103,13 @@ func TestHealthCheckInRProxysContainer(t *testing.T) {
|
||||
}
|
||||
|
||||
wantHealthCheck(t, settings(), 0, "")
|
||||
wantHealthCheck(t, settings(commandSetting, ""), 0, "")
|
||||
wantHealthCheck(t, settings(commandSetting, "rproxy"), 0, "")
|
||||
|
||||
// SMALLWEBWAF_COMMAND cannot be given as a file.
|
||||
wantHealthCheck(t, settings(commandSetting+"_FILE", writeFile(t, "servewaf\n")),
|
||||
0, "")
|
||||
|
||||
// Another setting does not fail it, even an invalid one, and neither does
|
||||
// SWRP_WAF_BACKEND being unset.
|
||||
wantHealthCheck(t, settings("SWRP_TIMEOUT_MS", "soon"), 0, "")
|
||||
@@ -127,6 +132,14 @@ func TestHealthCheckInRProxysContainer(t *testing.T) {
|
||||
}, 1, "unhealthy: ask rproxy: ")
|
||||
}
|
||||
|
||||
func TestHealthCheckFailsWithAnyOtherCommand(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
wantHealthCheck(t, map[string]string{commandSetting: "healthcheck"}, 1,
|
||||
"unhealthy: invalid setting: SMALLWEBWAF_COMMAND: "+
|
||||
"\"healthcheck\" is not rproxy or servewaf\n")
|
||||
}
|
||||
|
||||
func TestHealthCheckRefusesAnArgument(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
+12
-5
@@ -3,14 +3,21 @@ set -euo pipefail
|
||||
|
||||
# runit's run script for smallwebwaf, run again whenever smallwebwaf
|
||||
# exits; the wait spaces out the restarts. SMALLWEBWAF_COMMAND names the
|
||||
# command to run: rproxy when it is unset, or servewaf. exec, so that the
|
||||
# signal `sv stop` sends reaches smallwebwaf itself.
|
||||
# command to run: rproxy when it is unset or empty, or servewaf. With any
|
||||
# other value the script starts nothing and stops with a message naming it.
|
||||
# exec, so that the signal `sv stop` sends reaches smallwebwaf itself.
|
||||
main() {
|
||||
sleep 1
|
||||
local command="${SMALLWEBWAF_COMMAND:-rproxy}"
|
||||
if [[ "$command" == servewaf ]]; then
|
||||
give_state_dir_to_smallwebwaf
|
||||
fi
|
||||
case "$command" in
|
||||
rproxy) ;;
|
||||
servewaf) give_state_dir_to_smallwebwaf ;;
|
||||
*)
|
||||
printf 'SMALLWEBWAF_COMMAND: "%s" is not rproxy or servewaf\n' \
|
||||
"$command" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
exec chpst -u smallwebwaf:smallwebwaf /usr/local/bin/smallwebwaf "$command"
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user