check / check (push) Waiting to run
SWWAF_WAF_BODY_LIMIT (default off, at most 1G) has the Core Rule Set read form data and multipart up to the limit, the rest streaming on, and JSON and XML no larger than it, with text/json and the application and text types ending in +json or +xml. The part read is held for the app. A size or time limit met while reading ends the request. Content-Encoding is refused again on these kinds. A body Coraza cannot parse, or a multipart body failing its strict checks, adds 5, as does a multipart body the limit cuts before the colon of a part's header. Coraza is built with no_fs_access, so writes no file. Rule 900300 moves to phase 2. Judgement call: Content-Encoding is refused on a JSON or XML body too large to read, as SPEC.md allows. Model: opus-5-5
215 lines
9.4 KiB
Docker
215 lines
9.4 KiB
Docker
# Lint phase. The linter is invoked directly rather than through `make
|
|
# lint` or `script/lint`, which are themselves a docker build and would
|
|
# recurse into a daemon that does not exist in a build step.
|
|
#
|
|
# golangci/golangci-lint v2.14.0 (built with go1.27.0), 2026-09-24
|
|
FROM golangci/golangci-lint@sha256:ad862ba6b3798cbe0fd9fd7408d498fd74fbd2623a92406b2fd3898faf0bf98f AS lint
|
|
|
|
WORKDIR /src
|
|
|
|
COPY go.mod go.sum ./
|
|
RUN go mod download
|
|
|
|
COPY . .
|
|
|
|
RUN golangci-lint run --config .golangci.yml ./...
|
|
|
|
# Test phase, same shape and for the same reason. The go directive in
|
|
# go.mod is a minimum, so this Go may be newer than the linter's. The
|
|
# Debian image rather than the Alpine one, because the race detector
|
|
# needs the C compiler it carries.
|
|
#
|
|
# golang 1.27.1-trixie, 2026-09-19
|
|
FROM golang@sha256:3b77fc618ec235a1ab412de7737f120dd507c57e8d87de4cbb7994fb94275ed5 AS test
|
|
|
|
WORKDIR /src
|
|
|
|
COPY go.mod go.sum ./
|
|
RUN go mod download
|
|
|
|
COPY . .
|
|
|
|
# go.mod and go.sum must be as `go mod tidy` writes them, which is what
|
|
# `make tidy` does. Checked before the tests, which a missing go.sum line
|
|
# fails with a message that does not name `make tidy`.
|
|
RUN go mod tidy -diff || \
|
|
{ echo "go.mod or go.sum is not tidy: run make tidy" >&2; exit 1; }
|
|
|
|
# Go's build cache is kept on a tmpfs, out of the image: nothing uses it
|
|
# after this step, and writing it into the image takes seconds. The tests
|
|
# are built with the no_fs_access tag, as the binary is in the build stage.
|
|
RUN --mount=type=tmpfs,target=/root/.cache/go-build \
|
|
go test -tags no_fs_access -timeout 90s -race -cover ./... || \
|
|
{ echo "--- Rerunning with -v for details ---"; \
|
|
go test -tags no_fs_access -timeout 90s -race -v ./...; exit 1; }
|
|
|
|
# Tidy stage: `go mod tidy` in the test phase's Go, so that the files it
|
|
# writes pass the test phase's check. Nothing else depends on it, so only
|
|
# script/tidy, which names the stage after it, builds it.
|
|
#
|
|
# golang 1.27.1-trixie, 2026-09-19
|
|
FROM golang@sha256:3b77fc618ec235a1ab412de7737f120dd507c57e8d87de4cbb7994fb94275ed5 AS tidy
|
|
|
|
WORKDIR /src
|
|
|
|
COPY . .
|
|
|
|
RUN go mod tidy
|
|
|
|
# go.mod and go.sum alone, which script/tidy writes into the working tree.
|
|
FROM scratch AS tidy-files
|
|
|
|
COPY --from=tidy /src/go.mod /src/go.sum /
|
|
|
|
# Build stage. Nothing is wanted from the lint and test phases; the copies
|
|
# are what make BuildKit build them first, so the image, which needs this
|
|
# stage, cannot be produced unless lint and test passed.
|
|
#
|
|
# golang 1.27.1-trixie, 2026-09-19
|
|
FROM golang@sha256:3b77fc618ec235a1ab412de7737f120dd507c57e8d87de4cbb7994fb94275ed5 AS builder
|
|
|
|
COPY --from=lint /src/go.sum /dev/null
|
|
COPY --from=test /src/go.sum /dev/null
|
|
|
|
# This image has git. A tar-stream context keeps the sender's file
|
|
# owners, which git refuses.
|
|
RUN git config --system --add safe.directory /src
|
|
|
|
WORKDIR /src
|
|
|
|
COPY go.mod go.sum ./
|
|
RUN go mod download
|
|
|
|
COPY . .
|
|
|
|
# The VERSION build arg when one is given, otherwise
|
|
# `git describe --tags --always` on the .git in the build context. With
|
|
# .git present, a version that is still empty, dev or unknown fails the
|
|
# build: git is missing or could not read the checkout. The no_fs_access
|
|
# tag keeps Coraza from writing the files of a multipart body to the
|
|
# system's temporary directory, since smallwebwaf writes only to its state
|
|
# directory.
|
|
ARG VERSION
|
|
RUN VERSION="${VERSION:-$(git describe --tags --always)}"; \
|
|
if [ -e .git ]; then \
|
|
case "$VERSION" in ""|dev|unknown) \
|
|
echo "version is '$VERSION' although .git is present" >&2; \
|
|
exit 1 ;; \
|
|
esac; \
|
|
fi; \
|
|
CGO_ENABLED=0 go build -tags no_fs_access -trimpath \
|
|
-ldflags="-s -w -X main.Version=${VERSION}" \
|
|
-o /usr/local/bin/smallwebwaf ./cmd/smallwebwaf
|
|
|
|
# runsvinit, the image's entrypoint, built at the last commit of its
|
|
# archived repository. It has no go.mod, and `go build` of its directory
|
|
# needs one; it uses only the standard library, so the one written here
|
|
# names nothing else.
|
|
#
|
|
# golang 1.27.1-trixie, 2026-09-19
|
|
FROM golang@sha256:3b77fc618ec235a1ab412de7737f120dd507c57e8d87de4cbb7994fb94275ed5 AS runsvinit
|
|
|
|
RUN git clone --quiet https://github.com/peterbourgon/runsvinit /src
|
|
WORKDIR /src
|
|
# runsvinit v2.0.0-8-gb4b2c78, 2015-10-07
|
|
RUN git checkout --quiet --detach b4b2c785308b1ce785b6155c7fe5f16879080193 \
|
|
&& go mod init github.com/peterbourgon/runsvinit \
|
|
&& CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" \
|
|
-o /usr/local/bin/runsvinit .
|
|
|
|
# The image an app's Dockerfile builds FROM, described under "Deployment"
|
|
# in SPEC.md. It is the last stage, so a plain `docker build .` builds it.
|
|
#
|
|
# ubuntu 26.04, 2026-09-27
|
|
FROM ubuntu@sha256:f144425ff09be612d6d9ad965196e9cdc23dae1f42110a8a11a3e9a8198759f7
|
|
|
|
# runit's install creates its _runit-log user with minsysusers, which
|
|
# reads this file in place of runit's /usr/lib/sysusers.d/runit.conf.
|
|
# runit's line leaves out the shell, and minsysusers prints a Perl
|
|
# warning for that; this copy of it names /sbin/nologin, the shell
|
|
# minsysusers gives when none is named.
|
|
RUN mkdir /etc/sysusers.d \
|
|
&& echo 'u _runit-log - "runit svlogd user" /nonexistent /sbin/nologin' \
|
|
> /etc/sysusers.d/runit.conf
|
|
|
|
# ca-certificates, nix-bin and runit, from Ubuntu's archive as it was at
|
|
# the snapshot moment, which is never earlier than the Ubuntu image above.
|
|
# apt checks every package against the snapshot's InRelease files, and
|
|
# this step checks those against the hashes named here, which are those
|
|
# of the amd64 archive: other architectures use Ubuntu's ports archive.
|
|
# apt also fetches the live archive's InRelease files, which change daily
|
|
# and which the install does not use. The snapshot service is HTTPS only
|
|
# and this image has no CA certificates yet, so this step uses the Go
|
|
# image's.
|
|
RUN --mount=type=bind,from=builder,source=/etc/ssl/certs/ca-certificates.crt,target=/tmp/go-image-ca.crt \
|
|
apt-get update --snapshot 20261001T000000Z \
|
|
-o Acquire::https::CaInfo=/tmp/go-image-ca.crt \
|
|
&& printf '%s\n' \
|
|
'45f95ce276cdba3e41870516a130e03c58b8b7a79e9546b0efe9e526d255740c snapshot.ubuntu.com_ubuntu_20261001T000000Z_dists_resolute_InRelease' \
|
|
'802e675dd9de4c7f3916434a95e7c1d8eec0e82886622d7805ab19a2c6fe0365 snapshot.ubuntu.com_ubuntu_20261001T000000Z_dists_resolute-updates_InRelease' \
|
|
'64b3353f0bd4970b4f7271962245bcea9ff24d4cc7bea16b433f8a60e42ca3dd snapshot.ubuntu.com_ubuntu_20261001T000000Z_dists_resolute-backports_InRelease' \
|
|
'1d5041572116a8b23aabf79ac7439ad8af83d57ad3fb0f9aa0d4523ec10c5908 snapshot.ubuntu.com_ubuntu_20261001T000000Z_dists_resolute-security_InRelease' \
|
|
| (cd /var/lib/apt/lists && sha256sum --check --strict) \
|
|
&& DEBIAN_FRONTEND=noninteractive apt-get install --yes --no-install-recommends \
|
|
--snapshot 20261001T000000Z \
|
|
-o Acquire::https::CaInfo=/tmp/go-image-ca.crt \
|
|
ca-certificates nix-bin runit \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
|
|
# Nix run by root expects a group of build users, which nix-bin does not
|
|
# create; with the setting empty, root's builds run without them.
|
|
RUN mkdir /etc/nix && echo 'build-users-group =' > /etc/nix/nix.conf
|
|
|
|
# nixpkgs, from its release file, checked by SHA-256, and set up for root
|
|
# as `nixpkgs`, so that an app's Dockerfile installs a package with
|
|
# `nix-env -iA nixpkgs.<name>`. curl and xz come with nix-bin.
|
|
#
|
|
# nixpkgs nixos-26.05.11045.774debe7a0d1, 2026-10-02
|
|
RUN curl -fsSL -o /tmp/nixexprs.tar.xz \
|
|
https://releases.nixos.org/nixos/26.05/nixos-26.05.11045.774debe7a0d1/nixexprs.tar.xz \
|
|
&& echo 'b2994104605601690023a5a6a3bb5a07b2bd1716b4e3b208cba1056dacd2ab08 /tmp/nixexprs.tar.xz' \
|
|
| sha256sum --check --strict \
|
|
&& mkdir -p /root/.nix-defexpr/nixpkgs \
|
|
&& tar -xJf /tmp/nixexprs.tar.xz -C /root/.nix-defexpr/nixpkgs --strip-components=1 \
|
|
&& rm /tmp/nixexprs.tar.xz
|
|
|
|
# What root installs with nix-env lands in root's profile. This path to
|
|
# it works for every user, unlike /root/.nix-profile: only root can
|
|
# enter /root. It comes last, so that no package shadows the image's
|
|
# own tools: busybox, for one, brings an sv that looks for services
|
|
# elsewhere.
|
|
ENV PATH=${PATH}:/nix/var/nix/profiles/default/bin
|
|
|
|
COPY --from=runsvinit /usr/local/bin/runsvinit /usr/local/bin/runsvinit
|
|
COPY --from=builder /usr/local/bin/smallwebwaf /usr/local/bin/smallwebwaf
|
|
|
|
# 65532 is above the uids Ubuntu keeps for system users, which end at
|
|
# 999; useradd warns about it unless --key raises that end for this call.
|
|
RUN groupadd --system --gid 65532 smallwebwaf \
|
|
&& useradd --system --key SYS_UID_MAX=65532 --uid 65532 \
|
|
--gid smallwebwaf --no-create-home --shell /usr/sbin/nologin \
|
|
smallwebwaf
|
|
|
|
# The state files' directory, SWWAF_STATE_DIR by default, where a volume
|
|
# is mounted to keep them across deploys. The run script gives it to the
|
|
# smallwebwaf user at each start.
|
|
RUN mkdir /var/lib/smallwebwaf
|
|
|
|
# The default rule file, in SWWAF_RULES_DIR by default, where an app's
|
|
# Dockerfile can copy rule files of its own beside it.
|
|
COPY share/rules.d/00-default.rules /etc/smallwebwaf/rules.d/00-default.rules
|
|
|
|
# runsvinit starts runit's runsvdir on /etc/service, where Ubuntu's sv
|
|
# looks too.
|
|
COPY --chmod=755 share/smallwebwaf.run /etc/service/smallwebwaf/run
|
|
|
|
EXPOSE 8080
|
|
|
|
# traefik sends a container no requests until it is healthy, so the
|
|
# check runs every second from the start until it first passes, for up
|
|
# to a minute, and every 30 seconds after that.
|
|
HEALTHCHECK --start-period=1m --start-interval=1s \
|
|
CMD ["/usr/local/bin/smallwebwaf", "healthcheck"]
|
|
|
|
ENTRYPOINT ["/usr/local/bin/runsvinit"]
|