# 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. - [x] **Lock down the dyndns TSIG key file permissions** — done 2026-07-19 `/etc/bind/dyndns.jayfield.org.key` was `644` (world-readable), holding the shared secret that authenticates dynamic DNS updates for `dyndns.jayfield.org` with no IP-based `allow-update` backing it up. Fixed: `chmod 640` + `chown root:bind` (owner was already correct), matching `rndc.key`'s permissions. - [x] **Suppress BIND version disclosure** — done 2026-07-19 No `version` statement in `named.conf.options`, so `dig CH TXT version.bind @ns1.jayfield.org` leaked the exact BIND version (`9.18.39`) — a minor fingerprinting aid for attackers. Fixed: added `version "unknown";` inside the `options {}` block, validated with `named-checkconf`, and applied via `rndc reload`. Verified `dig CH TXT version.bind` now returns `"unknown"`. - [x] **Add response rate limiting (RRL)** — done 2026-07-19 No `rate-limit {}` block was configured. Since `allow-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: added `rate-limit { responses-per-second 10; window 5; }` to `named.conf.options`, validated with `named-checkconf`, and applied via `rndc reload`. Started with conservative defaults (10/s, 5s window) — revisit if legitimate high-volume resolvers get throttled. - [x] **Add HSTS headers to all vhosts** — done 2026-07-19 No vhost sent a `Strict-Transport-Security` header, leaving a window for on-path SSL-stripping on a user's very first plaintext request to any domain. Fixed: added `Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains"` to all 6 domain `:443` blocks (`dyndns`, `jayfield.org`, `www`, `mail`, `web`, `cloud`) — skipped `default-ssl.conf` since it's the self-signed catch-all with no real trusted hostname for a browser to pin. Validated with `apache2ctl configtest`, applied via `systemctl reload apache2`, verified live with `curl -sI` against all 6 domains. - [x] **`jayfield.org` had no `:80` vhost of its own** — done 2026-07-19 Bare `http://jayfield.org` fell through to the `dyndns` default vhost and redirected to the wrong domain. Fixed: added a `:80` block to `jayfield.org.conf` redirecting to `https://jayfield.org/`, matching the `dyndns`/`mail` pattern. Verified: `curl -sI http://jayfield.org/` → `302` with `Location: https://jayfield.org/`. - [x] **SSH: disable root login + password auth** — done 2026-07-19 `PermitRootLogin yes` and `PasswordAuthentication yes` were both still enabled (the "known deviation" flagged in `SETUP.md` §1) — every credential-stuffing bot on the internet got a real login prompt to grind against. Fixed: `PermitRootLogin no`, `PasswordAuthentication no`, `PubkeyAuthentication yes` in `/etc/ssh/sshd_config` (backed up as `sshd_config.bak.20260719225249`). The `Match Group sftp` block's own `PasswordAuthentication yes` was left as-is — separate, already-chrooted, no-shell/no-forwarding accounts, not part of this risk. Validated with `sshd -t`, applied via `systemctl reload sshd`, verified live with `sshd -T` and a fresh key-only connection before considering it done. - [ ] **Portainer's `:9443` UI is unreachable in HSTS-enforcing browsers** — found 2026-07-20 `https://alpha.jayfield.org:9443` throws `MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT` in Firefox with no click-through option ("keine Ausnahme kann hinzugefügt werden"). Root cause: the `includeSubDomains` HSTS policy sent by the real `alpha.jayfield.org` vhost (`Strict-Transport-Security` header, added in the item above) pins the *hostname* to HTTPS-with-a-trusted-cert on every port, but Portainer (`README.md` §Docker services) still serves its stock self-signed cert directly on `9443`. Not a MITM — self-inflicted by combining HSTS with an unproxied self-signed service. Planned fix (not yet applied): rebind Portainer's published port to `127.0.0.1:9443:9443`, add a `portainer.jayfield.org` Apache vhost (`ProxyPass`/`ProxyPassReverse` to `https://127.0.0.1:9443/`, `SSLProxyEngine on`, `SSLProxyVerify none` for the loopback hop), issue a real cert via `certbot --apache -d portainer.jayfield.org`, and add the standard HSTS header — same pattern already used for `web.jayfield.org`/`cloud.jayfield.org` (`SETUP.md` §4/§5). DNS needs a new `portainer` A record in `db.jayfield.org` first (SOA serial bump). Leave the `8000` edge-agent tunnel port as-is — no evidence it's in use. - [x] **fail2ban: `sshd` jail was evadable via low-and-slow attempts** — done 2026-07-19 Default `findtime=30m`/`maxretry=3` never triggered for `47.76.192.176`, which had been hitting `root`/`jayfield` logins 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 via `fail2ban.log` (`Found`/`Ban` counts) that no other jail showed the same per-IP timing pattern; a separate, *distributed* single-shot scan on `apache-noscript` was noted but left open (see `README.md`). Fixed: `/etc/fail2ban/jail.d/sshd-findtime.local` overrides `sshd` to `findtime=1d`, `maxretry=4`, `bantime=1d`, kept in its own drop-in rather than editing the Debian-packaged jail file. Applied via `fail2ban-client reload sshd`; `47.76.192.176` was banned on its very next attempt after the change.