Files
haproxy/docker/Dockerfile
T
bitdeals ea8d5e0589
Build docker image and push to registry.bitdeals.org / main-build-job (push) Successful in 21s
fix: pin the base by digest, and stop overriding its FIPS preference
The public door closed again this morning. haproxy exited at parse time on
every host with

    'bind *:443' : inconsistencies between private key and certificate
    loaded '/usr/local/etc/haproxy/certificates/site.pem'

on a leaf and a private key that openssl confirms are one pair — same public
key, and the same pair certbot has been serving since 2026-08-22. Nothing of
ours had changed: the image was rebuilt at 10:21 by the scheduled build, on a
base that had moved overnight.

The cause is the override this file added two days ago. Measured on testnet2,
same image, same certificate, only the variable changed:

    OPENSSL_FIPS=no    config check fails at `bind *:443`
    OPENSSL_FIPS=yes   config check passes, curl 200, TLSv1.3

`ENV OPENSSL_FIPS=no` was right for the 2026-09-02 base, where preferring the
FIPS provider left haproxy offering an X25519MLKEM768 group it could not use
and refusing every modern client. On today's base the same value stops it
loading an ECDSA certificate at all, while the shipped "yes" both loads it and
still negotiates TLS 1.3 — the thing the override existed to protect. So it is
removed rather than inverted; the comment keeps why, because the right value
depends on the base and the next base may want the other one.

Which is the second change, and the one that matters. `FROM bitnami/haproxy`
carries no tag and the build runs daily, so this image took whatever the base
was each morning: two outages in three days, neither with a commit behind it,
both diagnosed from scratch. The base is pinned by digest now — the one
measured working today. It stops moving on its own, and bumping it becomes a
deliberate act with a check behind it.
2026-09-04 11:30:32 +00:00

66 lines
3.3 KiB
Docker

# Pinned by digest, and that is the whole point of the line. `FROM
# bitnami/haproxy` carries no tag, the scheduled build runs daily, and so this
# image was rebuilt every morning on whatever the base happened to be — twice
# in three days that broke the public door with no commit of ours behind it:
# 2026-09-02 the base began preferring the FIPS provider and TLS 1.3 closed;
# 2026-09-04 the base changed again and haproxy could no longer load the
# certificate at all ("inconsistencies between private key and certificate" on
# a leaf and key that openssl confirms are a pair).
#
# This digest is the base measured working on 2026-09-04: haproxy 3.4.4-7f03ae6,
# serving the live testnet2 certificate, curl 200 over TLS 1.3. Bumping it is a
# deliberate act with a check behind it, which is what the last two mornings
# were missing.
FROM bitnami/haproxy@sha256:c3fac4a702bf242262588491e2541f83d870484ddf65e1c91527208dc1156c49
# 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"]