emacs-elpa-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[elpa] externals/nftables-mode f00cf640fb 15/41: nftables - glob gotcha;


From: Stefan Monnier
Subject: [elpa] externals/nftables-mode f00cf640fb 15/41: nftables - glob gotcha; HOW to rename ifaces; gateway (-i/-o) policy; mail reputation protection
Date: Mon, 23 May 2022 09:27:23 -0400 (EDT)

branch: externals/nftables-mode
commit f00cf640fb7adeebc1b20dce80acf0bc5b85bd1e
Author: Trent W. Buck <trentbuck@gmail.com>
Commit: Trent W. Buck <trentbuck@gmail.com>

    nftables - glob gotcha; HOW to rename ifaces; gateway (-i/-o) policy; mail 
reputation protection
---
 nftables-router.nft | 126 +++++++++++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 114 insertions(+), 12 deletions(-)

diff --git a/nftables-router.nft b/nftables-router.nft
index dcf3c283c8..8a478043ef 100644
--- a/nftables-router.nft
+++ b/nftables-router.nft
@@ -48,6 +48,16 @@
 ####         meaning comes from the "type filter hook input priority
 ####         filter" line.
 ####
+#### GOTCHA: you can't use globs inside a set or map/vmap:
+####
+####             iifname "ppp*"               # works
+####             iifname {"ppp*", "en*"}      # fails
+####             iifname vmap {"ppp*": drop}  # fails
+####
+#### GOTCHA: Service names resolve via nss (/etc/services) only in nft 0.9.1+!
+####         For example, "imap" is in /etc/services, but not in nft 0.9.0.
+####         In nft 0.9.0, "nft describe tcp dport" will print the internal 
list.
+####
 #### GOTCHA: Using variable ("define x = y") is annoying:
 ####
 ####          * definition variable names aren't limited to C-style 
identifiers;
@@ -109,6 +119,37 @@
 ####          what happens?
 ####
 ####        * Rule of thumb: always use "iifname" (not "iif").
+####
+#### NOTE: instead of using "define dmz = enp12s0f3",
+####       give your interface a logical name directly.
+####       Then you use the name EVERYWHERE, e.g. "ip -s l show dmz".
+####
+####       OLD METHOD:
+####         $ cat /etc/udev/rules.d/70-persistent-net.rules
+####         SUBSYSTEM=="net", DRIVERS=="?*", 
ATTR{address}=="de:ad:be:ef:ba:be", NAME="internet"
+####         SUBSYSTEM=="net", DRIVERS=="?*", 
ATTR{address}=="de:af:be:ad:de:ed", NAME="dmz"
+####         SUBSYSTEM=="net", DRIVERS=="?*", 
ATTR{address}=="da:bb:ed:fa:ca:de", NAME="byod"
+####
+####       NEW METHOD (DID NOT WORK WHEN I TRIED IT):
+####         $ cat /etc/systemd/network/dmz.link
+####         [Match]
+####         MACAddress=dead.beef.babe
+####         [Link]
+####         Name=dmz
+####         # Is this line needed?
+####         #NamePolicy=
+####
+#### NOTE: a single rule can match "allow 53/tcp and 53/udp", but
+####       it is ugly, so don't do it:
+####
+####           meta l4proto {tcp, udp}   @th,16,16 53  accept  comment "accept 
DNS on UDP/TCP"
+####
+####       The @th,16,16 goes to the transport header, skips 16 bits, then 
reads 16 bits.
+####       If those bits are equal to 53, the rule matches.
+####
+####       This relies on (abuses) the fact that TCP dport and UDP dport have 
the same offset and length.
+####       You cannot use "domain", because nft doesn't know we're matching a 
service number.
+
 
 
 # NOTE: this will remove *ALL* tables --- including tables set up by
@@ -133,15 +174,8 @@ table inet my_filter {
 
         # YOUR RULES HERE.
         # NOTE: service names resolve via nss (/etc/hosts) only in nft 0.9.1+!
-        ##FOR "router" EXAMPLE### NOTE: a single rule CAN match "allow 53/tcp 
and 53/udp", but it's UGLY, so we don't.
-        ##FOR "router" EXAMPLE### NOTE: I assume you used systemd (networkd or 
udev) to rename "enp0s0f0" to "lan".
-        ##FOR "router" EXAMPLE### NOTE: "iif foo" must exist at ruleset load 
time.
-        ##FOR "router" EXAMPLE###       If your ruleset starts BEFORE udev 
and/or systemd-networkd are READY=1,
-        ##FOR "router" EXAMPLE###       consider using 'iifname lan' instead 
of "iif lan".
         tcp dport ssh  accept
         tcp dport { http, https }  accept
-        ##FOR "router" EXAMPLE##iif enp11s0 tcp dport domain accept
-        ##FOR "router" EXAMPLE##iif enp11s0 udp dport { domain, ntp, bootps } 
accept
 
         jump my_epilogue
     }
