Moved Portainer's HTTPS UI behind an Apache reverse proxy on its own subdomain (portainer.jayfield.org) instead of publishing it directly on :9443 with a self-signed cert, which HSTS's includeSubDomains policy made unreachable in Firefox with no click-through exception. Same pattern as the existing web.jayfield.org/cloud.jayfield.org proxied subdomains: DNS A record, Apache vhost proxying to 127.0.0.1:9443 over HTTPS, real Let's Encrypt cert via certbot, standard HSTS header. Portainer container recreated to bind 9443 to loopback only; verified end-to-end (cert chain, HSTS header, redirect, and that :9443 is no longer reachable externally).
7.2 KiB
alpha.jayfield.org — TODO
Outstanding hardening items (see README.md for the full service overview).
Nothing here is urgent; all are low-risk, no-downtime changes.
-
Lock down the dyndns TSIG key file permissions — done 2026-07-19
/etc/bind/dyndns.jayfield.org.keywas644(world-readable), holding the shared secret that authenticates dynamic DNS updates fordyndns.jayfield.orgwith no IP-basedallow-updatebacking it up. Fixed:chmod 640+chown root:bind(owner was already correct), matchingrndc.key's permissions. -
Suppress BIND version disclosure — done 2026-07-19 No
versionstatement innamed.conf.options, sodig CH TXT version.bind @ns1.jayfield.orgleaked the exact BIND version (9.18.39) — a minor fingerprinting aid for attackers. Fixed: addedversion "unknown";inside theoptions {}block, validated withnamed-checkconf, and applied viarndc reload. Verifieddig CH TXT version.bindnow returns"unknown". -
Add response rate limiting (RRL) — done 2026-07-19 No
rate-limit {}block was configured. Sinceallow-query { any; }is required for a public authoritative zone, RRL is the standard mitigation against the server being used for spoofed-source DNS reflection/amplification against third parties. Fixed: addedrate-limit { responses-per-second 10; window 5; }tonamed.conf.options, validated withnamed-checkconf, and applied viarndc reload. Started with conservative defaults (10/s, 5s window) — revisit if legitimate high-volume resolvers get throttled. -
Add HSTS headers to all vhosts — done 2026-07-19 No vhost sent a
Strict-Transport-Securityheader, leaving a window for on-path SSL-stripping on a user's very first plaintext request to any domain. Fixed: addedHeader always set Strict-Transport-Security "max-age=63072000; includeSubDomains"to all 6 domain:443blocks (dyndns,jayfield.org,www,mail,web,cloud) — skippeddefault-ssl.confsince it's the self-signed catch-all with no real trusted hostname for a browser to pin. Validated withapache2ctl configtest, applied viasystemctl reload apache2, verified live withcurl -sIagainst all 6 domains. -
jayfield.orghad no:80vhost of its own — done 2026-07-19 Barehttp://jayfield.orgfell through to thedyndnsdefault vhost and redirected to the wrong domain. Fixed: added a:80block tojayfield.org.confredirecting tohttps://jayfield.org/, matching thedyndns/mailpattern. Verified:curl -sI http://jayfield.org/→302withLocation: https://jayfield.org/. -
SSH: disable root login + password auth — done 2026-07-19
PermitRootLogin yesandPasswordAuthentication yeswere both still enabled (the "known deviation" flagged inSETUP.md§1) — every credential-stuffing bot on the internet got a real login prompt to grind against. Fixed:PermitRootLogin no,PasswordAuthentication no,PubkeyAuthentication yesin/etc/ssh/sshd_config(backed up assshd_config.bak.20260719225249). TheMatch Group sftpblock's ownPasswordAuthentication yeswas left as-is — separate, already-chrooted, no-shell/no-forwarding accounts, not part of this risk. Validated withsshd -t, applied viasystemctl reload sshd, verified live withsshd -Tand a fresh key-only connection before considering it done. -
Portainer's
:9443UI is unreachable in HSTS-enforcing browsers — done 2026-07-20https://alpha.jayfield.org:9443threwMOZILLA_PKIX_ERROR_SELF_SIGNED_CERTin Firefox with no click-through option ("keine Ausnahme kann hinzugefügt werden"). Root cause: theincludeSubDomainsHSTS policy sent by the realalpha.jayfield.orgvhost (Strict-Transport-Securityheader, added in the item above) pins the hostname to HTTPS-with-a-trusted-cert on every port, but Portainer (README.md§Docker services) was still serving its stock self-signed cert directly on9443. Not a MITM — self-inflicted by combining HSTS with an unproxied self-signed service. Fixed: added aportainerA record todb.jayfield.org(SOA serial2022032216→2022032217); recreated theportainercontainer binding the HTTPS UI to127.0.0.1:9443:9443instead of publishing it (old container kept renamed until the new one was verified, then removed, matching the update procedure documented inREADME.md); added an Apache vhost forportainer.jayfield.org(ProxyPass/ProxyPassReversetohttps://127.0.0.1:9443/,SSLProxyEngine on,SSLProxyVerify none+SSLProxyCheckPeerCN off/SSLProxyCheckPeerName offfor the loopback hop to Portainer's self-signed cert) — same pattern asweb.jayfield.org/cloud.jayfield.org(SETUP.md§4/§5); issued a real cert viacertbot --apache -d portainer.jayfield.org --redirect; added the standard HSTS header. Left the8000edge-agent tunnel port published as-is — no evidence it's in use. Verified:openssl s_client/curlconfirm the Let's Encrypt cert (not self-signed) andStrict-Transport-Securityheader onhttps://portainer.jayfield.org,http://redirects 301 tohttps://, andss -ltnpshows9443bound to127.0.0.1only (viadocker-proxy), so it's no longer reachable on the public interface at all — access is now exclusively viahttps://portainer.jayfield.org. One transient wrinkle during rollout: the very first few requests through the new proxy vhost returned a stray401with dyndns's Basic Auth realm ("Restricted Content") — looked alarming, butLogLevel trace8on the vhost showed Apache correctly SNI-matching and proxying to the new container with a clean backend200; resolved itself within seconds (looked like a stale pooled proxy connection surviving the container recreate) and was stable across repeated checks before the debug logging was removed again. -
fail2ban:
sshdjail was evadable via low-and-slow attempts — done 2026-07-19 Defaultfindtime=30m/maxretry=3never triggered for47.76.192.176, which had been hittingroot/jayfieldlogins roughly every 33 minutes for most of a day — just outside the 30-minute window, so failures kept aging out before reaching 3 strikes. Confirmed viafail2ban.log(Found/Bancounts) that no other jail showed the same per-IP timing pattern; a separate, distributed single-shot scan onapache-noscriptwas noted but left open (seeREADME.md). Fixed:/etc/fail2ban/jail.d/sshd-findtime.localoverridessshdtofindtime=1d,maxretry=4,bantime=1d, kept in its own drop-in rather than editing the Debian-packaged jail file. Applied viafail2ban-client reload sshd;47.76.192.176was banned on its very next attempt after the change.