Securing Docker ports with firewalld

概述

为了保护 Docker 暴露的端口不受外部访问的影响,可以使用 firewalld 配置防火墙规则,只允许特定的 IP 访问。 通过让 firewalld 创建 DOCKER-USER 链,我们可以实现由 firewalld 维护的安全 Docker 端口, Docker 处理 iptables 规则以提供网络隔离,更多详细

本文基于环境

  • Docker-CE 19.03.12
  • CentOS Linux release 7.8.2003
  • Firewall 0.6.3

本文例子:

我们使用 Docker 安装一个 Nginx,并将 80(HTTP) 端口对外映射为 8080,443(HTTPS) 端口对外映射为 8443,并通过 Firewalld 仅允许特定的 IP 访问;提示:后续的防火墙规则中配置的端口是容器内部端口,例如 80,443

  • 192.168.51.246 安装 Nginx Docker

  • 配置 192.168.51.245 可以访问 Nginx Docker

  • 其他机器无法访问 Nginx Docker

重要的事情说三遍

如果你在 Docker 运行时重启 firewalld,那么 firewalld 将删除 DOCKER-USER

不要在 Docker 运行时重启 firewalld

不要在 Docker 运行时重启 firewalld

不要在 Docker 运行时重启 firewalld

准备

清除所有 iptables 配置并重启 Docker。否则在启动 Nginx Docker 时可能会看到 failed: iptables: No chain/target/match by that name 错误

iptables -t filter -F
iptables -t filter -X
iptables -t nat -F
iptables -t nat -X
systemctl restart docker

设置 SELINUX 权限

setenforce Permissive

永久设置 SELINUX 权限

修改 /etc/selinux/config 文件,设置 SELINUX=permissive,使其永久生效(需要重启)

安装 Nginx

docker run --name test-nginx --rm -d -p 8080:80 -p 8443:443 nginx:1.20.0-alpine

在任意其他机器上测试 http://192.168.51.246:8080/ 可以正常访问

curl http://192.168.51.246:8080/

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

配置 Firewalld

  1. 停止 Docker
systemctl stop docker
  1. 在 firewalld 中重建 DOCKER-USER iptables chain(请忽略任何警告)
firewall-cmd --permanent --direct --remove-chain ipv4 filter DOCKER-USER
firewall-cmd --permanent --direct --remove-rules ipv4 filter DOCKER-USER
firewall-cmd --permanent --direct --add-chain ipv4 filter DOCKER-USER
  1. 添加 iptables 规则到 DOCKER-USER chain
firewall-cmd --permanent --direct --add-rule ipv4 filter DOCKER-USER 1 \
  -m conntrack \
  --ctstate RELATED,ESTABLISHED -j ACCEPT \
  -m comment --comment 'Allow containers to connect to the outside world'

firewall-cmd --permanent --direct --add-rule ipv4 filter DOCKER-USER 1 \
  -j RETURN \
  -s 172.17.0.0/16 \
  -m comment --comment 'allow internal docker communication'

提示: 172.17.0.0/16 是 Docker 的默认子网地址, 也可以改为你实际的 Docker 子网地址

  1. 为 机器 192.168.51.245 配置允许访问 Nginx Docker 80,443 端口,优先级为1(您可以在以后添加更多优先级为 0 的规则。请参见下文)
firewall-cmd --permanent --direct --add-rule ipv4 filter DOCKER-USER 1 \
  -o docker0 \
  -p tcp -m multiport \
  --dports 80,443 -s 192.168.51.245/32 -j ACCEPT \
  -m comment \
  --comment 'Allow IP 192.168.51.245 to access http and https docker ports'

提示:这里的端口是指的 Docker 的内部端口 80,443,而不是映射的外部端口 8080,8443

  1. 阻止所有其他IP。 此规则的优先级最低,您可以稍后在此规则之前添加规则
firewall-cmd --permanent --direct --add-rule ipv4 filter DOCKER-USER 10 \
  -j REJECT -m comment --comment 'reject all other traffic to DOCKER-USER'
  1. 激活规则
firewall-cmd --reload
  1. 启动 Docker
systemctl start docker
  1. 配置完毕

你能在文件 /etc/firewalld/direct.xml 中看到规则配置

