361 lines
11 KiB
Plaintext
361 lines
11 KiB
Plaintext
#!/usr/sbin/nft -f
|
|
|
|
# Template notes: most variables should have an obvious meaning, but:
|
|
# - client_ipv4: private IPv4 network for clients (not routed outside), connected
|
|
# - client_ipv4_public: public IPv4 network for clients, must be routed to this host
|
|
# and should be blackholed here.
|
|
# - client_ipv6: public IPv6 network, must be routed to this host, connected
|
|
|
|
# NOTE: mustn't flush full ruleset; need to keep the table `captive_mark` and its set around
|
|
# DON'T ENABLE THIS:
|
|
# flush ruleset
|
|
|
|
# fully whitelist certain sites, e.g. VPN gateways your users are
|
|
# allowed to connect to even without accepting the terms.
|
|
define full_ipv4 = {
|
|
<%- @whitelist_full_ipv4.each do |n| -%>
|
|
<%= n %>,
|
|
<%- end -%>
|
|
}
|
|
define full_ipv6 = {
|
|
<%- @whitelist_full_ipv6.each do |n| -%>
|
|
<%= n %>,
|
|
<%- end -%>
|
|
}
|
|
|
|
# whitelist http[s] traffic to certain sites, e.g. your
|
|
# homepage hosting the terms, OCSP responders, websites
|
|
# to setup other Wifi configurations (cat.eduroam.org)
|
|
define http_server_ipv4 = {
|
|
<%- @whitelist_http_ipv4.each do |n| -%>
|
|
<%= n %>,
|
|
<%- end -%>
|
|
}
|
|
define http_server_ipv6 = {
|
|
<%- @whitelist_http_ipv6.each do |n| -%>
|
|
<%= n %>,
|
|
<%- end -%>
|
|
}
|
|
|
|
option ntp-servers <%= ', '.join(@ntp_servers_ipv4) %>;
|
|
option dhcp6.sntp-servers <%= ', '.join(@ntp_servers_ipv6) %>;
|
|
|
|
# whitelist your DNS resolvers
|
|
define dns_server_ipv4 = {
|
|
<%- @dns_resolvers_ipv4.each do |n| -%>
|
|
<%= n %>,
|
|
<%- end -%>
|
|
}
|
|
define dns_server_ipv6 = {
|
|
<%- @dns_resolvers_ipv6.each do |n| -%>
|
|
<%= n %>,
|
|
<%- end -%>
|
|
}
|
|
|
|
# whitelist your (and possible other friendly) NTP servers
|
|
define ntp_server_ipv4 = {
|
|
<%- @ntp_servers_ipv4.each do |n| -%>
|
|
<%= n %>,
|
|
<%- end -%>
|
|
}
|
|
define ntp_server_ipv6 = {
|
|
<%- @ntp_servers_ipv6.each do |n| -%>
|
|
<%= n %>,
|
|
<%- end -%>
|
|
}
|
|
|
|
# ports to block traffic for completely
|
|
## block traffic from clients to certain server ports
|
|
define backlist_tcp = {
|
|
25, # SMTP
|
|
161, # SNMP
|
|
135, # epmap (netbios/portmapping)
|
|
137, # TCP netbios-ns
|
|
138, # TCP netbios-dgm
|
|
139, # netbios-ssn
|
|
445, # microsoft-ds (cifs, samba)
|
|
}
|
|
define backlist_udp = {
|
|
25, # SMTP
|
|
161, # SNMP
|
|
135, # UDP epmap (netbios/portmapping)
|
|
137, # netbios-ns
|
|
138, # netbios-dgm
|
|
139, # UDP netbios-ssn
|
|
445, # UDP microsoft-ds (cifs, samba)
|
|
5353, # mDNS
|
|
}
|
|
## block traffic from certain client ports
|
|
define backlist_udp_source = {
|
|
162, # SNMP trap
|
|
}
|
|
|
|
# once a client accepted the terms:
|
|
# * define "good" (whitelisted) traffic with the following lists
|
|
# * decide in "chain forward" below whether to block other traffic completely or
|
|
# e.g. shape it to low bandwidth (also see shape_non_whitelisted.sh)
|
|
define whitelist_tcp = {
|
|
22, # ssh
|
|
53, # dns
|
|
80, # http
|
|
443, # https
|
|
3128, # http proxy (squid)
|
|
8080, # http alt
|
|
110, # pop3
|
|
995, # pop3s
|
|
143, # imap
|
|
993, # imaps
|
|
587, # submission
|
|
465, # submissions
|
|
1194, # openvpn default
|
|
}
|
|
# https://help.webex.com/en-us/article/WBX264/How-Do-I-Allow-Webex-Meetings-Traffic-on-My-Network
|
|
define whitelist_udp = {
|
|
53, # dns
|
|
123, # ntp
|
|
443, # http/3
|
|
1194, # openvpn default
|
|
51820, # wireguard default
|
|
500, # IPsec isakmp
|
|
4500, # IPsec ipsec-nat-t
|
|
10000, # IPSec Cisco NAT-T
|
|
9000, # Primary Webex Client Media
|
|
5004, # Webex Client Media
|
|
}
|
|
# whitelist traffic to local sites
|
|
define whitelist_dest_ipv4 = {
|
|
<%- @local_site_prefixes_ipv4.each do |n| -%>
|
|
<%= n %>,
|
|
<%- end -%>
|
|
}
|
|
define whitelist_dest_ipv6 = {
|
|
<%- @local_site_prefixes_ipv6.each do |n| -%>
|
|
<%= n %>,
|
|
<%- end -%>
|
|
}
|
|
|
|
# IPv4 HTTP redirect + SNAT
|
|
table ip nat4 {}
|
|
flush table ip nat4
|
|
table ip nat4 {
|
|
chain prerouting {
|
|
type nat hook prerouting priority -100;
|
|
policy accept;
|
|
# needs to be marked from client interface (bit 0), captive (bit 1), and http dnat (bit 2) - otherwise accept
|
|
meta mark & 0x00000007 != 0x00000007 accept
|
|
tcp dport 80 redirect to 8080
|
|
}
|
|
|
|
chain postrouting {
|
|
type nat hook postrouting priority 100;
|
|
policy accept;
|
|
# needs to be marked from client interface (bit 0) - otherwise no NAT
|
|
meta mark & 0x00000001 != 0x00000001 accept
|
|
oifname <%= @uplink_interface %> snat to <%= @client_ipv4_public %>
|
|
}
|
|
}
|
|
|
|
# IPv6 HTTP redirect
|
|
table ip6 nat6 {}
|
|
flush table ip6 nat6
|
|
table ip6 nat6 {
|
|
chain prerouting {
|
|
type nat hook prerouting priority -100;
|
|
policy accept;
|
|
# needs to be marked from client interface (bit 0), captive (bit 1), and http dnat (bit 2) - otherwise accept
|
|
meta mark & 0x00000007 != 0x00000007 accept
|
|
tcp dport 80 redirect to 8080
|
|
}
|
|
}
|
|
|
|
# ipv4 + ipv6
|
|
table inet captive_filter {}
|
|
flush table inet captive_filter
|
|
table inet captive_filter {
|
|
chain antispoof_input {
|
|
# need to accept packet for input and forward!
|
|
meta mark & 0x00000001 == 0 accept comment "accept from non-client interface"
|
|
ip saddr { 0.0.0.0, <%= @client_ipv4 %> } return
|
|
ip6 saddr { fe80::/64, <%= @client_ipv6 %> } return
|
|
drop
|
|
}
|
|
|
|
# we need the "redirect" decision before DNAT in prerouting:-100
|
|
chain mark_clients {
|
|
type filter hook prerouting priority -110;
|
|
policy accept;
|
|
meta mark & 0x00000001 == 0 accept comment "accept from non-client interface"
|
|
jump antispoof_input
|
|
meta mark & 0x00000002 == 0 accept comment "accept packets from non-captive clients"
|
|
# now accept all traffic to destinations allowed in captive state, and mark "redirect" packets:
|
|
jump captive_allowed
|
|
}
|
|
|
|
chain input {
|
|
type filter hook input priority 0;
|
|
policy accept;
|
|
# TODO: limit services available to clients? iptconf might already be enough
|
|
}
|
|
|
|
chain forward_down {
|
|
# only filter uplink -> client here:
|
|
iifname != <%= @uplink_interface %> accept
|
|
oifname != <%= @client_interface %> accept
|
|
# established connections
|
|
ct state established,related accept
|
|
# allow incoming ipv6 ping (ipv4 ping can't work due to NAT)
|
|
icmpv6 type echo-request accept
|
|
drop
|
|
}
|
|
|
|
chain antispoof_forward {
|
|
meta mark & 0x00000001 == 0 goto forward_down comment "handle forwardings not from client interface"
|
|
ip saddr { <%= @client_ipv4 %> } return
|
|
ip6 saddr { <%= @client_ipv6 %> } return
|
|
drop
|
|
}
|
|
|
|
chain captive_allowed_icmp {
|
|
# allow all pings to servers we allow other kind of traffic
|
|
icmp type echo-request accept
|
|
icmpv6 type echo-request accept
|
|
}
|
|
|
|
chain captive_allowed_http {
|
|
# http + https (but not QUIC)
|
|
tcp dport { 80, 443 } accept
|
|
goto captive_allowed_icmp
|
|
}
|
|
|
|
chain captive_allowed_dns {
|
|
# DNS, DoT, DoH
|
|
udp dport { 53, 853 } accept
|
|
tcp dport { 53, 443, 853 } accept
|
|
goto captive_allowed_icmp
|
|
}
|
|
|
|
chain captive_allowed_ntp {
|
|
# only NTP
|
|
udp dport 123 accept
|
|
goto captive_allowed_icmp
|
|
}
|
|
|
|
chain captive_allowed {
|
|
# all protocols for fully whitelisted
|
|
ip daddr $full_ipv4 accept
|
|
ip6 daddr $full_ipv6 accept
|
|
|
|
ip daddr $http_server_ipv4 jump captive_allowed_http
|
|
ip6 daddr $http_server_ipv6 jump captive_allowed_http
|
|
|
|
ip daddr $dns_server_ipv4 jump captive_allowed_dns
|
|
ip6 daddr $dns_server_ipv6 jump captive_allowed_dns
|
|
|
|
ip daddr $ntp_server_ipv4 jump captive_allowed_ntp
|
|
ip6 daddr $ntp_server_ipv6 jump captive_allowed_ntp
|
|
|
|
# mark (new) http clients to redirect to local http server with bit 2
|
|
tcp dport 80 ct state new meta mark set meta mark | 0x00000004 accept comment "DNAT HTTP"
|
|
# mark packets to reject in forward with bit 3
|
|
meta mark set meta mark | 0x00000008 comment "reject in forwarding"
|
|
}
|
|
|
|
# for DNS+NTP
|
|
ct timeout udp-oneshot {
|
|
protocol udp;
|
|
policy = { unreplied: 10, replied: 0 }
|
|
}
|
|
|
|
chain forward_reject {
|
|
# could reject TCP connections with tcp reset, but ICMP unreachable should be good enough
|
|
# (and it's also semantically correct):
|
|
# ip protocol tcp reject with tcp reset
|
|
# ip6 nexthdr tcp reject with tcp reset
|
|
|
|
# but we need to close existing tcp sessions: (when client moves to captive state)
|
|
ct state != new ip protocol tcp reject with tcp reset
|
|
ct state != new ip6 nexthdr tcp reject with tcp reset
|
|
|
|
# default icmp reject
|
|
reject with icmpx type admin-prohibited
|
|
}
|
|
|
|
# block some ports always
|
|
chain blacklist {
|
|
tcp dport $backlist_tcp goto forward_reject
|
|
udp dport $backlist_udp goto forward_reject
|
|
udp sport $backlist_udp_source goto forward_reject
|
|
}
|
|
|
|
# ports we assume are "proper" - still only allowed in non-captive state
|
|
chain whitelist {
|
|
ip daddr $whitelist_dest_ipv4 accept
|
|
ip6 daddr $whitelist_dest_ipv6 accept
|
|
tcp dport $whitelist_tcp accept
|
|
udp dport $whitelist_udp accept
|
|
ip protocol esp accept
|
|
ip6 nexthdr esp accept
|
|
icmp type echo-request accept
|
|
icmpv6 type echo-request accept
|
|
# icmp related to existing connections, ...
|
|
ct state established,related accept
|
|
}
|
|
|
|
chain forward {
|
|
type filter hook forward priority 0;
|
|
policy drop;
|
|
jump antispoof_forward
|
|
# optimize conntrack timeouts for DNS and NTP
|
|
udp dport { 53, 123 } ct timeout set "udp-oneshot"
|
|
jump blacklist
|
|
# reject packets marked for rejection in mark_clients/captive_allowed (bit 3)
|
|
meta mark & 0x00000008 != 0 goto forward_reject
|
|
jump whitelist
|
|
# optional (policy):
|
|
goto forward_reject comment "drop not-whitelisted traffic completely"
|
|
# (conntrack) mark connection with bit 4 for shaping
|
|
ct mark set ct mark | 0x00000010 counter accept comment "accept shaped traffic"
|
|
}
|
|
|
|
chain forward_con_shaped_mark {
|
|
type filter hook forward priority 10;
|
|
policy accept;
|
|
# copy conntrack mark bit 4 to meta mark bit 4
|
|
ct mark & 0x00000010 == 0 accept comment "non shaped connection"
|
|
meta mark set meta mark | 0x00000010 counter accept comment "shaped connection"
|
|
}
|
|
}
|
|
|
|
|
|
# NOTE: mustn't flush this table to keep the set around
|
|
# DON'T ENABLE THIS:
|
|
# flush table inet captive_mark
|
|
|
|
# as table wasn't flushed, at least delete the single chain we're expecting
|
|
table inet captive_mark {
|
|
chain prerouting { }
|
|
}
|
|
delete chain inet captive_mark prerouting;
|
|
|
|
table inet captive_mark {
|
|
# set isn't recreated, i.e. keeps dynamically added members
|
|
set allowed {
|
|
type ether_addr
|
|
flags timeout
|
|
}
|
|
|
|
chain prerouting {
|
|
type filter hook prerouting priority -150;
|
|
policy accept;
|
|
iifname != <%= @client_interface %> accept
|
|
# mark packets from client interface with bit 0
|
|
meta mark set meta mark | 0x00000001
|
|
ether saddr @allowed accept
|
|
# mark "captive" clients with bit 1
|
|
meta mark set meta mark | 0x00000002
|
|
accept
|
|
}
|
|
|
|
# further existing elements in this table won't be cleared by loading this file!
|
|
}
|