feat(deploy): host hardening on the frontend server (FH-D.3)
server-setup.sh configured ufw and stopped there, which leaves SSH open
to unlimited password guessing and the kernel on defaults that are wrong
for an internet-facing host.
Adds three drop-in files, so a re-run replaces its own config and never
edits a distro file in place:
/etc/ssh/sshd_config.d/10-marketplaces-hardening.conf
password and keyboard-interactive auth off, root key-only,
no agent/X11 forwarding, MaxAuthTries 3, 30s login grace
/etc/fail2ban/jail.d/marketplaces.local
sshd, nginx-http-auth, nginx-bad-request; 5 in 10m, 1h ban
/etc/sysctl.d/99-marketplaces-hardening.conf
no redirects or source routing, rp_filter, SYN cookies,
forwarding off, restricted kernel pointers and dmesg
Both accounts on the host are key-only by construction - the deploy user
is created with no password at all - so disabling password auth cannot
lock anyone out. It only closes guessing against a credential nobody
intended to exist.
The sshd block runs `sshd -t` first and removes its own drop-in if the
test fails. A bad sshd config that takes effect on a remote box is how
people lock themselves out permanently.
DEPLOYMENT.md §3.2 documents all three plus the post-provision checks.
Not copied from the reference implementation: its hardcoded server IP.
Kept as-is because ours is already better: add-domain.sh pre-checks the
DNS A record and runs nginx -t before and after.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -128,6 +128,90 @@ ufw allow 80/tcp >/dev/null
|
||||
ufw allow 443/tcp >/dev/null
|
||||
ufw --force enable >/dev/null
|
||||
|
||||
# FH-D.3. ufw alone leaves SSH open to unlimited password guessing and leaves
|
||||
# the kernel on defaults that are wrong for an internet-facing host. All three
|
||||
# blocks below are drop-in files, so a re-run overwrites its own config and
|
||||
# never edits a distro file in place.
|
||||
|
||||
echo "==> sshd hardening"
|
||||
cat > /etc/ssh/sshd_config.d/10-marketplaces-hardening.conf <<'SSHD'
|
||||
# Both accounts on this host are key-only by construction (the deploy user is
|
||||
# created with no password at all), so password auth can only ever succeed for
|
||||
# a credential nobody intended to exist.
|
||||
PasswordAuthentication no
|
||||
KbdInteractiveAuthentication no
|
||||
PermitEmptyPasswords no
|
||||
PermitRootLogin prohibit-password
|
||||
X11Forwarding no
|
||||
AllowAgentForwarding no
|
||||
MaxAuthTries 3
|
||||
LoginGraceTime 30
|
||||
ClientAliveInterval 300
|
||||
ClientAliveCountMax 2
|
||||
SSHD
|
||||
# Validate before reloading: a bad sshd config that takes effect on a remote
|
||||
# box is how people lock themselves out permanently.
|
||||
if sshd -t; then
|
||||
systemctl reload ssh 2>/dev/null || systemctl reload sshd
|
||||
else
|
||||
echo "sshd config test FAILED - removing the drop-in and leaving sshd as it was" >&2
|
||||
rm -f /etc/ssh/sshd_config.d/10-marketplaces-hardening.conf
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "==> fail2ban"
|
||||
apt-get install -y -qq fail2ban
|
||||
cat > /etc/fail2ban/jail.d/marketplaces.local <<'F2B'
|
||||
[DEFAULT]
|
||||
backend = systemd
|
||||
findtime = 10m
|
||||
bantime = 1h
|
||||
maxretry = 5
|
||||
|
||||
[sshd]
|
||||
enabled = true
|
||||
|
||||
[nginx-http-auth]
|
||||
enabled = true
|
||||
|
||||
[nginx-bad-request]
|
||||
enabled = true
|
||||
F2B
|
||||
systemctl enable --now fail2ban
|
||||
systemctl restart fail2ban
|
||||
|
||||
echo "==> kernel hardening"
|
||||
cat > /etc/sysctl.d/99-marketplaces-hardening.conf <<'SYSCTL'
|
||||
# Ignore ICMP redirects and source routing: this host has one gateway and
|
||||
# nothing upstream should be rewriting its routing table.
|
||||
net.ipv4.conf.all.accept_redirects = 0
|
||||
net.ipv4.conf.default.accept_redirects = 0
|
||||
net.ipv6.conf.all.accept_redirects = 0
|
||||
net.ipv6.conf.default.accept_redirects = 0
|
||||
net.ipv4.conf.all.send_redirects = 0
|
||||
net.ipv4.conf.default.send_redirects = 0
|
||||
net.ipv4.conf.all.accept_source_route = 0
|
||||
net.ipv6.conf.all.accept_source_route = 0
|
||||
|
||||
# Reverse-path filtering and martian logging.
|
||||
net.ipv4.conf.all.rp_filter = 1
|
||||
net.ipv4.conf.default.rp_filter = 1
|
||||
net.ipv4.conf.all.log_martians = 1
|
||||
|
||||
# SYN flood resistance.
|
||||
net.ipv4.tcp_syncookies = 1
|
||||
net.ipv4.tcp_max_syn_backlog = 2048
|
||||
net.ipv4.tcp_synack_retries = 2
|
||||
|
||||
# No IP forwarding: this is a web server, not a router.
|
||||
net.ipv4.ip_forward = 0
|
||||
|
||||
# Restrict kernel pointer and dmesg exposure to unprivileged users.
|
||||
kernel.kptr_restrict = 2
|
||||
kernel.dmesg_restrict = 1
|
||||
SYSCTL
|
||||
sysctl --quiet --system
|
||||
|
||||
echo "==> nginx config test"
|
||||
nginx -t
|
||||
systemctl enable --now nginx
|
||||
|
||||
Reference in New Issue
Block a user