Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/postgres-image-sheds-its-go-helper.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"hephaestus": patch
---

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.
20 changes: 20 additions & 0 deletions docker/postgres/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,23 @@ RUN set -eux; \
apt-get update; \
apt-get install -y --no-install-recommends "postgresql-${PG_MAJOR}-partman=${PARTMAN_VERSION}"; \
rm -rf /var/lib/apt/lists/*

# The only Go binary in the image is upstream's gosu, which 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 when this was written — leave the image only when the binary does. setpriv (util-linux)
# makes the identical switch and is patched by ordinary OS updates. The entrypoints hardcode the
# command name, so the stand-in takes the same path; gosu-setpriv.sh explains the argument handling.
#
# The removal is its own layer on purpose: an image scanner reads a path that a later layer merely
# overwrote as the file the lower layer put there, so the Go binary has to be deleted — which writes
# a whiteout — before the stand-in is copied over the same path.
#
# The build asserts the identity the entrypoints actually depend on — uid, primary gid and the
# supplementary groups, postgres being in ssl-cert too — rather than trusting the stand-in.
RUN rm /usr/local/bin/gosu
COPY gosu-setpriv.sh /usr/local/bin/gosu
RUN set -eux; \
chmod 0755 /usr/local/bin/gosu; \
expected="$(id -u postgres):$(id -g postgres):$(id -G postgres)"; \
actual="$(gosu postgres sh -c 'echo "$(id -u):$(id -g):$(id -G)"')"; \
[ "${expected}" = "${actual}" ]
25 changes: 25 additions & 0 deletions docker/postgres/gosu-setpriv.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#!/bin/sh
# Installed over /usr/local/bin/gosu in the image built by the Dockerfile beside this file.
#
# The official postgres entrypoints (docker-entrypoint.sh, docker-ensure-initdb.sh) each call
# `gosu postgres "$BASH_SOURCE" "$@"` exactly once, as root, to drop to the postgres user before
# re-running themselves. Upstream's gosu is a static Go binary, so every Go stdlib CVE lands in this
# image with nothing apt can do about it. setpriv (util-linux) makes the same uid/gid/supplementary
# -group switch and then execs, and it is an ordinary Debian package that OS updates keep patched.
#
# Only the `gosu <user> <command> [args...]` form those entrypoints use is supported. A `user:group`
# spec or an option fails loudly, because guessing at gosu's semantics would mean running a command
# with privileges nobody asked for.
set -eu

if [ "$#" -lt 2 ] || [ "${1#-}" != "$1" ] || [ "${1#*:}" != "$1" ]; then
echo "gosu (setpriv stand-in): usage: gosu <user> <command> [args...]" >&2
exit 1
fi

user="$1"
shift

# --init-groups reproduces gosu's supplementary groups (postgres is also in ssl-cert); the primary
# group is read from the passwd entry rather than assumed to share the user's name.
exec setpriv --reuid "$user" --regid "$(id -g "$user")" --init-groups -- "$@"
Loading