A stray /var/www/html/.htaccess (dated 2025-02-18, predating everything else in TODO.md) was silently requiring HTTP Basic Auth for jayfield.org and www.jayfield.org's shared DocumentRoot, 401ing every real visitor to the intentionally-public static site. Removed (backed up, not deleted). Also adds a README table auditing which public hostnames do/don't require credentials, prompted by checking this.
8.5 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.
-
Public static site (
jayfield.org/www.jayfield.org) was 401'ing every visitor — done 2026-07-20 Found while auditing which services are reachable without credentials: a stray/var/www/html/.htaccess(AuthType Basic, sameAuthName "Restricted Content"/AuthUserFile /etc/apache2/.htpasswdas the intentionaldyndns.jayfield.orgprotection) was silently requiring login for the DocumentRoot bothjayfield.organdwww.jayfield.orgshare —/var/wwwhasAllowOverride all, so it took effect with no vhost-level directive needed. File was dated 2025-02-18 (index.html/apache.htmlthemselves are from 2022) — predates every other change in this doc by well over a year, almost certainly a forgotten leftover rather than intentional, and contradictsREADME.md's own description of both as a public static site. Fixed: moved to/root/removed-configs-backup/htaccess-var-www-html.<timestamp>(recoverable, not deleted — same convention as this file's other removed-config backups). No Apache reload needed (.htaccessis read per-request). Verified bothjayfield.organdwww.jayfield.orgnow return200with real page content;dyndns.jayfield.org's own (intentional, vhost-level) Basic Auth is untouched and still returns401without credentials. -
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.