用于加固Discourse安装的Nftables规则

大家好,

这是我在这里的第一个帖子。我的背景:我管理 Linux 服务器已有十多年。大约一周前,我在一台服务器(Debian Bullseye)上安装了 Discourse。到目前为止,我都很喜欢!

现在我想在宿主系统(例如 Web 服务器)上实施常规的加固措施。这包括 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 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
   }
}

但是,激活后 Discourse 就无法工作了。我怀疑数据包没有传递到 Discourse 的 Docker 安装中。特别是这个规则可能存在问题:

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

但在我开始修改之前,我想问一下是否有人已经处理过这个问题,并为基础系统提供了可用的防火墙规则。这些规则对于 Docker Discourse 安装来说有意义吗?到目前为止,我与 Docker 的接触很少。

更多信息。这些防火墙规则由 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;
	}
}

此致敬礼

@OrkoGrayskull,你在搜索中找到这个了吗?

那里有更多关于 Docker 如何与防火墙交互的背景信息。

2 个赞

我已经阅读了该线程并自己测试了一些东西。Docker 会带来自己的 iptables 规则,并在安装后或在 docker 服务(重新)启动/加载时应用它们:

service docker restart

没有这些规则集,Discourse 的安装将无法正常工作。这意味着:在安装、重建或重启容器时,必须存在这些规则。否则会出现错误消息:

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))

但是,一旦 Discourse 安装完成或容器在更新后重启,Docker 规则就显然不再需要了。然后可以加载以下 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	    
   } 
}

这又引出了另一个问题:该怎么办?

好的。我想我现在明白了。使用 Discourse (Docker) 的有效 ntftables 规则集。

在初始安装 Discourse 后,在 Debian (Bullseye) 上执行以下操作:

[1] 安装后保存 Docker 规则集:

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

[2] 应用并显示规则集:

nft -f docker.nft
nft list ruleset

这是 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] 集成/调整规则集:
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] 重启 docker 服务后禁用 Docker iptables 规则:
nano /etc/docker/daemon.json

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

[5] 应用您的规则集:

nft -f /etc/nftables.conf

就是这样。已检查。有效。也许这对某人有帮助。