Regole Nftables per l'irrobustimento dell'installazione di Discourse

Ciao a tutti,

questo è il mio primo post qui. Il mio background: amministro server Linux da oltre un decennio. Circa una settimana fa ho eseguito un’installazione di Discourse su un server (Debian Bullseye). Mi piace finora, ottimo!

Ora volevo implementare le solite misure di hardening sul sistema host (ad es. webserver). Questo include, tra le altre cose, regole nftables. Generalmente uso queste:

#!/usr/sbin/nft -f

####################
# Purge/Flush      #
####################
flush ruleset

####################
# Incoming Traffic #
####################
table inet filter {
   chain input {
      type filter hook input priority 0; policy drop;

      # Allow loopback interface
      iifname lo accept

      # Rate limit ICMPv4|ICMPv6 traffic
      ip protocol icmp icmp type { echo-request, echo-reply, destination-unreachable, time-exceeded, parameter-problem, router-solicitation, router-advertisement } limit rate over 5/second drop
      ip6 nexthdr icmpv6 icmpv6 type { destination-unreachable, packet-too-big, time-exceeded, echo-request, parameter-problem, echo-reply, nd-router-solicit, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert, ind-neighbor-solicit, ind-neighbor-advert } limit rate over 5/second drop

      # Allow packets to established/related connections
      ct state established,related accept

      # Drop invalid connections
      ct state invalid drop

      # Allow ICMPv4: Ping requests | Error messages | Router selection messages
      ip protocol icmp icmp type { echo-request, echo-reply, destination-unreachable, time-exceeded, parameter-problem, router-solicitation, router-advertisement } accept

      # Allow ICMPv6 traffic (https://tools.ietf.org/html/rfc4890#page-18)
      ip6 nexthdr icmpv6 icmpv6 type { destination-unreachable, packet-too-big, time-exceeded, echo-request, parameter-problem, echo-reply, nd-router-solicit, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert, ind-neighbor-solicit, ind-neighbor-advert } accept

      # Allow SSH access on port 2222 [rate limit]
      tcp dport 2222 ct state new limit rate 3/minute accept

      # Allow HTTP / HTTPS traffic
      tcp dport { http, https } accept

      # Reject other packets
      ip protocol tcp reject with tcp reset
      ip6 nexthdr tcp reject with tcp reset
   }
####################
# Forward Traffic  #
####################
   chain forward {
      type filter hook forward priority 0; policy drop;
   }
####################
# Outgoing Traffic #
####################
   chain output {
      type filter hook output priority 0; policy accept;

      # Allow loopback interface
      oifname lo accept
   }
}

Tuttavia, dopo l’attivazione Discourse ha smesso di funzionare. Sospetto che i pacchetti non vengano passati all’installazione Docker di Discourse. Questa regola in particolare è probabilmente problematica:

####################
# Forward Traffic  #
####################
   chain forward {
      type filter hook forward priority 0; policy drop;
   }

Ma prima di iniziare a trafficare ora, volevo chiedere se qualcuno ha già affrontato questo problema e ha regole firewall funzionanti per il sistema di base. Queste regole hanno senso per un’installazione Docker di Discourse? Finora ho avuto poco a che fare con Docker.

Altre informazioni. Queste regole firewall vengono aggiunte automaticamente da Docker (docker-ce):