@@ -153,9 +187,67 @@ table inet my_filter {
         jump my_prologue  comment "deal with boring 
conntrack/loopback/ICMP/ICMPv6"
 
         # YOUR RULES HERE.
-        # NOTE: service names resolve via nss (/etc/hosts) only in nft 0.9.1+!
-        # NOTE: a single rule CAN match "allow 53/tcp and 53/udp", but it's 
UGLY, so we don't.
-        # NOTE: I assume you used systemd (networkd or udev) to rename 
"enp0s0f0" to "lan".
+
+        # If a pwned devices spams the internet,
+        # your entire network will be blacklisted!
+        # To avoid this, blacklist outbound SMTP (25/tcp) from non-MTA hosts.
+        # MSAs (e.g. Outlook) are not affected, because they use submission 
(587/tcp).
+        #
+        # NOTE: this must appear BEFORE "allow all to internet", obviously.
+        # NOTE: the LANs can still spam the DMZ.
+        # NOTE: the DMZ can still spam the internet, because
+        #       occasionally someone adds an MTA to the DMZ without telling me.
+        iifname != dmz  oifname internet  reject  comment "MSAs MUST use 
submission (not smtp)"
+
+        # Example of a router between four networks:
+        #   internet (the internet),
+        #   dmz      (internet-facing servers),
+        #   lan      (internal servers & managed laptops).
+        #   byod     (BYOD phones and laptops)
+        #
+        # Due to prologue, we're only handling NEW FLOWS.
+        # The return traffic is implicitly allowed.
+        # We want to say something like:
+        #
+        #   * full access to internet (except port 25?)
+        #   * limited access to dmz ("typical" server ports)
+        #   * everything else implicitly blocked
+        #
+        # Ignoring hairpin traffic (iifname = oifname),
+        # we have 4 P 2 = 12 permutations.
+        #
+        # We can write every combination out longhand:
+        #
+        #  * BONUS: very clear
+        #  * BONUS: can't get rules in "wrong" order (efficiency)
+        #  * BONUS: can't accidentally have overlapping rules (correctness)
+        #  * BONUS: if a new iface appears, it will default deny (correctness)
+        #  * MALUS: too verbose with many networks (5P2 = 20; 6P2 = 30, 7P2 = 
42)
+        #  * MALUS: can't use globs for shorthand (e.g. "*" . dmz).
+        #
+        # NOTE: the "continue" lines could be omitted, but are harmless.
+        iifname . oifname vmap {
+            lan      . internet : accept,      # all can attack the internet
+            byod     . internet : accept,      # all can attack the internet
+            dmz      . internet : accept,      # all can attack the internet
+            lan      . dmz      : jump my_dmz, # all can attack DMZ (only 
specific ports)
+            byod     . dmz      : jump my_dmz, # all can attack DMZ (only 
specific ports)
+            internet . dmz      : jump my_dmz, # all can attack DMZ (only 
specific ports)
+            lan      . byod     : accept,      # only LAN can attack phones
+            dmz      . byod     : continue,    # only LAN can attack phones
+            internet . byod     : continue,    # only LAN can attack phones
+            byod     . lan      : continue,    # nobody can attack LAN
+            dmz      . lan      : continue,    # nobody can attack LAN
+            internet . lan      : continue,    # nobody can attack LAN
+        }
+        # OR, we can try to be clever, and write individual rules.
+        # This is shorter, but harder to reason about!
+        #    oifname internet  accept
+        #    oifname dmz       jump my_dmz
+        #    iifname lan       accept
+
+        ### NOTE: a single rule CAN match "allow 53/tcp and 53/udp", but it's 
UGLY, so we don't.
+        ### NOTE: I assume you used systemd (networkd or udev) to rename 
"enp0s0f0" to "lan".
         tcp dport ssh  accept
         tcp dport { http, https }  accept
         iifname lan  tcp dport domain  accept
@@ -171,6 +263,17 @@ table inet my_filter {
     #    policy accept
     #}
 
+    # In theory DMZ hosts must fend for themselves;
+    # in practice their competence is suspect.
+    # Thus, limit DMZ access to "typical" ports (plus some per-host 
exceptions).
+    # Within those typical ports, DMZ hosts still fend for themselves.
+    chain my_dmz {
+        tcp dport {domain,ssh,http,https,smtp,submission,imaps}  accept
+        udp dport {domain} accept
+        # Allow additional special ports, but only to the server that serves 
them.
+        define russm.example.com = 127.254.254.254
+        ip daddr $russm.example.com  udp dport 60000-61000  accept  comment 
"mosh (FIXME: write nf_conntrack_mosh.ko!)"
+    }
 
     chain my_prologue {
         # Typically 99%+ of packets are part of an already-established flow.
@@ -203,7 +306,6 @@ table inet my_filter {
         # FIXME: are "ip protocol icmp" and "ip6 nexthdr icmpv6" needed?
         ip protocol icmp  icmp type vmap @ICMP_policy
         ip6 nexthdr icmpv6  icmpv6 type vmap @ICMPv6_RFC4890_policy
-
     }
 
     chain my_epilogue {
@@ -303,7 +405,7 @@ table inet my_filter {
 # NOTE: I assume "the internet" iface is ADSL PPPoE/PPPoA (iiftype/oiftype 
ppp), and
 #       that's the ONLY ppp iface you have (cf. bullshit ppptp VPN for iPhone 
users).
 #       If you have decent internet, you will probably want to give the iface 
a logical name,
-#       then match by that name (iifname/oifname "upstream").
+#       then match by that name (iifname/oifname "internet").
 #
 table ip my_nat {
     chain my_postrouting {



reply via email to

[Prev in Thread] Current Thread [Next in Thread]