Skip to content

Commit 516933d

Browse files
fix(docker): drop the postgres image's Go privilege-drop binary
The upstream postgres image bakes in gosu, a static Go binary the entrypoints run once as root to step down to the postgres user. apt cannot reach it, so its Go stdlib CVEs — 22 HIGH/CRITICAL against Go 1.24.6, all of them fixable and therefore all rejected by the release vulnerability policy — cannot be patched out of the image. The current postgres:18-bookworm still carries gosu 1.19 built against that toolchain, so waiting for a docker-library rebuild is not a fix. setpriv (util-linux) performs the identical uid/gid/supplementary-group switch and execs, and is an ordinary Debian package that OS updates keep patched. The binary is deleted in its own layer, which writes a whiteout, before the stand-in is copied over the same path: a scanner reads a merely-overwritten path as the file the lower layer put there, so an overwrite alone leaves the findings. Running the container as the postgres user instead was measured and rejected: the local development stack bind-mounts server/postgres-data, which Docker creates root-owned, so a non-root container cannot initialise it. Closes #1713 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 33f30c2 commit 516933d

3 files changed

Lines changed: 50 additions & 0 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"hephaestus": patch
3+
---
4+
5+
Clears 22 high and critical vulnerabilities from the PostgreSQL image. The image no longer carries the bundled Go helper that dropped privileges at start-up — the same step now uses a tool the operating-system updates keep patched, so the vulnerabilities cannot come back with the next rebuild. The database initialises, restarts and runs exactly as before, and no configuration changes.

docker/postgres/Dockerfile

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,23 @@ RUN set -eux; \
1010
apt-get update; \
1111
apt-get install -y --no-install-recommends "postgresql-${PG_MAJOR}-partman=${PARTMAN_VERSION}"; \
1212
rm -rf /var/lib/apt/lists/*
13+
14+
# The only Go binary in the image is upstream's gosu, which the entrypoints run once as root to step
15+
# down to the postgres user. apt cannot reach it, so its Go stdlib CVEs — 22 HIGH/CRITICAL against
16+
# Go 1.24.6 when this was written — leave the image only when the binary does. setpriv (util-linux)
17+
# makes the identical switch and is patched by ordinary OS updates. The entrypoints hardcode the
18+
# command name, so the stand-in takes the same path; gosu-setpriv.sh explains the argument handling.
19+
#
20+
# The removal is its own layer on purpose: an image scanner reads a path that a later layer merely
21+
# overwrote as the file the lower layer put there, so the Go binary has to be deleted — which writes
22+
# a whiteout — before the stand-in is copied over the same path.
23+
#
24+
# The build asserts the identity the entrypoints actually depend on — uid, primary gid and the
25+
# supplementary groups, postgres being in ssl-cert too — rather than trusting the stand-in.
26+
RUN rm /usr/local/bin/gosu
27+
COPY gosu-setpriv.sh /usr/local/bin/gosu
28+
RUN set -eux; \
29+
chmod 0755 /usr/local/bin/gosu; \
30+
expected="$(id -u postgres):$(id -g postgres):$(id -G postgres)"; \
31+
actual="$(gosu postgres sh -c 'echo "$(id -u):$(id -g):$(id -G)"')"; \
32+
[ "${expected}" = "${actual}" ]

docker/postgres/gosu-setpriv.sh

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#!/bin/sh
2+
# Installed over /usr/local/bin/gosu in the image built by the Dockerfile beside this file.
3+
#
4+
# The official postgres entrypoints (docker-entrypoint.sh, docker-ensure-initdb.sh) each call
5+
# `gosu postgres "$BASH_SOURCE" "$@"` exactly once, as root, to drop to the postgres user before
6+
# re-running themselves. Upstream's gosu is a static Go binary, so every Go stdlib CVE lands in this
7+
# image with nothing apt can do about it. setpriv (util-linux) makes the same uid/gid/supplementary
8+
# -group switch and then execs, and it is an ordinary Debian package that OS updates keep patched.
9+
#
10+
# Only the `gosu <user> <command> [args...]` form those entrypoints use is supported. A `user:group`
11+
# spec or an option fails loudly, because guessing at gosu's semantics would mean running a command
12+
# with privileges nobody asked for.
13+
set -eu
14+
15+
if [ "$#" -lt 2 ] || [ "${1#-}" != "$1" ] || [ "${1#*:}" != "$1" ]; then
16+
echo "gosu (setpriv stand-in): usage: gosu <user> <command> [args...]" >&2
17+
exit 1
18+
fi
19+
20+
user="$1"
21+
shift
22+
23+
# --init-groups reproduces gosu's supplementary groups (postgres is also in ssl-cert); the primary
24+
# group is read from the passwd entry rather than assumed to share the user's name.
25+
exec setpriv --reuid "$user" --regid "$(id -g "$user")" --init-groups -- "$@"

0 commit comments

Comments
 (0)