table ip nat {
	chain DOCKER {
		iifname "docker0" counter packets 0 bytes 0 return
		iifname != "docker0" meta l4proto tcp tcp dport 443 counter packets 155 bytes 7070 dnat to 172.17.0.2:443
		iifname != "docker0" meta l4proto tcp tcp dport 80 counter packets 128 bytes 6263 dnat to 172.17.0.2:80
	}

	chain POSTROUTING {
		type nat hook postrouting priority srcnat; policy accept;
		oifname != "docker0" ip saddr 172.17.0.0/16 counter packets 34 bytes 2188 masquerade
		meta l4proto tcp ip saddr 172.17.0.2 ip daddr 172.17.0.2 tcp dport 443 counter packets 0 bytes 0 masquerade
		meta l4proto tcp ip saddr 172.17.0.2 ip daddr 172.17.0.2 tcp dport 80 counter packets 0 bytes 0 masquerade
	}

	chain PREROUTING {
		type nat hook prerouting priority dstnat; policy accept;
		fib daddr type local counter packets 11439 bytes 550595 jump DOCKER
	}

	chain OUTPUT {
		type nat hook output priority -100; policy accept;
		ip daddr != 127.0.0.0/8 fib daddr type local counter packets 0 bytes 0 jump DOCKER
	}
}
table ip filter {
	chain DOCKER {
		iifname != "docker0" oifname "docker0" meta l4proto tcp ip daddr 172.17.0.2 tcp dport 443 counter packets 155 bytes 7070 accept
		iifname != "docker0" oifname "docker0" meta l4proto tcp ip daddr 172.17.0.2 tcp dport 80 counter packets 128 bytes 6263 accept
	}

	chain DOCKER-ISOLATION-STAGE-1 {
		iifname "docker0" oifname != "docker0" counter packets 588 bytes 44199 jump DOCKER-ISOLATION-STAGE-2
		counter packets 1187 bytes 428425 return
	}

	chain DOCKER-ISOLATION-STAGE-2 {
		oifname "docker0" counter packets 0 bytes 0 drop
		counter packets 588 bytes 44199 return
	}

	chain FORWARD {
		type filter hook forward priority filter; policy drop;
		counter packets 1187 bytes 428425 jump DOCKER-USER
		counter packets 1187 bytes 428425 jump DOCKER-ISOLATION-STAGE-1
		oifname "docker0" ct state related,established counter packets 316 bytes 370893 accept
		oifname "docker0" counter packets 283 bytes 13333 jump DOCKER
		iifname "docker0" oifname != "docker0" counter packets 588 bytes 44199 accept
		iifname "docker0" oifname "docker0" counter packets 0 bytes 0 accept
	}

	chain DOCKER-USER {
		counter packets 1187 bytes 428425 return
	}

	chain INPUT {
		type filter hook input priority filter; policy accept;
	}
}

Saluti

Ehi @OrkoGrayskull hai trovato

nelle tue ricerche? C’è un po’ più di contesto lì su come docker interagisce con i firewall.

2 Mi Piace

Ho letto il thread e ho testato qualcosa da solo. Docker porta con sé le proprie regole iptables e le applica dopo l’installazione o quando il servizio docker viene (ri)avviato/caricato:

service docker restart

Senza questi set di regole l’installazione di Discourse non funzionerà. Significa che durante l’installazione, la ricostruzione o il riavvio del container, queste regole devono essere presenti. Altrimenti appare il messaggio di errore:

Error response from daemon: driver failed programming external connectivity on endpoint app (e4d4d3cc812a11862ee6aaa6ab453e61b95da1e6d90f9a76a71959148d228476):  (iptables failed: iptables --wait -t nat -A DOCKER -p tcp -d 0/0 --dport 443 -j DNAT --to-destination 172.17.0.2:443 ! -i docker0: iptables: No chain/target/match by that name.
 (exit status 1))

Tuttavia, una volta completata l’installazione di Discourse o riavviato il container dopo un aggiornamento, le regole Docker apparentemente non sono più necessarie. A quel punto è possibile caricare le seguenti regole nftables:

#!/usr/sbin/nft -f

#################### 
# Purge/Flush      # 
#################### 
flush ruleset

#################### 
# Incoming Traffic # 
#################### 
table inet filter { 	    
   chain input { 		       
      type filter hook input priority 0; policy drop;
      
      # Allow loopback interface 		       
      iifname lo accept

      # Rate limit ICMPv4|ICMPv6 traffic       
      ip protocol icmp icmp type { echo-request, echo-reply, destination-unreachable, time-exceeded, parameter-problem, router-solicitation, router-advertisement } limit rate over 5/second drop       
      ip6 nexthdr icmpv6 icmpv6 type { destination-unreachable, packet-too-big, time-exceeded, echo-request, parameter-problem, echo-reply, nd-router-solicit, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert, ind-neighbor-solicit, ind-neighbor-advert } limit rate over 5/second drop

      # Allow packets to established/related connections        
      ct state established,related accept         
  
      # Drop invalid connections        
      ct state invalid drop
      
      # Allow ICMPv4: Ping requests | Error messages | Router selection messages       
      ip protocol icmp icmp type { echo-request, echo-reply, destination-unreachable, time-exceeded, parameter-problem, router-solicitation, router-advertisement } accept              
   
      # Allow ICMPv6 traffic (https://tools.ietf.org/html/rfc4890#page-18)       
      ip6 nexthdr icmpv6 icmpv6 type { destination-unreachable, packet-too-big, time-exceeded, echo-request, parameter-problem, echo-reply, nd-router-solicit, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert, ind-neighbor-solicit, ind-neighbor-advert } accept

      # Allow SSH access on port 7777 [rate limit]       
      tcp dport 7777 ct state new limit rate 3/minute accept

      # Allow HTTP / HTTPS traffic
      tcp dport { http, https } accept
      
      # Reject other packets       
      ip protocol tcp reject with tcp reset
      ip6 nexthdr tcp reject with tcp reset    
   }
#################### 
# Forward Traffic  # 
####################    
   chain forward { 		       
      type filter hook forward priority 0; policy drop; 	    
   } 
#################### 
# Outgoing Traffic # 
####################    
   chain output { 		       
      type filter hook output priority 0; policy accept;  	
      
      # Allow loopback interface 		       
      oifname lo accept	    
   } 
}

