#!/usr/bin/env nft -f # The home-server project produces a multi-purpose setup using Ansible. # Copyright © 2018–2023 Y. Gablin, under the GPL-3.0-or-later license. # Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. flush ruleset table arp RateLimiter { chain ArpIn { type filter hook input priority 0 policy accept {% if is_vpn_used is defined %} meta iif tun0 limit rate 2/second burst 10 packets accept {% else %} meta iif host0 limit rate 2/second burst 10 packets accept {% endif %} } chain ArpOut { type filter hook output priority 0 policy accept } } {% for V in ['4', '6'] %} {% set v = V | replace('4', '') %} {% macro trust(list) %} {% for net in list.split(' ') %} {% if not net is match('127(?:\.\d{1,3}){3}(?:/\d+)?|::1|^$') %} {% if (net is match('\d{1,3}(?:\.\d{1,3}){3}(?:/\d+)?') and V == '4') or (net is search(':') and V == '6') %} {{caller(net)}} {% endif %} {% endif %} {% endfor %} {% endmacro %} table ip{{v}} Inet{{V}} { set https_ban { type ipv{{V}}_addr flags timeout } set mail_ban { type ipv{{V}}_addr flags timeout } set sshd_ban { type ipv{{V}}_addr flags timeout } {% set seq = fw_portknock_seq.split(' ') %} {% for port in seq %} set Knocked_{{loop.index}} { type ipv{{V}}_addr flags timeout {% if loop.last %} timeout {{fw_knock_timeout_min}}m {% else %} timeout 10s {% endif %} gc-interval 4s } {% endfor %} chain doBan { type filter hook prerouting priority -200; policy accept; ip{{v}} saddr @https_ban tcp dport {80, 443} drop ip{{v}} saddr @mail_ban tcp dport {25, 465, 587, 993} drop ip{{v}} saddr @sshd_ban tcp dport {22, 2222, 22000} drop } chain NAT_in { type nat hook prerouting priority -100 {% call(net) trust(SafeZone_IP) %} # Git SSH tcp dport 2222 log prefix "DNAT/git: " dnat to {{net}} {% endcall %} # Trusted hosts tcp dport 443 ip{{v}} saddr @Knocked_{{seq | length}} log prefix "DNAT/HTTPS after Port-Knock: " redirect to 444 {% call(net) trust(net_trusted_ranges) %} tcp dport 443 ip{{v}} saddr {{net}} redirect to 444 {% endcall %} tcp dport 22 ip{{v}} saddr @Knocked_{{seq | length}} log prefix "DNAT/SSH after Port-Knock: " redirect to 23 {% call(net) trust(net_trusted_ranges) %} tcp dport 22 ip{{v}} saddr {{net}} redirect to 23 {% endcall %} } chain NAT_out { type nat hook postrouting priority 100 ct status dnat masquerade } {% for port in seq %} chain Knock_{{loop.index}} { {% if not loop.first %} set update ip{{v}} saddr timeout 0s @Knocked_{{loop.index-1}} {% endif %} set add ip{{v}} saddr @Knocked_{{loop.index}}{% if loop.last %} log prefix "Port-Knock accepted: "{% endif %} } {% if not loop.last %} chain Unknock_{{loop.index}} { set update ip{{v}} saddr timeout 0s @Knocked_{{loop.index}} } {% endif %} {% endfor %} chain RefreshKnock { set update ip{{v}} saddr timeout {{fw_knock_timeout_min}}m @Knocked_{{seq | length}} } chain PortKnock { {% for port in seq | reverse %} {% if loop.first %} ct state new ip{{v}} saddr @Knocked_{{loop.revindex}} goto RefreshKnock {% else %} tcp dport {{seq[loop.revindex]}} ct state new ip{{v}} saddr @Knocked_{{loop.revindex}} goto Knock_{{loop.revindex+1}} tcp dport {{port}} ct state new ip{{v}} saddr @Knocked_{{loop.revindex}} return ip{{v}} saddr @Knocked_{{loop.revindex}} ct state new goto Unknock_{{loop.revindex}} {% endif %} {% if loop.last %} tcp dport {{port}} ct state new goto Knock_{{loop.revindex}} {% endif %} {% endfor %} } chain FilterIn { type filter hook input priority 0 policy drop # early drop of invalid connections ct state invalid drop # allow icmp {% if V == '4' %} icmp type { echo-reply, destination-unreachable, router-advertisement, time-exceeded, parameter-problem } accept icmp type { source-quench, redirect, info-request, info-reply, address-mask-request, address-mask-reply } drop meta l4proto icmp limit rate 2/second burst 4 packets accept {% else %} icmpv6 type { destination-unreachable, packet-too-big, time-exceeded, parameter-problem, echo-reply, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert } accept meta l4proto ipv6-icmp limit rate 2/second burst 4 packets accept {% endif %} # allow established/related connections ct state {established, related} accept # allow from loopback {% if V == '4' %} meta iif lo ip saddr != 127.0.0.0/8 drop {% else %} meta iif lo ip6 saddr != ::1/128 drop {% endif %} meta iif lo accept # allow iodine meta iifname dns0 accept # port-knocking jump PortKnock # trusted ssh and https ct status dnat accept # smtp tcp dport 25 accept # iodine tcp dport 53 accept udp dport 53 accept # http tcp dport 80 accept # https tcp dport 443 accept # smtps tcp dport 465 accept # submission (smtp) tcp dport 587 accept # ipp {% call(net) trust(net_trusted_ranges) %} tcp dport 631 ip{{v}} saddr {{net}} accept udp dport 631 ip{{v}} saddr {{net}} accept {% endcall %} # imaps tcp dport 993 accept # xmpp client tcp dport 5222 accept # xmpp server tcp dport 5269 accept # xmpp components tcp dport 5347 accept # zeroconf {% call(net) trust(net_trusted_ranges) %} udp dport 5353 ip{{v}} saddr {{net}} accept {% endcall %} # remote-help ssh tcp dport 22000 accept {% call(net) trust(net_trusted_ranges) %} tcp dport 22001-22009 ip{{v}} saddr {{net}} accept {% endcall %} # transmission tcp dport {{transmission_bt_port}} accept udp dport {{transmission_bt_port}} accept } chain FilterOut { type filter hook output priority 0 policy drop {% if V == '4' %} icmp type { echo-reply, destination-unreachable, router-advertisement, time-exceeded, parameter-problem } accept icmp type { source-quench, redirect, info-request, info-reply, address-mask-request, address-mask-reply } drop meta l4proto icmp limit rate 2/second burst 4 packets accept {% else %} icmpv6 type { destination-unreachable, packet-too-big, time-exceeded, parameter-problem, echo-reply, nd-router-solicit, nd-neighbor-solicit, nd-neighbor-advert } accept meta l4proto ipv6-icmp limit rate 2/second burst 4 packets accept {% endif %} ct state {established, related} accept meta oif lo accept {% call(net) trust(SafeZone_IP + ' ' + dns_hosts + ' ' + allowed_domains_ip + ' ' + ntp_hosts) %} ip{{v}} daddr {{net}} accept {% endcall %} meta skuid {{aur_user}} accept meta skuid exim tcp dport 25 accept meta skuid prosody accept meta skgid spamd accept meta skuid transmission tcp dport 443 accept meta skuid transmission udp dport 443 accept meta skuid transmission tcp dport > 1024 accept meta skuid transmission udp dport > 1024 accept } } {% endfor %}