<?xml version="1.0" encoding="utf-8"?>
<direct>
  <chain table="filter" ipv="ipv4" chain="DOCKER-USER"/>
  <rule priority="1" table="filter" ipv="ipv4" chain="DOCKER-USER">-m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT -m comment --comment 'Allow containers to connect to the outside world'</rule>
  <rule priority="1" table="filter" ipv="ipv4" chain="DOCKER-USER">-j RETURN -s 172.17.0.0/16 -m comment --comment 'allow internal docker communication'</rule>
  <rule priority="1" table="filter" ipv="ipv4" chain="DOCKER-USER">-o docker0 -p tcp -m multiport --dports 80,443 -s 192.168.51.245/32 -j ACCEPT -m comment --comment 'Allow IP 192.168.51.245 to access htt
    p and https docker ports'</rule>
  <rule priority="10" table="filter" ipv="ipv4" chain="DOCKER-USER">-j REJECT -m comment --comment 'reject all other traffic to DOCKER-USER'</rule>
</direct>

使用 iptables -L 命令查看

Chain DOCKER-USER (1 references)
target     prot opt source               destination
LOG        all  --  anywhere             anywhere             LOG level warning prefix " DOCKER TCP: "
ACCEPT     all  --  anywhere             anywhere             ctstate RELATED,ESTABLISHED /* Allow containers to connect to the outside world */
RETURN     all  --  172.17.0.0/16        anywhere             /* allow internal docker communication */
ACCEPT     tcp  --  192.168.51.245       anywhere             multiport dports http,https /* Allow IP 192.168.51.245 to access http and https docker ports */
REJECT     all  --  anywhere             anywhere             /* reject all other traffic to DOCKER-USER */ reject-with icmp-port-unreachable
RETURN     all  --  anywhere             anywhere

调试日志

为了进行调试,您可以将日志记录添加到具有最高优先级的DOCKER-USER链中

firewall-cmd --direct --add-rule ipv4 filter DOCKER-USER 0 \
  -j LOG --log-prefix ' DOCKER TCP: '

执行 tail -f /var/log/messages | grep "DOCKER TCP" 命令可以查看日志

来自 192.168.51.245 的访问被允许

May 12 11:45:48 localhost kernel: DOCKER TCP: IN=ens33 OUT=docker0 MAC=00:0c:29:a5:9d:2d:00:0c:29:4f:d7:c9:08:00 SRC=192.168.51.245 DST=172.17.0.2 LEN=60 TOS=0x00 PREC=0x00 TTL=63 ID=52186 DF PROTO=TCP SPT=38880 DPT=80 WINDOW=29200 RES=0x00 SYN URGP=0
May 12 11:45:48 localhost kernel: DOCKER TCP: IN=docker0 OUT=ens33 PHYSIN=veth621dcb8 MAC=02:42:c9:d9:ec:7f:02:42:ac:11:00:02:08:00 SRC=172.17.0.2 DST=192.168.51.245 LEN=60 TOS=0x00 PREC=0x00 TTL=63 ID=0 DF PROTO=TCP SPT=80 DPT=38880 WINDOW=28960 RES=0x00 ACK SYN URGP=0
May 12 11:45:48 localhost kernel: DOCKER TCP: IN=ens33 OUT=docker0 MAC=00:0c:29:a5:9d:2d:00:0c:29:4f:d7:c9:08:00 SRC=192.168.51.245 DST=172.17.0.2 LEN=52 TOS=0x00 PREC=0x00 TTL=63 ID=52187 DF PROTO=TCP SPT=38880 DPT=80 WINDOW=229 RES=0x00 ACK URGP=0
May 12 11:45:48 localhost kernel: DOCKER TCP: IN=ens33 OUT=docker0 MAC=00:0c:29:a5:9d:2d:00:0c:29:4f:d7:c9:08:00 SRC=192.168.51.245 DST=172.17.0.2 LEN=135 TOS=0x00 PREC=0x00 TTL=63 ID=52188 DF PROTO=TCP SPT=38880 DPT=80 WINDOW=229 RES=0x00 ACK PSH URGP=0
May 12 11:45:48 localhost kernel: DOCKER TCP: IN=docker0 OUT=ens33 PHYSIN=veth621dcb8 MAC=02:42:c9:d9:ec:7f:02:42:ac:11:00:02:08:00 SRC=172.17.0.2 DST=192.168.51.245 LEN=52 TOS=0x00 PREC=0x00 TTL=63 ID=24181 DF PROTO=TCP SPT=80 DPT=38880 WINDOW=227 RES=0x00 ACK URGP=0
May 12 11:45:48 localhost kernel: DOCKER TCP: IN=docker0 OUT=ens33 PHYSIN=veth621dcb8 MAC=02:42:c9:d9:ec:7f:02:42:ac:11:00:02:08:00 SRC=172.17.0.2 DST=192.168.51.245 LEN=290 TOS=0x00 PREC=0x00 TTL=63 ID=24182 DF PROTO=TCP SPT=80 DPT=38880 WINDOW=227 RES=0x00 ACK PSH URGP=0
May 12 11:45:48 localhost kernel: DOCKER TCP: IN=docker0 OUT=ens33 PHYSIN=veth621dcb8 MAC=02:42:c9:d9:ec:7f:02:42:ac:11:00:02:08:00 SRC=172.17.0.2 DST=192.168.51.245 LEN=664 TOS=0x00 PREC=0x00 TTL=63 ID=24183 DF PROTO=TCP SPT=80 DPT=38880 WINDOW=227 RES=0x00 ACK PSH URGP=0
May 12 11:45:48 localhost kernel: DOCKER TCP: IN=ens33 OUT=docker0 MAC=00:0c:29:a5:9d:2d:00:0c:29:4f:d7:c9:08:00 SRC=192.168.51.245 DST=172.17.0.2 LEN=52 TOS=0x00 PREC=0x00 TTL=63 ID=52189 DF PROTO=TCP SPT=38880 DPT=80 WINDOW=237 RES=0x00 ACK URGP=0
May 12 11:45:48 localhost kernel: DOCKER TCP: IN=ens33 OUT=docker0 MAC=00:0c:29:a5:9d:2d:00:0c:29:4f:d7:c9:08:00 SRC=192.168.51.245 DST=172.17.0.2 LEN=52 TOS=0x00 PREC=0x00 TTL=63 ID=52190 DF PROTO=TCP SPT=38880 DPT=80 WINDOW=247 RES=0x00 ACK URGP=0
May 12 11:45:48 localhost kernel: DOCKER TCP: IN=ens33 OUT=docker0 MAC=00:0c:29:a5:9d:2d:00:0c:29:4f:d7:c9:08:00 SRC=192.168.51.245 DST=172.17.0.2 LEN=52 TOS=0x00 PREC=0x00 TTL=63 ID=52191 DF PROTO=TCP SPT=38880 DPT=80 WINDOW=247 RES=0x00 ACK FIN URGP=0
May 12 11:45:48 localhost kernel: DOCKER TCP: IN=docker0 OUT=ens33 PHYSIN=veth621dcb8 MAC=02:42:c9:d9:ec:7f:02:42:ac:11:00:02:08:00 SRC=172.17.0.2 DST=192.168.51.245 LEN=52 TOS=0x00 PREC=0x00 TTL=63 ID=24184 DF PROTO=TCP SPT=80 DPT=38880 WINDOW=227 RES=0x00 ACK FIN URGP=0
May 12 11:45:48 localhost kernel: DOCKER TCP: IN=ens33 OUT=docker0 MAC=00:0c:29:a5:9d:2d:00:0c:29:4f:d7:c9:08:00 SRC=192.168.51.245 DST=172.17.0.2 LEN=52 TOS=0x00 PREC=0x00 TTL=63 ID=52192 DF PROTO=TCP SPT=38880 DPT=80 WINDOW=247 RES=0x00 ACK URGP=0

来自 192.168.51.236 的访问被拒绝

May 12 11:45:55 localhost kernel: DOCKER TCP: IN=ens33 OUT=docker0 MAC=00:0c:29:a5:9d:2d:00:0c:29:26:fd:35:08:00 SRC=192.168.51.236 DST=172.17.0.2 LEN=60 TOS=0x00 PREC=0x00 TTL=63 ID=49189 DF PROTO=TCP SPT=56194 DPT=80 WINDOW=29200 RES=0x00 SYN URGP=0

提示

如果你想重置 firewalld,那么请按如下步骤操作,!!!必须先停止 Docker!!!

systemctl stop docker
systemctl stop firewalld
rm -rf /etc/firewalld/direct.xml
systemctl start firewalld
systemctl start docker