From 1dace858e9c74fd6e7680b2f33246b83f0f8f8e0 Mon Sep 17 00:00:00 2001 From: clawbot <35+clawbot@noreply.example.org> Date: Sat, 3 Oct 2026 23:55:58 +0000 Subject: [PATCH] Settle the open points of the Ubuntu and nixpkgs image (closes #38) ca-certificates, nix-bin and runit come from a dated Ubuntu snapshot no older than the pinned Ubuntu image. The Dockerfile names the SHA-256 hash of each snapshot InRelease file apt uses, and the build checks them before apt-get install, so every package is checked against hashed files. That install uses the Go image's CA certificate file. ca-certificates is installed by name. The image writes build-users-group = to /etc/nix/nix.conf so root can build without a daemon. nixpkgs comes from its release file on releases.nixos.org, checked by SHA-256, and takes about 500 MiB of disk. runsvinit is archived upstream and is built at a fixed commit with a go.mod written for the build. The example run scripts put their code in a main function. Model: opus-5-5 --- README.md | 13 +++++--- SPEC.md | 93 +++++++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 79 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 651c23c..b34ec15 100644 --- a/README.md +++ b/README.md @@ -291,10 +291,15 @@ stand for the app's own options: ```bash #!/usr/bin/env bash set -euo pipefail -sleep 1 -exec chpst -u app:app /usr/local/bin/app \ - --listen 127.0.0.1:8081 \ - --trusted-proxies 10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,127.0.0.1/32,::1/128 + +main() { + sleep 1 + exec chpst -u app:app /usr/local/bin/app \ + --listen 127.0.0.1:8081 \ + --trusted-proxies 10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,127.0.0.1/32,::1/128 +} + +main "$@" ``` - The image's entrypoint, `runsvinit`, has runit start `smallwebwaf` and the app diff --git a/SPEC.md b/SPEC.md index 729b7ed..d1a5f30 100644 --- a/SPEC.md +++ b/SPEC.md @@ -1155,16 +1155,29 @@ The image holds: - Ubuntu 26.04 LTS, the newest long-term support release of Ubuntu, pinned by digest. The image moves to the next LTS release when that ships. +- Three packages from Ubuntu, `ca-certificates`, `nix-bin` and `runit`, + installed from a dated snapshot of Ubuntu's archive and checked by hash (see + "Packages from Ubuntu" below). The Ubuntu image has no CA certificates, and + they come with `nix-bin` only because a library it uses recommends them, so + `ca-certificates` is installed by name. Without it Nix cannot download + packages, and `smallwebwaf`, unable to reach GeoJS, would count every visitor + as coming from an unknown country. - Nix, the package manager, from Ubuntu's own `nix-bin` package, and nixpkgs, the collection of packages Nix installs from, fixed at one commit (see "Packages from nixpkgs" below). Root uses Nix directly, and no Nix daemon runs - in the container. -- runit, from Ubuntu's own `runit` package, and `runsvinit` as the entrypoint. - Neither Ubuntu nor nixpkgs packages `runsvinit`, so the image builds it from - its source (`github.com/peterbourgon/runsvinit`) at a version fixed by hash. - `runsvinit` starts runit's `runsvdir`, which starts a `runsv` for each - directory under `/etc/service`; each `runsv` runs the `run` script in its - directory, and runs it again whenever it exits. Ubuntu's runit looks for + in the container. Nix run by root expects a group of build users, `nixbld`, + which `nix-bin` does not create, so the image writes `build-users-group =` to + `/etc/nix/nix.conf`, and root's builds then run without build users. +- runit, from Ubuntu's own `runit` package, and `runsvinit` as the entrypoint, + as the owner's code style guide asks for service containers. Neither Ubuntu + nor nixpkgs packages `runsvinit`, so the image builds it from its source + (`github.com/peterbourgon/runsvinit`) at a fixed commit hash. Its repository + is archived and has not changed since 2015, and its last tag is `v2.0.0`. It + has no `go.mod`, and `go build` of its directory needs one, so the build + writes one; since `runsvinit` uses only Go's standard library, that file names + nothing else. `runsvinit` starts runit's `runsvdir`, which starts a `runsv` + for each directory under `/etc/service`; each `runsv` runs the `run` script in + its directory, and runs it again whenever it exits. Ubuntu's runit looks for services in `/etc/service` too, so when `docker stop` has `runsvinit` stop each service with runit's `sv`, `sv` finds it. - The `smallwebwaf` binary, and a user of its own, `smallwebwaf` (uid and gid @@ -1179,8 +1192,9 @@ The image holds: (`HEALTHCHECK`). Every `run` script, the app's included, is a bash script that starts with -`#!/usr/bin/env bash` and `set -euo pipefail`, as the owner's code style guide -asks; Ubuntu ships bash. +`#!/usr/bin/env bash` and `set -euo pipefail` and puts its code in a `main` +function, called on its last line, as the owner's code style guide asks; Ubuntu +ships bash. Beyond its `FROM` line, the app's Dockerfile adds: @@ -1219,23 +1233,56 @@ with `app.run` beside the Dockerfile: ```bash #!/usr/bin/env bash set -euo pipefail -sleep 1 -exec chpst -u app:app /usr/local/bin/app \ - --listen 127.0.0.1:8081 \ - --trusted-proxies 10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,127.0.0.1/32,::1/128 + +main() { + sleep 1 + exec chpst -u app:app /usr/local/bin/app \ + --listen 127.0.0.1:8081 \ + --trusted-proxies 10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,127.0.0.1/32,::1/128 +} + +main "$@" ``` +Packages from Ubuntu: the image installs `ca-certificates`, `nix-bin` and +`runit` from Ubuntu's snapshot service, which serves the archive as it was at a +given moment, rather than from the archive itself, whose packages change with +every update. The image's Dockerfile names that moment, in apt's +`--snapshot 20261001T000000Z` form, on both `apt-get update` and +`apt-get install`. That moment is never earlier than the date of the pinned +Ubuntu image, since packages from an older snapshot can need older versions of +packages the Ubuntu image already holds, and it moves forward whenever that +image's digest does. `apt-get update` keeps the snapshot's `InRelease` files, +which apt checks against the archive's signature, in `/var/lib/apt/lists/`; each +lists the SHA-256 hash of the package lists it covers, and each package list the +hash of every package in it. The Dockerfile also names the SHA-256 hash of each +`InRelease` file apt uses, and the build checks them after `apt-get update` and +before `apt-get install`, so every package apt installs is checked, through +those files, against hashes the Dockerfile names. The snapshot service is +reached over HTTPS, and the Ubuntu image has no CA certificates of its own, so +this one install uses those of the Go image that `smallwebwaf` is built in, +which is pinned by digest too: apt's `Acquire::https::CaInfo` option names that +image's CA certificate file, `/etc/ssl/certs/ca-certificates.crt`, copied into +the build. + Packages from nixpkgs: nixpkgs is fixed at one commit of its newest release -branch, `nixos-26.05` today. The image's Dockerfile names the commit and the -hash of its contents, and the build checks that hash. nixpkgs is set up for root -under the name `nixpkgs`, so the app's Dockerfile installs a package with -`nix-env -iA nixpkgs.`, and whatever it installs is on the `PATH` of every -service. Because nixpkgs stays at that commit, an app built on the same -`smallwebwaf` image gets the same packages each time it is built. A newer commit -of the branch, with its security fixes, comes with a newer `smallwebwaf` image, -as do Ubuntu's own fixes; an app takes them by changing the digest in its `FROM` -line. When nixpkgs makes its next release, every six months, the image moves to -that release's branch. +branch, `nixos-26.05` today. For each commit of the branch that has passed its +tests, the Nix project publishes a release on `releases.nixos.org`, such as +`nixos-26.05.11045.774debe7a0d1`, and the image takes nixpkgs from that +release's file `nixexprs.tar.xz`, not from a GitHub archive of the commit, whose +bytes can change. The image's Dockerfile names the release and the SHA-256 hash +of that file, which the release's page lists, and the build checks the hash +before unpacking it. nixpkgs is set up for root under the name `nixpkgs`, so the +app's Dockerfile installs a package with `nix-env -iA nixpkgs.`, and +whatever it installs is on the `PATH` of every service. Because nixpkgs stays at +that commit, an app built on the same `smallwebwaf` image gets the same packages +each time it is built. Unpacked, nixpkgs takes about 500 MiB of disk, more on +some filesystems such as ZFS, and each package an app installs from it adds its +own size, with everything it depends on. A newer commit of the branch, with its +security fixes, comes with a newer `smallwebwaf` image, as do Ubuntu's own +fixes; an app takes them by changing the digest in its `FROM` line. When nixpkgs +makes its next release, every six months, the image moves to that release's +branch. The two processes: -- 2.54.0