Files
haproxy/docker/Dockerfile
T
bitdeals 1824e741bb
Build docker image and push to registry.bitdeals.org / main-build-job (push) Successful in 30s
fix: unpin the base again; the stand is what catches a bad one
ea8d5e0 pinned `FROM bitnami/haproxy` by digest after the base broke the public
door twice in three days. That is not the trade this project wants: there are no
version tags to pin to, only `latest`, so a pin means a digest raised by hand for
every security update of the base.

What actually catches a bad base is the testnet stand. Watchtower there picks up
each rebuild within seconds, which is precisely how both incidents surfaced —
loudly, on a stand, and not in production. Production runs no Watchtower and is
updated by hand, so a base that breaks the door is seen here first and never
reaches it unattended.

The `ENV OPENSSL_FIPS=no` removal from ea8d5e0 stays. Restoring it would restore
this morning's outage: on the current base "no" is what stops HAProxy loading an
ECDSA certificate. The comment above FROM keeps both measurements, because the
right value depends on the base and the next base may want the other one — that
is the first thing to check the next time this image will not start.

Verified on testnet2 with the image built from this file: config check clean
against the project config and the live certificate, curl 200, TLSv1.3.
2026-09-04 11:59:16 +00:00

83 lines
4.2 KiB
Docker

# Deliberately untagged, and deliberately without an OPENSSL_FIPS of our own.
#
# The base moves, and twice in three days a move of it closed the public door
# with no commit of ours behind it. On 2026-09-02 the base began preferring
# OpenSSL's FIPS provider — Photon's openssl.cnf reads the preference straight
# out of the environment as `default_properties = ?fips=$ENV::OPENSSL_FIPS` —
# and that provider cannot produce an X25519MLKEM768 key, so HAProxy offered a
# group it could not use and refused every client that asks for post-quantum key
# exchange. `ENV OPENSSL_FIPS=no` fixed that, and was right for that base.
#
# On the 2026-09-04 base it is the other way round: with "no" HAProxy cannot load
# an ECDSA certificate at all and exits at parse time on `bind *:443`, saying
# "inconsistencies between private key and certificate" about a leaf and key that
# openssl confirms are one pair. The base's own "yes" loads it and still
# negotiates TLS 1.3. Measured on testnet2 against the live certificate, same
# image, only the variable changed:
#
# OPENSSL_FIPS=no config check fails at `bind *:443`
# OPENSSL_FIPS=yes config check passes, curl 200, TLSv1.3
#
# So the value that is right depends on the base, and hard-coding either one is
# how the next base move breaks this image. Take what the base ships.
#
# The base is not pinned either: there are no version tags to pin to (only
# `latest` exists), and a digest would have to be raised by hand for every
# security update. What catches a bad base is the testnet stand — Watchtower
# there picks up each rebuild within seconds, which is exactly how both of these
# were found. **Production does not run Watchtower and is updated by hand**, so
# a base that breaks the door is seen on the stand first. When it happens again,
# the two lines above are the first thing to check.
FROM bitnami/haproxy
# The FIPS preference is left exactly as the base ships it, and that is a
# reversal. The base is Photon OS, whose openssl.cnf reads the variable straight
# out of the environment:
#
# [alg_sect]
# default_properties = ?fips=$ENV::OPENSSL_FIPS
#
# On the 2026-09-02 base "yes" meant every algorithm fetch preferred the FIPS
# provider, which cannot produce an X25519MLKEM768 key, so haproxy offered a
# group it could not use and refused every modern client. `ENV OPENSSL_FIPS=no`
# fixed that and was correct for that base.
#
# On the base pinned above it is the other way round: with "no" haproxy cannot
# load an ECDSA certificate at all and exits at parse time, while the shipped
# "yes" loads it and still negotiates TLS 1.3. Measured on testnet2 against the
# live certificate, same image, only the variable changed:
#
# OPENSSL_FIPS=no config check fails at `bind *:443`
# OPENSSL_FIPS=yes config check passes, curl 200, TLSv1.3
#
# So the override is gone rather than inverted. Pinning the base is what makes
# leaving it alone safe: the value that is right depends on the base, and the
# base no longer moves on its own.
# Copy config
COPY ./docker/haproxy.cfg /bitnami/haproxy/conf/haproxy.cfg
# Directory for the runtime API socket. HAProxy runs as uid 1001 here and binds
# a unix socket by creating `<path>.<pid>.tmp` and renaming it over the target,
# so it needs write permission on the *directory*, not just the file — which is
# also why a stale socket left by a previous run is harmless.
#
# /var/lib is owned by root, hence the explicit USER switch. Docker copies this
# ownership onto an empty named volume when it initialises one here, so the
# volume shared with certbot comes up writable by HAProxy without a chown at
# runtime.
USER root
RUN mkdir -p /var/lib/haproxy && chown 1001:1001 /var/lib/haproxy
USER 1001
# The base image's entrypoint is the haproxy binary itself, with no shell in
# between, so this wrapper is the whole chain: it fills in XFF_HMAC_KEY when
# nothing else did (see the script for why that is better than requiring it) and
# execs the same binary with the same arguments. CMD is restated rather than
# left to inheritance — it would be inherited, but a changed ENTRYPOINT is
# exactly where that stops being obvious to the next reader.
COPY --chmod=0755 ./docker/entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
CMD ["-f", "/bitnami/haproxy/conf/haproxy.cfg"]