Build the smallwebwaf image on Ubuntu 26.04 LTS with nixpkgs #37
@@ -147,20 +147,23 @@ For each request `smallwebwaf`:
|
|||||||
due, and writes the log line.
|
due, and writes the log line.
|
||||||
|
|
||||||
A minimal deployment is the app's own Dockerfile, built on the `smallwebwaf`
|
A minimal deployment is the app's own Dockerfile, built on the `smallwebwaf`
|
||||||
image, with no setting. Beyond its `FROM` line it adds the app's binary, any
|
image, with no setting. That image is built on Ubuntu 26.04 LTS, the newest
|
||||||
packages it needs, and the app's runit service, which starts the app as a user
|
long-term support release of Ubuntu, pinned by digest, and moves to the next one
|
||||||
of its own, listening on `127.0.0.1:8081`:
|
when it ships. It has nixpkgs installed, so the app adds the packages it needs
|
||||||
|
from nixpkgs. Beyond its `FROM` line the app's Dockerfile adds the app's binary,
|
||||||
|
any packages it needs, and the app's runit service, which starts the app as a
|
||||||
|
user of its own, listening on `127.0.0.1:8081`:
|
||||||
|
|
||||||
```dockerfile
|
```dockerfile
|
||||||
# The smallwebwaf image, pinned by digest.
|
# The smallwebwaf image, pinned by digest.
|
||||||
FROM <registry>/smallwebwaf:<pinned digest>
|
FROM <registry>/smallwebwaf:<pinned digest>
|
||||||
|
|
||||||
# Packages the app needs, if any.
|
# Packages the app needs, if any, from the nixpkgs in the image.
|
||||||
RUN apk add --no-cache tzdata
|
RUN nix-env -iA nixpkgs.git
|
||||||
|
|
||||||
# The app's binary, and a user of its own to run it.
|
# The app's binary, and a user of its own to run it.
|
||||||
COPY app /usr/local/bin/app
|
COPY app /usr/local/bin/app
|
||||||
RUN adduser -D -H -s /sbin/nologin app
|
RUN useradd --system --no-create-home --shell /usr/sbin/nologin app
|
||||||
|
|
||||||
# The app's runit service.
|
# The app's runit service.
|
||||||
COPY --chmod=755 app.run /etc/service/app/run
|
COPY --chmod=755 app.run /etc/service/app/run
|
||||||
@@ -169,8 +172,9 @@ COPY --chmod=755 app.run /etc/service/app/run
|
|||||||
with `app.run` beside the Dockerfile, where `--listen` and `--trusted-proxies`
|
with `app.run` beside the Dockerfile, where `--listen` and `--trusted-proxies`
|
||||||
stand for the app's own options:
|
stand for the app's own options:
|
||||||
|
|
||||||
```sh
|
```bash
|
||||||
#!/bin/sh
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
sleep 1
|
sleep 1
|
||||||
exec chpst -u app:app /usr/local/bin/app \
|
exec chpst -u app:app /usr/local/bin/app \
|
||||||
--listen 127.0.0.1:8081 \
|
--listen 127.0.0.1:8081 \
|
||||||
@@ -180,6 +184,10 @@ exec chpst -u app:app /usr/local/bin/app \
|
|||||||
- The image's entrypoint, `runsvinit`, has runit start `smallwebwaf` and the app
|
- The image's entrypoint, `runsvinit`, has runit start `smallwebwaf` and the app
|
||||||
side by side, each as its own user, and start either again a second after it
|
side by side, each as its own user, and start either again a second after it
|
||||||
exits. Leave out `ENTRYPOINT` and `USER` from the app's Dockerfile.
|
exits. Leave out `ENTRYPOINT` and `USER` from the app's Dockerfile.
|
||||||
|
- `nix-env -iA nixpkgs.<name>` installs a package from the nixpkgs in the image,
|
||||||
|
and the app finds it on its `PATH`. That nixpkgs is fixed at one commit, so
|
||||||
|
the same `smallwebwaf` image always gives the app the same packages; newer
|
||||||
|
ones come with a newer `smallwebwaf` image.
|
||||||
- Deploy it as you deploy any app, with traefik's labels on this one container
|
- Deploy it as you deploy any app, with traefik's labels on this one container
|
||||||
pointing at port 8080. upaas needs no change for this.
|
pointing at port 8080. upaas needs no change for this.
|
||||||
- The app has to trust `127.0.0.1` and `::1` for forwarded headers, besides the
|
- The app has to trust `127.0.0.1` and `::1` for forwarded headers, besides the
|
||||||
|
|||||||
@@ -44,10 +44,11 @@ from a directory of hand-editable text files.
|
|||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
- One statically linked Go binary, shipped in an Alpine Linux image that is the
|
- One statically linked Go binary, shipped in an image built on Ubuntu 26.04 LTS
|
||||||
base of the app's own image, so that `smallwebwaf` and the app run in one
|
with nixpkgs installed, which is the base of the app's own image, so that
|
||||||
container (see "Deployment"). There runit starts `smallwebwaf` as its own
|
`smallwebwaf` and the app run in one container (see "Deployment"). There runit
|
||||||
non-root user, and `smallwebwaf` writes only to its state directory.
|
starts `smallwebwaf` as its own non-root user, and `smallwebwaf` writes only
|
||||||
|
to its state directory.
|
||||||
- One listener, on port 8080, which traefik routes to. It forwards requests to
|
- One listener, on port 8080, which traefik routes to. It forwards requests to
|
||||||
the app on `127.0.0.1:8081`, and it answers the endpoints of `smallwebwaf`
|
the app on `127.0.0.1:8081`, and it answers the endpoints of `smallwebwaf`
|
||||||
itself for health, metrics and ban management, under `/_smallwebwaf/`, which
|
itself for health, metrics and ban management, under `/_smallwebwaf/`, which
|
||||||
@@ -1136,18 +1137,27 @@ One JSON object per alert:
|
|||||||
The recommended deployment puts `smallwebwaf` inside the app's own container:
|
The recommended deployment puts `smallwebwaf` inside the app's own container:
|
||||||
the app's Dockerfile builds `FROM` the `smallwebwaf` image. Each app remains one
|
the app's Dockerfile builds `FROM` the `smallwebwaf` image. Each app remains one
|
||||||
container, deployed as before, and an app is hardened just by changing its base
|
container, deployed as before, and an app is hardened just by changing its base
|
||||||
image and perhaps installing on top of it the packages it needs. In the
|
image and perhaps installing on top of it, from nixpkgs, the packages it needs.
|
||||||
container, `smallwebwaf` listens on port 8080, which traefik routes to, and
|
In the container, `smallwebwaf` listens on port 8080, which traefik routes to,
|
||||||
forwards each request to the app on `127.0.0.1:8081`, its default backend
|
and forwards each request to the app on `127.0.0.1:8081`, its default backend
|
||||||
(`SWWAF_UPSTREAM_URL`). No setting is required.
|
(`SWWAF_UPSTREAM_URL`). No setting is required.
|
||||||
|
|
||||||
The image holds:
|
The image holds:
|
||||||
|
|
||||||
- Alpine Linux, so that an app built on it installs packages with `apk add`.
|
- Ubuntu 26.04 LTS, the newest long-term support release of Ubuntu, pinned by
|
||||||
- runit, and `runsvinit` as the entrypoint. `runsvinit` starts runit's
|
digest. The image moves to the next LTS release when that ships.
|
||||||
`runsvdir`, which starts a `runsv` for each directory under `/etc/service`;
|
- Nix, the package manager, from Ubuntu's own `nix-bin` package, and nixpkgs,
|
||||||
each `runsv` runs the `run` script in its directory, and runs it again
|
the collection of packages Nix installs from, fixed at one commit (see
|
||||||
whenever it exits.
|
"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
|
||||||
|
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
|
- The `smallwebwaf` binary, and a user of its own, `smallwebwaf` (uid and gid
|
||||||
65532).
|
65532).
|
||||||
- The service directory `/etc/service/smallwebwaf`, whose `run` script waits one
|
- The service directory `/etc/service/smallwebwaf`, whose `run` script waits one
|
||||||
@@ -1158,15 +1168,20 @@ The image holds:
|
|||||||
- Port 8080 declared (`EXPOSE 8080`), and the health check described below
|
- Port 8080 declared (`EXPOSE 8080`), and the health check described below
|
||||||
(`HEALTHCHECK`).
|
(`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.
|
||||||
|
|
||||||
Beyond its `FROM` line, the app's Dockerfile adds:
|
Beyond its `FROM` line, the app's Dockerfile adds:
|
||||||
|
|
||||||
- its binary;
|
- its binary;
|
||||||
- any packages it needs, with `apk add`;
|
- any packages it needs, from nixpkgs, with `nix-env -iA nixpkgs.<name>`;
|
||||||
- its runit service: a directory under `/etc/service` named after the app, never
|
- its runit service: a directory under `/etc/service` named after the app, never
|
||||||
`smallwebwaf`, whose `run` script starts with `sleep 1` and then starts the
|
`smallwebwaf`, whose `run` script waits one second (`sleep 1`) and then starts
|
||||||
app with `chpst`, listening on `127.0.0.1:8081`, as a user of its own that the
|
the app with `chpst`, listening on `127.0.0.1:8081`, as a user of its own that
|
||||||
Dockerfile creates. That user is neither root nor `smallwebwaf`, so that the
|
the Dockerfile creates with `useradd`. That user is neither root nor
|
||||||
app cannot touch the state files or the process of `smallwebwaf`.
|
`smallwebwaf`, so that the app cannot touch the state files or the process of
|
||||||
|
`smallwebwaf`.
|
||||||
|
|
||||||
It sets no `ENTRYPOINT` or `USER` of its own: the container must start
|
It sets no `ENTRYPOINT` or `USER` of its own: the container must start
|
||||||
`runsvinit`, as root, so that it can start each service as its own user.
|
`runsvinit`, as root, so that it can start each service as its own user.
|
||||||
@@ -1178,12 +1193,12 @@ listens on and the proxies it trusts as options of its own:
|
|||||||
# The smallwebwaf image, pinned by digest.
|
# The smallwebwaf image, pinned by digest.
|
||||||
FROM <registry>/smallwebwaf:<pinned digest>
|
FROM <registry>/smallwebwaf:<pinned digest>
|
||||||
|
|
||||||
# Packages the app needs, if any.
|
# Packages the app needs, if any, from the nixpkgs in the image.
|
||||||
RUN apk add --no-cache tzdata
|
RUN nix-env -iA nixpkgs.git
|
||||||
|
|
||||||
# The app's binary, and a user of its own to run it.
|
# The app's binary, and a user of its own to run it.
|
||||||
COPY app /usr/local/bin/app
|
COPY app /usr/local/bin/app
|
||||||
RUN adduser -D -H -s /sbin/nologin app
|
RUN useradd --system --no-create-home --shell /usr/sbin/nologin app
|
||||||
|
|
||||||
# The app's runit service.
|
# The app's runit service.
|
||||||
COPY --chmod=755 app.run /etc/service/app/run
|
COPY --chmod=755 app.run /etc/service/app/run
|
||||||
@@ -1191,14 +1206,27 @@ COPY --chmod=755 app.run /etc/service/app/run
|
|||||||
|
|
||||||
with `app.run` beside the Dockerfile:
|
with `app.run` beside the Dockerfile:
|
||||||
|
|
||||||
```sh
|
```bash
|
||||||
#!/bin/sh
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
sleep 1
|
sleep 1
|
||||||
exec chpst -u app:app /usr/local/bin/app \
|
exec chpst -u app:app /usr/local/bin/app \
|
||||||
--listen 127.0.0.1:8081 \
|
--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
|
--trusted-proxies 10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,127.0.0.1/32,::1/128
|
||||||
```
|
```
|
||||||
|
|
||||||
|
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.<name>`, 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.
|
||||||
|
|
||||||
The two processes:
|
The two processes:
|
||||||
|
|
||||||
- `runsvinit` is the container's first process and runs as root, as do runit's
|
- `runsvinit` is the container's first process and runs as root, as do runit's
|
||||||
|
|||||||
Reference in New Issue
Block a user