Firewall Configuration Compatible With Docker

由于更换了VPS供应商,折腾了一下防火墙的配置。主要解决了Debian 12的防火墙与Docker的兼容问题。

1. 概述

参考:

1.1. 主要问题

Debian 12与Docker在防火墙方面的不兼容:

  • Debian从10开始默认使用nftables配置和管理防火墙,而Docker还在使用iptables(且不支持nftables)。
  • Docker网络在防火墙上有一套自己的规则。
  • 防火墙规则,一般是清空再加载,不同的配置会出现被覆盖的问题。

1.2. 网络模型

OSI模型,Open Systems Interconnection model(即开放式系统互联模型),是一个描述网络功能的概念框架。其分为7层。这是大学计算机课程《计算机网络》里的基本知识。

TCP/IP模型,是互联网的基础,它是一系列网络协议的总称。分5层或4层。

OSI模型是学术上和法律上的国际标准,是完整的权威的网络参考模型。而TCP/IP参考模型是事实上的国际标准,即现实生活中被广泛使用的网络参考模型。

两个模型的层次划分如下:

OSI模型(7层) TCP/IP模型(5层) TCP/IP模型(4层)
第7层 应用层 第5层 应用层 第4层 应用层
第6层 表现层
第5层 会话层
第4层 传输层 第4层 传输层 第3层 传输层
第3层 网络层 第3层 网络层 第2层 网络层
第2层 数据链路层 第2层 数据链路层 第1层 网络接口层
第1层 物理层 第1层 物理层

1.3. netfilter框架

netfilter是Linux内核中的一个数据包处理框架,其工作在网络模型的上层协议(对应OSI模型第5层及其以上,或TCP/IP模型的应用层)。iptables和nftables是用于管理netfilter。

netfilter提供了5个hook点,数据包经过协议栈时会触发内核模块注册在这里的处理函数。iptables和nftables配置这些处理函数对数据包的管理,实现防火墙的功能。

netfilter提供5个Hook点如下图:

User space           上层协议堆栈(例如HTTP协议的Web服务)
              数据终点(收)                          数据起点(发)
                  ↑                                    |
------------------│------------------------------------|---------------
Kernel space      │                                    ⭣
                input                               output
         LOCAL_IN ↑                                    | LOCAL_OUT
                  |                                    ⭣
             目标IP是本机                            路由查询2
 PRE_ROUTING      |                                    ⭣ POST_ROUTING
  prerouting ⭢ 路由查询1 -目标IP不是本机⭢ forward ⭢ postrouting -┐
      ↑                                                |       |
------|------------------------------------------------|-------|------
      |                Network adapter                 |       |
------|------------------------------------------------|-------|------
User -┘                                                |       |
     ←-------------------------------------------------┘       └→ 转发给其它主机
  • PRE_ROUTING: 是所有接收数据包到达的第一个hook触发点,此处将进行数据包目的地转换 (DNAT), 决定数据包是发给 本地进程、其他机器、其他network namespace
  • LOCAL_IN: 经过路由判断后,目标地址是本机的接收数据包到达此hook触发点
  • FORWARD: 经过路由判断后,目标地址不是本机地址的数据包到达此hook触发点
  • LOCAL_OUT: 所有本地生成的发往其他机器的包, 在进入网络栈后首先到达此hook触发点
  • POST_ROUTING: 本机产生准备发出的包或者转发的包,在经过路由判断后到达此hook触发点

参考:

2. 问题

结合上面的原理,针对Debian 12部署Docker所出现的防火墙问题,整理解决方案。

2.1. 管理工具的冲突

  • 问题:Docker使用iptbales,Debian 12使用nftables,两个不同的防火墙管理工具。
  • 解决:
    • Debian 12虽然默认使用nftables,但其提供了iptables-nft,把iptbales的配置转为nftables,实现对iptables的兼容。
    • Debian 12安装iptables,默认采用iptables-nft。即Docker执行iptbales命令时,实际是调用了iptables-nft。
    • 另外,如果Debian 12上需要使用原来的iptbales,需要安装iptables-legacy。

2.2. 防火墙配置冲突

  • 问题:按照一般的教程配置完防火墙,开机后会导致Docker的防火墙规则丢失。
  • 原因:
    • 一般的防火墙配置教程,会在开机后执行iptables-restorenft -f来加载一套自定义的防火墙规则。
    • 这个操作一般在Docker服务启动后执行,导致Docker配置好的防火墙规则被清除。
  • 解决:
    • 使用netfilter-persistent命令保存防火墙规则。
    • netfilter-persistent会在网络启动后,Docker服务启动前,加载用户的防火墙规则。然后Docker服务启动后再加载自己的防火墙规则,实现不冲突。
    • 另外,Docker在FORWARD链里创建了子链DOKCER-USER,提供给用户配置针对Docker的防火墙规则。Docker服务重启后,不会清除或修改DOKCER-USER链的规则。

