#!/usr/sbin/nft -f
{% set ifaces_fabric = interfaces | selectattr('name', 'match', '^lan') | map(attribute='name') %}

flush ruleset

table inet filter {
    include "/etc/nftables.d/interfaces.nft"
    include "/etc/nftables.d/networks.nft"
    include "/etc/nftables.d/sets.nft*"

    set link {
        type iface_index
        elements = { {{ ifaces_fabric | product(['2', '4']) | map('join', '.') | join(', ') }} }
    }

    # convenience port set definitions
    set ad-ports { # https://learn.microsoft.com/en-us/troubleshoot/windows-server/active-directory/config-firewall-for-ad-domains-and-trusts
        type inet_proto . inet_service
        flags interval
        elements = {
            tcp . 53,
            tcp . 88,
            tcp . 135,
            tcp . 139,
            tcp . 389,
            tcp . 445,
            tcp . 464,
            tcp . 636,
            tcp . 3268-3269,
            #tcp . 3389, # RDP
            tcp . 5000-5100,
            tcp . 5985,
            tcp . 5986,
            tcp . 9389,
            tcp . 22222-22224,
            tcp . 49152-65535,
            udp . 53,
            udp . 88,
            udp . 135,
            udp . 137, # netbios, maybe can do without
            udp . 138, # netbios, maybe can do without
            udp . 389,
            udp . 464,
            udp . 3269
        }
    }

    set ldap-ports {
        type inet_proto . inet_service
        flags interval
        elements = {
            tcp . 88,
            tcp . 389,
            tcp . 636,
            tcp . 3268,
            tcp . 3269,
            udp . 88,
            udp . 389
        }
    }

    chain input {
        type filter hook input priority 0; policy drop

        ct state vmap { established : accept, related : accept, invalid : drop } \
        comment "Accept established streams and drop invalid connections"

        iif lo accept \
        comment "Accept any localhost traffic"

        iif mgmt tcp dport ssh accept \
        comment "Accept SSH from management VRF"

        # allow SSH connections from firewall master’s IPs
{% for iface in hostvars[master].interfaces %}
{% for address in iface.ip_addresses %}
        tcp dport ssh {{ 'ip' if address.family.value == 4 else 'ip6' }} saddr {{ address.address | ipaddr('address')  }} accept
{% for nat_address in address.nat_outside %}
        tcp dport ssh ip saddr {{ nat_address.address | ipaddr('address') }} accept
{% endfor %}
{% endfor %}
{% endfor %}

        iif @link tcp dport bgp ip6 saddr fe80::/10 accept \
        comment "Accept link-local BGP on fabric links"

        iif @link udp dport 3784 ip6 saddr fe80::/10 accept \
        comment "Accept link-local BFD on fabric links"

        udp dport 51820 accept \
        comment "Accept WireGuard from anywhere"

        iif {{ iface_sync }} ip6 saddr fe80::/10 udp dport 3780 accept \
        comment "Accept connection tracking sync data"

        tcp dport auth reject with icmpx type port-unreachable \
        comment "Reject AUTH to make it fail fast"

        # ICMPv4
        ip protocol icmp icmp type {
            echo-request, echo-reply, destination-unreachable,
            parameter-problem, time-exceeded,
        } accept \
        comment "Accept ICMP"

        # ICMPv6
        ip6 nexthdr icmpv6 icmpv6 type {
            echo-request, echo-reply, destination-unreachable,
            packet-too-big, parameter-problem, time-exceeded,
        } accept \
        comment "Accept basic IPv6 functionality"

        ip6 nexthdr icmpv6 icmpv6 type {
            nd-neighbor-solicit, nd-neighbor-advert,
            nd-router-solicit, nd-router-advert,
        } ip6 hoplimit 255 accept \
        comment "Allow IPv6 neighbor discovery"
    }

    chain forward {
        type filter hook forward priority filter; policy drop

        ct state { established, related } accept \
        comment "Forward all established and related traffic"

        ct status dnat accept \
        comment "Forward DNAT traffic for servers and suchlike"

        ip protocol icmp icmp type {
            echo-request, echo-reply, destination-unreachable,
            parameter-problem, time-exceeded,
        } accept \
        comment "Accept ICMPv4"

        ip6 nexthdr icmpv6 icmpv6 type {
            echo-request, echo-reply, destination-unreachable,
            packet-too-big, parameter-problem, time-exceeded,
        } accept \
        comment "Accept ICMPv6"

        include "/etc/nftables.d/forward.nft*"
    }

    chain output {
        type filter hook output priority 0; policy accept
    }
}

table ip nat {
    include "/etc/nftables.d/interfaces.nft"
    include "/etc/nftables.d/networks.nft"
    include "/etc/nftables.d/sets.nft*"
    include "/etc/nftables.d/netmap.nft*"

    # Ensure these maps exist even if empty.
    map netmap-in { type ipv4_addr : interval ipv4_addr; flags interval; }
    map netmap-out { type ipv4_addr : interval ipv4_addr; flags interval; }

    chain postrouting {
        type nat hook postrouting priority srcnat

        iif @inside oif @outside snat ip prefix to ip saddr map @netmap-out \
        comment "Static source NAT for 1:1 mapped addresses"

        include "/etc/nftables.d/nat.nft*"
    }

    chain prerouting {
        type nat hook prerouting priority dstnat

        dnat ip prefix to ip daddr map @netmap-in \
        comment "Static destination NAT for 1:1 mapped addresses"
    }
}