#!/usr/sbin/nft -f flush ruleset define eth_iface = enp1s0 define wg_iface = wg0 define wg_port = 51820 table inet filter { chain input_ipv4 { # accepting ping (icmp-echo-request) for diagnostic purposes. # However, it also lets probes discover this host is alive. # This sample accepts them within a certain rate limit: icmp type echo-request limit rate 5/second accept } chain input_ipv6 { # accept neighbour discovery otherwise connectivity breaks icmpv6 type { nd-neighbor-solicit, nd-router-advert, nd-neighbor-advert } accept # accepting ping (icmpv6-echo-request) for diagnostic purposes. # However, it also lets probes discover this host is alive. # This sample accepts them within a certain rate limit: icmpv6 type echo-request limit rate 5/second accept } chain input_world { udp dport { $wg_port } accept } chain input_vpn { # TODO: Should we limit source address space? # # ip saddr 10.8.0.0/32 # Allow VPN to use DNS. tcp dport { 53 } accept udp dport { 53 } accept } chain input { # By default, drop all traffic unless it meets a filter # criteria specified by the rules that follow below. type filter hook input priority 0; policy drop; # Allow traffic from established and related packets, drop invalid ct state vmap { established : accept, related : accept, invalid : drop } # Jump to chain according to layer 3 protocol using a verdict map meta protocol vmap { ip : jump input_ipv4, ip6 : jump input_ipv6 } # Allow traffic for/from both the world and VPN. tcp dport { ssh, http, https, } accept # allow loopback traffic, anything else jump to chain for further evaluation iifname vmap { lo : accept, $eth_iface : jump input_world, $wg_iface : jump input_vpn } # Uncomment to enable logging of denied input traffic # log prefix "[nftables] input Denied: " counter drop # Reject with polite "port unreachable" icmp response reject } chain forward { # Drop everything (assumes this device is not a router) # type filter hook forward priority filter; type filter hook forward priority 0; policy drop; # Forward all icmp/icmpv6 packets meta l4proto { icmp, ipv6-icmp } accept # Allow traffic from established and related packets, drop invalid ct state vmap { established : accept, related : accept, invalid : drop } # Forward traffic within the VPN and between it and the outside world. iifname $wg_iface oifname $wg_iface counter accept; iifname $wg_iface oifname $eth_iface counter accept; iifname $eth_iface oifname $eth_iface counter accept; # Reject with polite "host unreachable" icmp response reject with icmpx type host-unreachable } chain prerouting { type nat hook prerouting priority 0; } chain postrouting { type nat hook postrouting priority 100; policy accept; # Masquerade all packets from WireGuard VPN to the outside world. iifname $wg_iface oifname $eth_iface masquerade } # no need to define output chain, default policy is accept if undefined. }