feat: harden the edge and move the runtime API off TCP
The runtime API was an unauthenticated `level admin` channel on TCP 9999. `expose:` restricts nothing and docker networks have no per-port rules, so it was reachable by every container sharing a network — nginx included — and reaching it means installing your own certificate and key. It is now a unix socket on a volume shared with certbot alone, which also keeps the private key off the network on every renewal. `expose-fd listeners` is dropped: it hands the listening sockets themselves to a client of that socket and only serves seamless reloads, which this image never performs. HAProxy binds a unix socket by creating `<path>.<pid>.tmp` and renaming it, so it needs write access to the directory; the image now creates /var/lib/haproxy owned by uid 1001 and docker carries that onto an empty named volume. Without it HAProxy refuses to start. Also in this commit: - The visitor's address no longer leaves the process. `option forwardfor` is gone, X-Forwarded-For is deleted unconditionally, and X-Client-Id carries an HMAC of the address under the optional XFF_HMAC_KEY instead — one-to-one with the address, so a rate limit keyed on it is as precise, but not reversible. An empty key sends no header rather than one derived from an empty key. - Logging, which was absent entirely. To stdout for `docker logs`, errors-only via the existing dontlog-normal. Both log-format and error-log-format are hand-written: the built-in formats open with %ci:%cp and would have logged the addresses the rest of this works to avoid. The pseudonym is computed by a tcp-request connection rule so a refused handshake has one too. - TLS pinned: floor TLS 1.2, ECDHE-only in ECDSA and RSA variants, no session tickets, ALPN offering HTTP/2. - HTTP redirects to HTTPS, excepting the ACME challenge path, plus HSTS at one day — short deliberately, since the header cannot be recalled once sent. - timeout http-request, which `timeout client` cannot stand in for: that one resets on every byte, so a slow-drip client held a connection indefinitely. - Backends re-resolve through the declared `resolvers docker`, which nothing referenced. Names were resolved once at boot, so a container recreated on a new IP was never noticed. init-addr libc,none lets HAProxy start anyway. - The commented-out /ms deny rules are deleted. They would have blocked /ms/info and /ms/api/v2/whoami, both deliberately public. - compose: build context is the repository root, as the Dockerfile's COPY expects and as CI already did; restart policy added, since an empty certificate volume is a fatal start-up error; the volumes section, which was missing, so the file can actually come up. Verified on testnet2 against the real binary: config parses clean with the key set, empty, and rejects a non-base64 key at parse time; a live stack confirms the pseudonym is stable per address, differs between addresses, survives a forged header, and that the client address appears nowhere in the log.
This commit is contained in:
+17
-4
@@ -1,14 +1,27 @@
|
||||
services:
|
||||
haproxy:
|
||||
build:
|
||||
context: ./docker
|
||||
dockerfile: Dockerfile
|
||||
# The repository root, not ./docker: the Dockerfile copies
|
||||
# ./docker/haproxy.cfg, which a context of ./docker cannot see.
|
||||
context: .
|
||||
dockerfile: ./docker/Dockerfile
|
||||
image: registry.bitdeals.org/haproxy
|
||||
# `bind ... ssl crt` is resolved while the configuration is parsed, so an
|
||||
# empty certificates volume is a fatal start-up error rather than a warning.
|
||||
# certbot writes a self-signed placeholder on its own first start; until it
|
||||
# has, HAProxy needs to keep retrying. Order it after certbot too — in a
|
||||
# project that defines one, add: depends_on: [nginx, certbot]
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- certificates:/usr/local/etc/haproxy/certificates:ro
|
||||
# The runtime API socket. Share this volume with certbot and with nothing
|
||||
# else: reaching the socket is equivalent to holding the TLS private key.
|
||||
- haproxy_admin:/var/lib/haproxy
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
expose:
|
||||
- "9999"
|
||||
|
||||
volumes:
|
||||
certificates:
|
||||
haproxy_admin:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user