Questo mi porta a un’altra domanda: cosa fare?

Ok. Penso di aver capito ora. Regole ntftables funzionanti con Discourse (Docker).

Dopo l’installazione iniziale di Discourse, esegui quanto segue su Debian (Bullseye):

[1] Salva il ruleset di Docker dopo l’installazione:

nft flush ruleset
systemctl restart docker
iptables-save > iptables-docker.conf
iptables-restore-translate -f iptables-docker.conf > docker.nft

[2] Applica e mostra il ruleset:

nft -f docker.nft
nft list ruleset

Questo è il ruleset esportato dopo l’installazione di Discourse:

table ip filter {
	chain INPUT {
		type filter hook input priority filter; policy accept;
	}

	chain FORWARD {
		type filter hook forward priority filter; policy accept;
		counter jump DOCKER-USER
		counter jump DOCKER-ISOLATION-STAGE-1
		oifname "docker0" ct state established,related counter accept
		oifname "docker0" counter DOCKER
		iifname "docker0" oifname != "docker0" counter accept
		iifname "docker0" oifname "docker0" counter accept
	}

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

	chain DOCKER {
		iifname != "docker0" oifname "docker0" ip daddr 172.17.0.2 tcp dport 443 counter accept
		iifname != "docker0" oifname "docker0" ip daddr 172.17.0.2 tcp dport 80 counter accept
	}

	chain DOCKER-ISOLATION-STAGE-1 {
		iifname "docker0" oifname != "docker0" counter packets 0 bytes 0 jump DOCKER-ISOLATION-STAGE-2
		counter return
	}

	chain DOCKER-ISOLATION-STAGE-2 {
		oifname "docker0" drop
		counter return
	}

	chain DOCKER-USER {
		counter return
	}
}
table ip nat {
	chain PREROUTING {
		type nat hook prerouting priority dstnat; policy accept;
		fib daddr type local counter jump DOCKER
	}

	chain INPUT {
		type nat hook input priority 100; policy accept;
	}

	chain OUTPUT {
		type nat hook output priority -100; policy accept;
		ip daddr != 127.0.0.0/8 fib daddr type local counter jump DOCKER
	}

	chain POSTROUTING {
		type nat hook postrouting priority srcnat; policy accept;
		oifname != "docker0" ip saddr 172.17.0.0/16 counter masquerade
		ip saddr 172.17.0.2 ip daddr 172.17.0.2 tcp dport 443 masquerade
		ip saddr 172.17.0.2 ip daddr 172.17.0.2 tcp dport 80 masquerade
	}

	chain DOCKER {
		iifname "docker0" counter return
		iifname != "docker0" tcp dport 443 counter dnat to 172.17.0.2:443
		iifname != "docker0" tcp dport 80 counter dnat to 172.17.0.2:80
	}
}

[3] Integra/Adatta il ruleset:
nano /etc/nftables.conf

#!/usr/sbin/nft -f

####################
# Purge/Flush      #
####################
flush ruleset