2.3. Docker容器映射到宿主机端口的配置

  • 问题:Docker容器映射到宿主机端口,在INPUT链的规则对其无效。
  • 原因:
    • 防火墙的INPUT链,只针对本机运行服务所开启的端口。
    • 访问Docker容器映射到宿主机端口时,没有走INPUT链,而是走FORWARD链。
  • 解决:
    • Docker容器映射到宿主机端口的防火墙规则,配置在FORWARD链里的DOKCER-USER链。

2.4. DOKCER-USER链的配置

  • 问题:如何把DOKCER-USER链配置为“白名单”?
  • 解决:
    • 由于DOKCER-USER链是FORWARD链的子链,要先理解FORWARD链。FORWARD链就像混合了INPUT和OUTPUT的规则。
    • DOKCER-USER链配置为“白名单”时,先允许(ACCEPT)指定宿主机端口流入对应Docker容器端口,再禁止(REJECT)从宿主机网口流入到Docker容器,最后把其它数据包交给(RETURN)DOCKER-FORWARD链处理(例如从Docker容器流出)。

3. 配置

通常Debian 12预装了iptables,但需要安装netfilter-persistent命令,对应的安装包为iptables-persistent

sudo apt update && sudo apt -y install iptables-persistent

查看当前iptables配置

# 列出所有链的规则,简单信息
sudo iptables -L

# 列出所有链的规则,详细信息。`-n`端口、协议显示为数字,`-v`详细模式,`--line-numbers`各个链配置显示行号
sudo iptables -L -n -v --line-numbers

# 查看`DOCKER-USER`链的规则。`DOCKER-USER`是链的名称,可以替换为其它链。
sudo iptables -L DOCKER-USER -n -v --line-numbers

iptables配置参考

# 设置默认允许所有连接,方便配置
sudo iptables -P INPUT ACCEPT
sudo iptables -P FORWARD ACCEPT
sudo iptables -P OUTPUT ACCEPT

# 配置INPUT链
# INPUT链允许本地回环
sudo iptables -A INPUT -i lo -j ACCEPT
# INPUT链允许已建立的连接
sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# INPUT链允许`ping`命令访问
sudo iptables -A INPUT -p icmp -j ACCEPT
# INPUT链开启SSH端口(22)
sudo iptables -A INPUT -p tcp -m conntrack --ctstate NEW --dport 22 -j ACCEPT
# INPUT链开启HTTP端口(80),如果是Docker容器提供HTTP服务,此配置可以不执行
sudo iptables -A INPUT -p tcp --dport 80 -j ACCEPT
# INPUT链开启HTTPS端口(443),如果是Docker容器提供HTTPS服务,此配置可以不执行
sudo iptables -A INPUT -p tcp --dport 443 -j ACCEPT

# 配置DOCKER-USER链
# DOCKER-USER链清空配置
sudo iptables -F DOCKER-USER
# DOCKER-USER链允许已建立的连接
sudo iptables -A DOCKER-USER -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# DOCKER-USER链允许HTTP服务(端口80)访问,且只允许数据包从宿主机80端口(参数`--ctorigdstport`的值)流入任意Docker容器80端口(参数`--dport`的值)
sudo iptables -A DOCKER-USER -p tcp --dport 80 -m conntrack --ctorigsrc 0.0.0.0/0 --ctorigdstport 80 -j ACCEPT
# DOCKER-USER链允许HTTP服务(端口443)访问,且只允许数据包从宿主机443端口(参数`--ctorigdstport`的值)流入任意Docker容器443端口(参数`--dport`的值)
sudo iptables -A DOCKER-USER -p tcp --dport 443 -m conntrack --ctorigsrc 0.0.0.0/0 --ctorigdstport 443 -j ACCEPT
# DOCKER-USER链禁止数据包从宿主机网口`enp1s0`(需按实际情况修改)流入,实现DOCKER-USER链“白名单”功能
sudo iptables -A DOCKER-USER -i enp1s0 -j REJECT
# DOCKER-USER链允许其它数据包,例如从Docker容器流出的数据包
sudo iptables -A DOCKER-USER -j RETURN

# INPUT链和FORWARD链默认禁止,即配置为“白名单”模式
sudo iptables -P INPUT DROP
sudo iptables -P FORWARD DROP

# 保存配置,保存在`/etc/iptables/rules.v4`和`/etc/iptables/rules.v6`
sudo netfilter-persistent save

注意:

  • iptables命令是即时生效。最好确认能直接操作实体机或者VPS,避免防火墙配置失败而不能访问。
  • 保证配置正确后,才执行“保存配置”的命令。即使配置错误,可以通过重启系统进行还原防火墙。
  • Docker的防火墙配置,会在Docker服务器重启后自动配置,所以这里保存配置时不用理会是否包含了Docker的防火墙配置。另一层意思是,即使搞坏了Docker的防火墙配置,重启Docker服务即可。
使用 Hugo 构建
主题 StackJimmy 设计