ryandaniels.iptables_docker
Ansible 角色:用于 Docker(和 Docker Swarm)的 iptables
通过 iptables 向服务器添加防火墙规则,用于保护 Docker 和 Docker Swarm。这实际上可以保护你的 Docker 容器!
这个 Ansible 角色存在的原因是 firewalld 与 Docker(及 Docker Swarm)不兼容。
解决的问题:在 Docker 中启动一个“发布”端口的容器时,你无法控制该端口,它会通过服务器的防火墙暴露出来。无论你是否使用 iptables 或服务器上的其他防火墙,Docker 都会将这个“发布”端口开放给所有人,并绕过你的防火墙。
这个解决方案的使用场景:允许受信任的 IP 连接到 Docker 容器(和 Docker Swarm 容器),以及其他开放的操作系统端口。受信任的 IP 可能不在同一网络 IP 范围,甚至可能不在同一网络子网中。
这本应是简单的。通过防火墙保护 Docker。但不幸的是,它并不是。我尽量保持这个过程简单。
可能存在未知的问题,请自行承担风险!
另请参见:https://ryandaniels.ca/blog/secure-docker-with-iptables-firewall-and-ansible/
以及关于 Docker 使用 INPUT 链的相关内容:https://ryandaniels.ca/blog/docker-iptables-input-chain/
当前已在以下环境测试并有效:
- CentOS/RHEL 7
- Ubuntu 18.04
- Ubuntu 20.04
功能
- 支持 Docker 和 Docker Swarm(也称为 Docker SwarmKit)。
- 默认安全。一旦配置,只有 Docker 的 IP 能访问所有容器以及服务器上所有其他有开放端口的操作系统进程。
- 尽可能简单。iptables 规则越少,性能理论上就会越快。
- 自动化。无需手动添加端口到防火墙配置(如果使用受信任的 IP 集合)。
- 添加允许与所有 Docker 容器和服务器上其他进程通信的“受信任” IP。
- 通过防火墙向公众(所有人)开放指定的 Docker 容器端口或服务器的操作系统端口,例如 SSH。
- 还可以指定接口。默认情况下过滤所有接口。你可以过滤特定的网络接口,并允许所有其他接口(仅指定一个不受信任的接口)。
- 所有操作均在“离线”模式下进行,因此在 iptables 规则激活时,Docker 应该不会遇到问题。
- 你不需要是 iptables 专家即可使用此功能。
- 与 Docker Swarm 未记录的 iptables 使用和加密的覆盖网络也兼容(iptables 规则附加到 INPUT 链)。
该解决方案使用 iptables
作为防火墙,并使用 ipset
允许 iptables 拥有一个允许的 IP 列表。ipset
还允许你使用不连续的 IP 范围。
使用的 iptables 链及其作用:
INPUT,没有被清空。规则插入顶部以跳转到与操作系统相关的自定义链。
DOCKER-USER,已清空。所有与 Docker (和 Docker Swarm)相关的规则在这里,默认情况下阻止容器对所有人暴露。默认情况下只有 Docker 服务器的 IP 被允许。其他 IP 和容器端口可以由用户添加。
FILTERS,已清空。服务器进程(不是 Docker)的自定义链。默认情况下只有 Docker 服务器的 IP 被允许。其他 IP 和容器端口可以由用户添加。
iptables 手册:http://ipset.netfilter.org/iptables.man.html
警告
不要让自己无法访问服务器。这是在修改你的防火墙。请始终保持其他进入服务器的方式,比如“控制台”。
关于 IP 的注意事项:这仅适用于 IPv4。IPv6 尚未测试。如果你禁用服务器上的 IPv6,更安全。
其他安全考虑:
如果使用非 Swarm(普通 Docker),建议将端口绑定到内部 IP 以增强安全性。如果使用 Swarm,建议使用特定的 IP 进行 Docker Swarm 通信。
例如:docker swarm init --advertise-addr 192.168.100.100 --listen-addr=192.168.100.100 --data-path-addr=192.168.100.100
重要提示:Docker 与 firewalld 不兼容。此 Ansible 角色启用了检查,如果 firewalld 服务正在运行或已启用,则将失败。
有关 firewalld 和 Docker 的更多信息:
https://success.docker.com/article/why-am-i-having-network-problems-after-firewalld-is-restarted
https://www.tripwire.com/state-of-security/devops/psa-beware-exposing-ports-docker/
https://docs.docker.com/network/iptables/
SELinux 缺陷:
目前,SELinux 存在一个缺陷,导致无法将 iptables 规则保存到 iptables.save 文件中。
影响:第二次保存 iptables 规则会静默失败。
已添加解决方法,以便 SELinux 允许 chmod 与 iptables.save 文件交互。
另外,你也可以禁用 SELinux,但这不建议。
错误报告:https://bugs.centos.org/view.php?id=12648
有关手动执行解决方法的更多详细信息,请参见 下文。
警告:
确保首先在非生产环境中测试,我不能做任何保证,或承担责任。
小心,这将删除和添加操作系统上的 iptables 规则。谨慎使用。
现有 iptables 规则可能会被删除!请在运行此命令之前确认你的设置。
可能存在未知的问题,请自行承担风险!
测试的 Docker 版本
Docker Engine - 社区版版本:
- 19.03.8
- 19.03.9
- 19.03.12
在正常的 Docker 模式和 3 节点 Docker Swarm 集群中测试。
测试的发行版
- CentOS: 7.7,7.8
- Ubuntu 18.04
- Ubuntu 20.04
依赖项
- iptables 和 iptables-services
已在 v1.4.21(CentOS 7 中可用的最新版本)上进行测试。
- ipset 和 ipset-service
已在 v7.1(CentOS 7 中可用的最新版本)上进行测试。
默认设置
- 启用调试
debug_enabled_default: false
- 代理(在代理服务器后面安装所需软件包时需要)
proxy_env: []
- 默认情况下禁用角色。在 group_vars 或 playbook 中更改为 true
iptables_docker_managed: false
- 检查(Docker)服务是否在运行或启用,并失败该角色
iptables_docker_check_problem_service_managed: true
- 检查的服务,并失败该角色
iptables_docker_check_problem_service:
- docker.service
- 从变量显示配置
iptables_docker_show_config: true
- 启动 iptables 服务
iptables_docker_start: true
- 安装 iptables 包
iptables_docker_managed_pkg: true
iptables_docker_packages:
- iptables
- iptables-services
- ipset
- ipset-service
- policycoreutils-python #所需用于 semodule
- 强制复制 ipset 文件以触发 ipset 重新加载
iptables_docker_copy_ipset_force: false
- 强制复制 iptables 文件以触发 iptables 重新加载
iptables_docker_copy_iptables_force: false
- iptables 保存的配置位置
iptables_docker_iptables_config_save: /etc/sysconfig/iptables
- ipset 保存的配置位置
iptables_docker_ipset_config_dir: /etc/sysconfig/ipset.d
- ipset 最大元素(允许列表中的 IP)
如果在首次创建后更改,必须手动删除并重新创建。64k IP 应该足够。
iptables_docker_ipset_maxelem: 65536
用户设置
- 覆盖 Docker 服务器 IP(可选)
可选指定 Docker 服务器 IP。如果未设置,则将从 Ansible 清单中的 docker_hosts 组确定 IP。
# iptables_docker_server_ip_allow_set:
# - 192.168.100.100
# - 192.168.100.101
# - 192.168.100.102
- 有权使用所有 Docker 容器暴露的端口和所有服务器进程暴露的端口的 IP。
# iptables_docker_ip_allow_set: []
iptables_docker_ip_allow_set:
- 192.168.100.1
- 192.168.101.0/24
- 192.168.102.0/24
- 限制操作系统规则的网络适配器
仅列出的适配器将被阻止。其他将被允许。默认情况下阻止所有(使用‘+’)。 如果你只想限制特定的网络接口,请使用确切的名称。 如果你想限制所有相同类型的接口,请使用“interface+”以匹配每个接口,因为“+”是 iptables 的通配符。 例如,要限制 ethX 接口,请使用“eth+”。“eth+”是以 eth 开头的所有事物的通配符。 请勿使用“*”。这不是通配符,不匹配任何内容! 这里越少越好。封锁所有(‘+’)更安全,但如果不能,首先添加流量高的网络适配器。 本地(lo)这里不需要。
iptables_docker_external_network_adapter:
- "+" #通配符,用于所有
# - "eth+"
# - "enp0s+"
# - "wlp1s+"
- 对公众开放的操作系统 TCP 端口
允许所有人连接的端口(将对公众可访问)。这里只有操作系统的端口,而不是 Docker 容器的端口。
iptables_docker_global_ports_allow_tcp:
- 22 # SSH
- 对公众开放的操作系统 UDP 端口
允许所有人连接的端口(将对公众可访问)。这里只有操作系统的端口,而不是 Docker 容器的端口。
iptables_docker_global_ports_allow_udp: []
- 限制 Docker 规则的网络适配器
默认情况下使用与操作系统相同的设置。
iptables_docker_swarm_network_adapter: "{{ iptables_docker_external_network_adapter }}"
# iptables_docker_swarm_network_adapter:
# - "+" #通配符,用于所有
# # - "eth+"
- 对公众开放的 Docker TCP 端口
添加你希望所有人都能访问的 Docker 容器 TCP 端口。适用于 Docker 和 Docker Swarm。 Docker Swarm 端口在这里并不需要。
iptables_docker_swarm_ports_allow_tcp: []
# iptables_docker_swarm_ports_allow_tcp:
# - 9000
- 对公众开放的 Docker UDP 端口
添加你希望所有人都能访问的 Docker 容器 UDP 端口。适用于 Docker 和 Docker Swarm。 Docker Swarm 端口在这里并不需要。
iptables_docker_swarm_ports_allow_udp: []
- Docker 桥接网络名称(docker0),和 IP 范围(用于 DOCKER-USER iptables 源允许)
iptables_docker_bridge_name: docker0
iptables_docker_bridge_ips: 172.17.0.0/16
- Docker Swarm 桥接网络 IP 范围(docker_gwbridge)(用于 DOCKER-USER iptables 源允许)
iptables_docker_swarm_bridge_name: docker_gwbridge
iptables_docker_swarm_bridge_ips: 172.18.0.0/16
示例配置文件(inventories/dev-env/group_vars/all.yml)
以下示例中:
IP 将被添加到受信任列表:
192.168.100.1
192.168.101.0/24
由于使用了通配符“+”,所有网络接口将受到限制。
22 端口将公开开放。
---
iptables_docker_ip_allow_set:
- 192.168.100.1
- 192.168.101.0/24
iptables_docker_external_network_adapter:
- "+" #通配符,用于所有
iptables_docker_global_ports_allow_tcp:
- 22 # SSH
示例清单文件
[docker_hosts]
centoslead1 ansible_host=192.168.100.100
centoswork1 ansible_host=192.168.100.101
centoswork2 ansible_host=192.168.100.102
示例 Playbook iptables_docker.yml
---
- hosts: '{{ inventory }}'
become: yes
vars:
# 使用这个角色
iptables_docker_managed: true
roles:
- ryandaniels.iptables_docker
用法
在运行之前,请确保检查是否已经在使用 iptables!除非你使用与此相同的 iptables 链,否则不应覆盖/删除任何内容。
默认情况下,除非设置 iptables_docker_managed=true
,否则不会运行任何任务。这是故意的,以防止不阅读手册书的人意外操作。
ansible-playbook iptables_docker.yml --extra-vars "inventory=centos7 iptables_docker_managed=true" -i hosts-dev
跳过安装软件包(如果已知已存在 - 加快任务速度)
ansible-playbook iptables_docker.yml --extra-vars "inventory=centos7 iptables_docker_managed=true" -i hosts --skip-tags=iptables_docker_pkg_install
显示更详细的输出(调试信息)
ansible-playbook iptables_docker.yml --extra-vars "inventory=centos7 iptables_docker_managed=true debug_enabled_default=true" -i hosts-dev
不要启动 iptables 服务或添加 iptables 规则
ansible-playbook iptables_docker.yml --extra-vars "inventory=centos7 iptables_docker_managed=true iptables_docker_start=false" -i hosts-dev
强制 ipset 和 iptables 更新
ansible-playbook iptables_docker.yml --extra-vars "inventory=centos7 iptables_docker_managed=true iptables_docker_copy_ipset_force=true iptables_docker_copy_iptables_force=true" -i hosts-dev
仅显示配置(来自变量)
ansible-playbook iptables_docker.yml --extra-vars "inventory=centos7 iptables_docker_managed=true iptables_docker_show_config=true" -i hosts --tags "iptables_docker_show_config"
关于 ipset 大小限制的说明
重要提示:请注意“条目数量”的大小。如果该数字接近 maxelem 大小(65536),那么你需要删除 ipset “ip_allow”并以更大的 max 大小重新创建它。
64K 应该足够。
文件位于: templates/ip_allow.set.j2
create -exist ip_allow hash:ip family inet hashsize 1024 maxelem 65536
检查 ipset 列表的大小:
ipset list |grep "Number of entries"
重要输出:
Number of entries: 3
SELinux 手动解决方法用于 iptables 和 chmod
错误详情:https://bugs.centos.org/view.php?id=12648
问题是在第二次保存 iptables 时,SELinux 阻止了它,因为 chmod 在 iptables.save 文件中存在问题。
使用以下方法来允许 chmod 修改 iptables.save 文件,如果不使用 Ansible 角色。
要重现,请在设置 iptables 配置以在重启/停止后保存后,重启 iptables 服务,
yum install audit policycoreutils policycoreutils-python
ausearch -m AVC,USER_AVC,SELINUX_ERR,USER_SELINUX_ERR -i|tail -55
ausearch -c 'chmod' --raw | audit2allow -M iptables_save_chmod
#或者 grep "iptables.save" /var/log/audit/audit.log|tail | audit2allow -M iptables_save_chmod
semodule -i iptables_save_chmod.pp
iptables 命令参考
更多命令可以在 iptables 文档中找到:http://ipset.netfilter.org/iptables.man.html
列出当前活动的 iptables:
iptables -nvL --line-numbers
CentOS/RHEL 有用的命令:
cat /etc/sysconfig/ipset.d/ip_allow.set
systemctl restart ipset
ipset list | head
iptables -F DOCKER-USER
iptables -F FILTERS
iptables-restore -n < ansible_iptables_docker-iptables
grep -v "^#" ansible_iptables_docker-iptables
iptables -S INPUT
iptables -S DOCKER-USER
iptables -S FILTERS
Ubuntu 有用的命令:
vi /etc/iptables/ipsets
#如果手动删除 IP,请在添加之前手动添加“flush”。
/usr/sbin/netfilter-persistent reload
cat /etc/iptables/ipsets
cat /etc/iptables/rules.v4
手动命令(CentOS/RHEL)
检查你已有的 iptables 规则。请记下,以防被丢失!
iptables -nvL --line-numbers
安装所需的软件包:
yum install iptables iptables-services ipset ipset-service
如果使用 SELinux,还需安装:
yum install policycoreutils-python
使用你的服务器 IP 以及其他受信任的 IP 配置 ipset:
mkdir -p /etc/sysconfig/ipset.d
cat > /etc/sysconfig/ipset.d/ip_allow.set << 'EOF'
create -exist ip_allow hash:ip family inet hashsize 1024 maxelem 65536
add ip_allow 192.168.1.123
add ip_allow 192.168.101.0/24
add ip_allow 192.168.102.0/24
EOF
启动并启用 ipset 服务:
systemctl status ipset
systemctl start ipset
systemctl enable ipset
查看 ipset 中加载的配置:
ipset list | head
添加的 iptables 规则(默认情况下 22 端口对所有人开放):
cat > ansible_iptables_docker-iptables << 'EOF'
*filter
:DOCKER-USER - [0:0]
:FILTERS - [0:0]
#不能清空 INPUT。会清除docker swarm加密覆盖规则
#-F INPUT
#使用 ansible 或首先手动运行添加 -I INPUT -j FILTERS
#-I INPUT -j FILTERS
-A DOCKER-USER -m state --state RELATED,ESTABLISHED -j RETURN
-A DOCKER-USER -i docker_gwbridge -j RETURN
-A DOCKER-USER -s 172.18.0.0/16 -j RETURN
-A DOCKER-USER -i docker0 -j RETURN
-A DOCKER-USER -s 172.17.0.0/16 -j RETURN
#如果取消注释,以下 Docker 端口将对所有人开放
#-A DOCKER-USER -p tcp -m tcp -m multiport --dports 8000,8001 -j RETURN
#-A DOCKER-USER -p udp -m udp -m multiport --dports 9000,9001 -j RETURN
-A DOCKER-USER -m set ! --match-set ip_allow src -j DROP
-A DOCKER-USER -j RETURN
-F FILTERS
#因为 Docker Swarm 加密覆盖网络会将规则附加到 INPUT。它不幸地要放在顶部
-A FILTERS -p udp -m policy --dir in --pol ipsec -m udp --dport 4789 -m set --match-set ip_allow src -j RETURN
-A FILTERS -m state --state RELATED,ESTABLISHED -j ACCEPT
-A FILTERS -p icmp -j ACCEPT
-A FILTERS -i lo -j ACCEPT
#如果取消注释,以下操作系统端口将对所有人开放
-A FILTERS -p tcp -m state --state NEW -m tcp -m multiport --dports 22 -j ACCEPT
#-A FILTERS -p udp -m udp -m multiport --dports 53,123 -j ACCEPT
-A FILTERS -m set ! --match-set ip_allow src -j DROP
-A FILTERS -j RETURN
COMMIT
EOF
使用 iptables-restore 将上述规则添加到 iptables。非常重要的标志是 -n。这确保我们不会在 Docker(或 Docker Swarm)中已有规则的情况下清除 iptables 规则。
iptables-restore -n < ansible_iptables_docker-iptables
接下来,将一条规则添加到 INPUT 链,以便我们开始在 FILTERS 中使用新规则。它必须放在最顶部,只需添加一次:
iptables -I INPUT 1 -j FILTERS
保存 iptables 规则:
/usr/libexec/iptables/iptables.init save
启动并启用 iptables 服务:
systemctl status iptables
systemctl start iptables
systemctl enable iptables
如果你想自定义 iptables 规则以允许更多端口对所有人开放,只需将该端口添加到 iptables 文件中的相应规则(tcp 或 udp),然后重新运行上述相同命令:
iptables-restore -n < ansible_iptables_docker-iptables
/usr/libexec/iptables/iptables.init save
不要忘记上文的 警告!特别是关于 SELinux 的部分。
手动命令(Ubuntu 20.04)
检查你已有的 iptables 规则。记下以防丢失!
Ubuntu 18.04 的设置几乎相同。除了没有 ipset-persistent 软件包,在 Ubuntu 18.04 中省略该软件包并将文件从 files/ubuntu/iptables-persistent*/plugins/*-ipset
复制到 /usr/share/netfilter-persistent/plugins.d/
。
iptables -nvL --line-numbers
安装所需的软件包:
apt install iptables iptables-persistent netfilter-persistent ipset ipset-persistent
使用服务器 IP 及其他受信任的 IP 配置 ipset:
mkdir -p /etc/iptables
cat > /etc/iptables/ipsets << 'EOF'
create -exist ip_allow hash:ip family inet hashsize 1024 maxelem 65536
flush
add ip_allow 192.168.1.123
add ip_allow 192.168.101.0/24
add ip_allow 192.168.102.0/24
EOF
重新加载 ipset:
/usr/sbin/netfilter-persistent reload
查看 ipset 中的加载配置:
ipset list | head
添加的 iptables 规则(默认情况下 22 端口对所有人开放):
使用与 CentOS/RHEL 相同的命令。
使用 iptables-restore 将上述规则添加到 iptables。非常重要的标志是 -n。这确保我们不会在 Docker(或 Docker Swarm)中已有规则的情况下清除 iptables 规则。
iptables-restore -n < ansible_iptables_docker-iptables
接下来,将一条规则添加到 INPUT 链,以便我们开始在 FILTERS 中使用新规则。它必须放在最顶部,只需添加一次:
iptables -I INPUT 1 -j FILTERS
保存 iptables 规则:
/usr/sbin/netfilter-persistent save
启动并启用 iptables 服务:
systemctl status netfilter-persistent
systemctl start netfilter-persistent
systemctl enable netfilter-persistent
如果你想自定义 iptables 规则以允许更多端口对所有人开放,只需将该端口添加到 iptables 文件中的相应规则(tcp 或 udp),然后重新运行上述相同命令:
iptables-restore -n < ansible_iptables_docker-iptables
/usr/sbin/netfilter-persistent save
别忘了上面的 警告!
待办事项
- 检查 firewalld 并在运行或启用时失败
- iptables 保存 Docker 规则存在问题?应该没问题。
- iptables_docker_ip_allow_set 不能为空。如果它为空,那么没有意义,因为什么都没被阻止!
- 在网络适配器中添加 * 的检查并出错
- 在允许列表中自动添加 Docker IP 的列表(使用来自清单组 docker_hosts 的 IP)
- 更改自动 Docker 服务器受信任 IP,以便可以覆盖
- 确认“when”和“tags”是可以的
- Ubuntu?Ubuntu 没有 iptables-services 或 ipset-service。具有 iptables-persistent 和 ipset-?没有 ufw 支持
- ipv6??这是仅适用于 ipv4
- 测试 TCP、UDP Docker 容器和操作系统端口是否工作
- 测试 Docker 容器的出站流量是否正常工作
- 添加测试?Molecule?仅单节点 Swarm 模式?如何测试“非受信任” IP 的连接是否失败?
作者
Ryan Daniels