feat: init
This commit is contained in:
commit
8cfede9f57
28 changed files with 2129 additions and 0 deletions
121
modules/security.nix
Normal file
121
modules/security.nix
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
{ 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;
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue