121 lines
4.6 KiB
Nix
121 lines
4.6 KiB
Nix
{ config, pkgs, ... }:
|
|
{
|
|
networking.firewall = {
|
|
enable = true;
|
|
# SSH is NOT public — only accessible via Tailscale (trustedInterfaces)
|
|
allowedTCPPorts = [
|
|
80 # HTTP (Caddy ACME + redirect)
|
|
443 # HTTPS
|
|
];
|
|
logReversePathDrops = true;
|
|
|
|
# Kernel-level DDoS protection via iptables
|
|
# These rules fire BEFORE Caddy even sees the packet, so they're very cheap.
|
|
extraCommands = ''
|
|
# ── SYN flood protection ──
|
|
# Limit new TCP connections to 30/sec per source IP (burst 50).
|
|
# Legitimate browsers open ~6 connections; scrapers open hundreds.
|
|
iptables -N RATE_LIMIT 2>/dev/null || iptables -F RATE_LIMIT
|
|
iptables -A RATE_LIMIT -m hashlimit \
|
|
--hashlimit-name syn_flood \
|
|
--hashlimit-above 30/sec \
|
|
--hashlimit-burst 50 \
|
|
--hashlimit-mode srcip \
|
|
--hashlimit-htable-expire 300000 \
|
|
-j DROP
|
|
iptables -A RATE_LIMIT -j RETURN
|
|
|
|
# Hook into INPUT chain for new TCP SYN packets to HTTP/HTTPS
|
|
iptables -C INPUT -p tcp --syn -m multiport --dports 80,443 -j RATE_LIMIT 2>/dev/null || \
|
|
iptables -I INPUT -p tcp --syn -m multiport --dports 80,443 -j RATE_LIMIT
|
|
|
|
# ── Connection limit ──
|
|
# Max 200 concurrent connections per source IP to HTTP/HTTPS.
|
|
# A single browser uses ~6-10; a scraper farm uses thousands.
|
|
iptables -C INPUT -p tcp -m multiport --dports 80,443 -m connlimit --connlimit-above 200 --connlimit-mask 32 -j DROP 2>/dev/null || \
|
|
iptables -I INPUT -p tcp -m multiport --dports 80,443 -m connlimit --connlimit-above 200 --connlimit-mask 32 -j DROP
|
|
|
|
# ── Same for IPv6 ──
|
|
ip6tables -N RATE_LIMIT 2>/dev/null || ip6tables -F RATE_LIMIT
|
|
ip6tables -A RATE_LIMIT -m hashlimit \
|
|
--hashlimit-name syn_flood_v6 \
|
|
--hashlimit-above 30/sec \
|
|
--hashlimit-burst 50 \
|
|
--hashlimit-mode srcip \
|
|
--hashlimit-htable-expire 300000 \
|
|
-j DROP
|
|
ip6tables -A RATE_LIMIT -j RETURN
|
|
|
|
ip6tables -C INPUT -p tcp --syn -m multiport --dports 80,443 -j RATE_LIMIT 2>/dev/null || \
|
|
ip6tables -I INPUT -p tcp --syn -m multiport --dports 80,443 -j RATE_LIMIT
|
|
|
|
ip6tables -C INPUT -p tcp -m multiport --dports 80,443 -m connlimit --connlimit-above 200 --connlimit-mask 64 -j DROP 2>/dev/null || \
|
|
ip6tables -I INPUT -p tcp -m multiport --dports 80,443 -m connlimit --connlimit-above 200 --connlimit-mask 64 -j DROP
|
|
'';
|
|
|
|
# Clean up custom chains on stop
|
|
extraStopCommands = ''
|
|
iptables -D INPUT -p tcp --syn -m multiport --dports 80,443 -j RATE_LIMIT 2>/dev/null || true
|
|
iptables -D INPUT -p tcp -m multiport --dports 80,443 -m connlimit --connlimit-above 200 --connlimit-mask 32 -j DROP 2>/dev/null || true
|
|
iptables -F RATE_LIMIT 2>/dev/null || true
|
|
iptables -X RATE_LIMIT 2>/dev/null || true
|
|
|
|
ip6tables -D INPUT -p tcp --syn -m multiport --dports 80,443 -j RATE_LIMIT 2>/dev/null || true
|
|
ip6tables -D INPUT -p tcp -m multiport --dports 80,443 -m connlimit --connlimit-above 200 --connlimit-mask 64 -j DROP 2>/dev/null || true
|
|
ip6tables -F RATE_LIMIT 2>/dev/null || true
|
|
ip6tables -X RATE_LIMIT 2>/dev/null || true
|
|
'';
|
|
};
|
|
|
|
services.openssh = {
|
|
enable = true;
|
|
settings = {
|
|
PasswordAuthentication = false;
|
|
KbdInteractiveAuthentication = false;
|
|
PermitRootLogin = "prohibit-password";
|
|
X11Forwarding = false;
|
|
MaxAuthTries = 3;
|
|
};
|
|
# Do NOT open firewall — SSH only over Tailscale
|
|
openFirewall = false;
|
|
};
|
|
|
|
# Fail2ban for HTTP abuse (not SSH — SSH isn't public)
|
|
services.fail2ban = {
|
|
enable = true;
|
|
maxretry = 5;
|
|
bantime = "1h";
|
|
bantime-increment = {
|
|
enable = true;
|
|
maxtime = "48h";
|
|
};
|
|
};
|
|
|
|
boot.kernel.sysctl = {
|
|
# Reverse path filtering
|
|
"net.ipv4.conf.all.rp_filter" = 1;
|
|
"net.ipv4.conf.default.rp_filter" = 1;
|
|
|
|
# Ignore broadcast pings
|
|
"net.ipv4.icmp_echo_ignore_broadcasts" = 1;
|
|
|
|
# Don't accept or send redirects
|
|
"net.ipv4.conf.all.accept_redirects" = 0;
|
|
"net.ipv6.conf.all.accept_redirects" = 0;
|
|
"net.ipv4.conf.all.send_redirects" = 0;
|
|
|
|
# Reject source-routed packets
|
|
"net.ipv4.conf.all.accept_source_route" = 0;
|
|
"net.ipv6.conf.all.accept_source_route" = 0;
|
|
|
|
# SYN flood protection (kernel-level SYN cookies)
|
|
"net.ipv4.tcp_syncookies" = 1;
|
|
"net.ipv4.tcp_max_syn_backlog" = 4096;
|
|
|
|
# Reduce TIME_WAIT accumulation from abusive connections
|
|
"net.ipv4.tcp_fin_timeout" = 15;
|
|
|
|
# Connection tracking table size (default 65536 is too small under DDoS)
|
|
"net.netfilter.nf_conntrack_max" = 262144;
|
|
};
|
|
}
|