nftables – nftables Chain Priority Issues

debiannftables

nft is causing me endless headaches, no matter how I tweak the policy, I still cannot get it to function.

The concept I have in mind :

  • One "base" chain where common rules live (e.g. allow ssh etc.)
  • One or more application specific where daemon specific rules live (e.g. http server chain)

I have tried many different permutations of rules, but I can never get both "base" + daemon traffic flowing, I always end up blocking one or the other ! ;-(

Here is my current (simplified) config (as presently constituted it allows ssh but fails to allow http)

/etc/nftables.conf:

#!/usr/sbin/nft -f                                                                                                                                                                                                                                              
flush ruleset                                                                                                                       
table inet filter {   
     counter input_ssh {}     
        set my_admin_ipv4 {                                                                                                        
                type ipv4_addr                                                                                                      
                flags interval                                                                                                      
                counter                                                                                                             
                elements = {                                                                                                        
                        10.0.0.0/8,                                                                                                 
                        172.16.0.0/12,                                                                                              
                        192.168.0.0/16                                                                                                                                                                                      
                }                                                                                                                   
        }         
       chain input {
                type filter hook input priority filter;
                iifname lo accept comment "Allow loopback traffic";
                ct state established,related accept comment "Allow established/related connections";
                ct state invalid drop comment "Deny invalid connections";

                # SSH
                tcp dport ssh ip saddr @my_admin_ipv4 counter name input_ssh accept comment "Allow IPv4 SSH from Admin";
    policy drop;
        }
        chain forward {
                type filter hook forward priority 0;
                policy drop;
        }
        chain output {
                type filter hook output priority 0;
        }
 include "/etc/nft/*.conf"
}

/etc/nft/http.conf:

counter input_http {} 
   chain http {
    type filter hook input priority filter - 1;
      # HTTP #
      tcp dport {80,443} counter name input_nginx accept comment "Allow HTTP";
    policy accept; 
    }

Best Answer

You can choose to mark packets to be accepted as a safe conduct in following chains of the same hook.

  • each accept rule should mark the packet

    Rules doing an explicit accept should mark the packet right before accepting it. Any occurence of:

    ... accept
    

    should be replaced with:

    ... meta mark set 0xf00 accept
    

    The value isn't important as long it's not 0 if there's no other role to the mark.

  • each chain should accept a marked packet since that's the safe conduct

    by using this rule early in the chain:

    meta mark != 0 accept
    

That's the general idea. Adaptations and exceptions can still be made if it makes more sense.

Here is the revisited ruleset following this approach. The mark is accepted after the ct ... invalid drop rule which is an important rule that should not be bypassed (and could actually have been done in an earlier chain once and for all since it's a drop rule). If below, input is the last chain in the filter/input hook it's not really needed to mark accepted packets, but it doesn't hurt to do so.

/etc/nftables.conf::

flush ruleset 
table inet filter {   
    counter input_ssh {}     
    set my_admin_ipv4 {                                                                                                        
        type ipv4_addr                                                                                                      
        flags interval                                                                                                      
        counter                                                                                                             
        elements = {                                                                                                        
            10.0.0.0/8,                                                                                                 
            172.16.0.0/12,                                                                                              
            192.168.0.0/16                                                                                                                                                                                      
        }                                                                                                                   
    }         
    chain input {
        type filter hook input priority filter; policy drop;
        iifname lo meta mark set 0x1 accept comment "Allow loopback traffic";
        ct state established,related meta mark set 0x1 accept comment "Allow established/related connections";
        ct state invalid drop comment "Deny invalid connections";
        meta mark != 0 accept

        # SSH
        tcp dport ssh ip saddr @my_admin_ipv4 counter name input_ssh meta mark set 0x1 accept comment "Allow IPv4 SSH from Admin";
    }
    chain forward {
        type filter hook forward priority 0; policy drop;                      
    }
    chain output {
        type filter hook output priority 0; policy accept;
    }
    include "/etc/nft/*.conf"
}

/etc/nft/http.conf (replaced counter_nginx with counter_http). The meta mark != 0 accept rule is probably not needed here since there might not be any other chain before anyway, but it doesn't hurt to have it too.

    counter input_http {} 
    chain http {
        type filter hook input priority filter - 1; policy accept;
         meta mark != 0 accept
         # HTTP #
         tcp dport {80,443} counter name input_http meta mark set 0x2 accept comment "Allow HTTP";
    }

This method uses marks so will be harder to integrate with other firewall rules that were already using marks for other purposes. It's still possible by reserving a few bits of the mark with bitwise operations.