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

关于项目

Manage iptables configuration to secure Docker (including Docker Swarm).

安装
ansible-galaxy install ryandaniels.iptables_docker
许可证
mit
下载
2.7k
拥有者
Ansible all the things