####################
# IPv4/IPv6        #
####################
table inet filter {
####################
# Incoming Traffic #
####################	    
   chain input {       
      type filter hook input priority 0; policy drop;
      
      # Allow loopback interface	       
      iifname lo accept

      # Rate limit ICMPv4|ICMPv6 traffic 
      ip protocol icmp icmp type { echo-request, echo-reply, destination-unreachable, time-exceeded, parameter-problem, router-solicitation, router-advertisement } limit rate over 5/second drop 
      ip6 nexthdr icmpv6 icmpv6 type { destination-unreachable, packet-too-big, time-exceeded, echo-request, parameter-problem, echo-reply, nd-router-solicit, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert, ind-neighbor-solicit, ind-neighbor-advert } limit rate over 5/second drop

      # Allow packets to established/related connections    
      ct state established,related accept   
  
      # Drop invalid connections  
      ct state invalid drop
      
      # Allow ICMPv4: Ping requests | Error messages | Router selection messages       
      ip protocol icmp icmp type { echo-request, echo-reply, destination-unreachable, time-exceeded, parameter-problem, router-solicitation, router-advertisement } accept              
   
      # Allow ICMPv6 traffic (https://tools.ietf.org/html/rfc4890#page-18)       
      ip6 nexthdr icmpv6 icmpv6 type { destination-unreachable, packet-too-big, time-exceeded, echo-request, parameter-problem, echo-reply, nd-router-solicit, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert, ind-neighbor-solicit, ind-neighbor-advert } accept

      # Allow SSH access on port 7777 [rate limit]       
      tcp dport 7777 ct state new limit rate 3/minute accept

      # Allow HTTP / HTTPS traffic [needed for IPv6 Discourse access]
      tcp dport { http, https } accept
      
      # Reject other packets       
      ip protocol tcp reject with tcp reset
      ip6 nexthdr tcp reject with tcp reset    
   }
#################### 
# Outgoing Traffic # 
####################    
   chain output { 		       
      type filter hook output priority 0; policy accept;  
      
      # Allow loopback interface 		       
      oifname lo accept	    
   } 
}

#################### 
# IPv6             # 
#################### 
table ip6 filter { 
#################### 
# Forward Traffic  # 
####################   
   chain forward {       
      type filter hook forward priority 0; policy drop;    
   } 
} 

#################### 
# IPv4             # 
#################### 
table ip filter { 
#################### 
# Forward Traffic  # 
####################    
   chain forward {       
      type filter hook forward priority 0; policy drop;       
      jump DOCKER-USER       
      jump DOCKER-ISOLATION-STAGE-1       
      oifname "docker0" ct state established,related accept       
      oifname "docker0" jump DOCKER       
      iifname "docker0" oifname != "docker0" accept       
      iifname "docker0" oifname "docker0" accept    
   } 
#################### 
# Docker Traffic   # 
####################    
   chain DOCKER {       
      iifname != "docker0" oifname "docker0" ip daddr 172.17.0.2 tcp dport 443 accept       
      iifname != "docker0" oifname "docker0" ip daddr 172.17.0.2 tcp dport 80 accept    
   }    
   chain DOCKER-ISOLATION-STAGE-1 {       
      iifname "docker0" oifname != "docker0" jump DOCKER-ISOLATION-STAGE-2       
      return    
   }    
   chain DOCKER-ISOLATION-STAGE-2 {       
      oifname "docker0" drop       
      return    
   }    
   chain DOCKER-USER {       
      return    
   } 
} 

#################### 
# NAT IPv4         # 
#################### 
table ip nat { 
#################### 
# Docker NAT       # 
####################    
   chain PREROUTING {       
      type nat hook prerouting priority dstnat; policy accept;       
      fib daddr type local jump DOCKER    
   }
   chain INPUT {      
      type nat hook input priority 100; policy accept;    
   }
   chain OUTPUT {       
      type nat hook output priority -100; policy accept;       
      ip daddr != 127.0.0.0/8 fib daddr type local jump DOCKER    
   }
   chain POSTROUTING {       
      type nat hook postrouting priority srcnat; policy accept;       
      oifname != "docker0" ip saddr 172.17.0.0/16 masquerade       
      ip saddr 172.17.0.2 ip daddr 172.17.0.2 tcp dport 443 masquerade       
      ip saddr 172.17.0.2 ip daddr 172.17.0.2 tcp dport 80 masquerade    
   }
   chain DOCKER {       
      iifname "docker0" return       
      iifname != "docker0" tcp dport 443 dnat to 172.17.0.2:443       
      iifname != "docker0" tcp dport 80 dnat to 172.17.0.2:80    
   } 
}

[4] Disabilita le regole iptables di Docker dopo il riavvio del servizio docker:
nano /etc/docker/daemon.json

{
   "iptables": false
}
systemctl restart docker
nft list ruleset

[5] Applica il tuo ruleset:

nft -f /etc/nftables.conf

Questo è tutto. Verificato. Funziona. Forse questo aiuterà qualcuno.