使用iptables管控docker容器


docker与iptables说明


某些项目考虑到安全问题,需要启用iptables来进行加固。根据官方文档介绍(https://dockerdocs.cn/network/iptables/):

在Linux上,Docker操纵iptables规则以提供网络隔离。尽管这是实现的详细信息,并且您不应修改Docker在iptables策略中插入的规则,但是如果您想要拥有自己的策略(而不是由Docker管理的策略),它确实会对您需要执行的操作产生一些影响。

如果您在暴露于Internet的主机上运行Docker,则可能需要适当的iptables策略,以防止未经授权访问您主机上运行的容器或其他服务。此页面描述了如何实现此目标以及需要注意的注意事项。

Docker安装了两个名为DOCKER-USER和的自定义iptables链DOCKER,它确保始终由这两个链首先检查传入的数据包。

Docker的所有iptables规则都已添加到DOCKER链中。请勿手动操作此链条。如果您需要添加在Docker规则之前加载的规则,则将它们添加到DOCKER-USER链中。在Docker自动创建任何规则之前,将应用这些规则。

在这些链之后,FORWARD将评估添加到链中的规则(手动添加或通过另一个基于iptables的防火墙)。这意味着,如果您通过Docker公开端口,则无论防火墙配置了什么规则,该端口都会公开。如果您希望即使在通过Docker公开端口时也要应用这些规则,则必须将这些规则添加到 链中。DOCKER-USER

通过如上资料已经确定,只需要在 DOCKER-USER 链上进行改动即可。


iptables规则设置


注意:设定规则时,请带上 -i [网卡名] ,否则会发生误差的情况。


规则设定


默认情况下,允许所有外部源IP连接到docker主机。查看DOCKER-USER

root@localhost(192.168.199.41)~>iptables -nvL

Chain INPUT (policy ACCEPT 71 packets, 5105 bytes)
 pkts bytes target     prot opt in     out     source               destination

Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination
    0     0 DOCKER-USER  all  --  *      *       0.0.0.0/0            0.0.0.0/0
    0     0 DOCKER-ISOLATION-STAGE-1  all  --  *      *       0.0.0.0/0            0.0.0.0/0
    0     0 ACCEPT     all  --  *      docker0  0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
    0     0 DOCKER     all  --  *      docker0  0.0.0.0/0            0.0.0.0/0
    0     0 ACCEPT     all  --  docker0 !docker0  0.0.0.0/0            0.0.0.0/0
    0     0 ACCEPT     all  --  docker0 docker0  0.0.0.0/0            0.0.0.0/0

Chain OUTPUT (policy ACCEPT 33 packets, 4924 bytes)
 pkts bytes target     prot opt in     out     source               destination

Chain DOCKER (1 references)
 pkts bytes target     prot opt in     out     source               destination

Chain DOCKER-ISOLATION-STAGE-1 (1 references)
 pkts bytes target     prot opt in     out     source               destination
    0     0 DOCKER-ISOLATION-STAGE-2  all  --  docker0 !docker0  0.0.0.0/0            0.0.0.0/0
    0     0 RETURN     all  --  *      *       0.0.0.0/0            0.0.0.0/0

Chain DOCKER-ISOLATION-STAGE-2 (1 references)
 pkts bytes target     prot opt in     out     source               destination
    0     0 DROP       all  --  *      docker0  0.0.0.0/0            0.0.0.0/0
    0     0 RETURN     all  --  *      *       0.0.0.0/0            0.0.0.0/0

Chain DOCKER-USER (1 references)	#重点关注该链
 pkts bytes target     prot opt in     out     source               destination
    0     0 RETURN     all  --  *      *       0.0.0.0/0            0.0.0.0/0

禁止访问docker

既然要做限制,第一条肯定是全部禁用,然后再添加需要放开的IP及端口。


#禁止外部访问docker容器
root@localhost(192.168.199.41)~>iptables -I DOCKER-USER -i eth0 -j REJECT
root@localhost(192.168.199.41)~>iptables -nvL DOCKER-USER
Chain DOCKER-USER (1 references)
 pkts bytes target     prot opt in     out     source               destination
    0     0 REJECT     all  --  eth0   *       0.0.0.0/0            0.0.0.0/0            reject-with icmp-port-unreachable
    0     0 RETURN     all  --  *      *       0.0.0.0/0            0.0.0.0/0

启动一个容器

root@localhost(192.168.199.41)~>docker run -d --name  ngx -p 80:80 nginx:alpine

尝试从外部访问

root@localhost(192.168.199.108)~>curl 192.168.199.41
curl: (7) Failed connect to 192.168.199.41:80; Connection refused

开启192.168.199.108访问容器80端口

root@localhost(192.168.199.41)~>iptables -R DOCKER-USER 1 -i eth0 -s 192.168.199.108 -p tcp --dport 80 -j ACCEPT
root@localhost(192.168.199.41)~>iptables -nvL DOCKER-USER --line-number
Chain DOCKER-USER (1 references)
num   pkts bytes target     prot opt in     out     source               destination
1        0     0 ACCEPT     tcp  --  eth0   *       192.168.199.108      0.0.0.0/0            tcp dpt:80
2       32  1472 REJECT     all  --  eth0   *       0.0.0.0/0            0.0.0.0/0            reject-with icmp-port-unreachable
3       24  3170 RETURN     all  --  *      *       0.0.0.0/0            0.0.0.0/0

再次尝试从192.168.199.108访问

root@localhost(192.168.199.108)~>curl 192.168.199.41
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
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>

访问成功。


参考规则


仅特定IP访问容器,其他全部禁止。


iptables -I DOCKER-USER -i eth0 ! -s 192.168.199.108 -j DROP

开放一段ip及端口访问(开放192.168.199.1-192.168.199.10可以访问80-90及8080-9090端口)

iptables -I DOCKER-USER -i eth0 -j REJECT
iptables -I DOCKER-USER -m iprange -m multiport -i eth0 -p tcp --src-range 192.168.199.1-192.168.199.10 --dport 80:90,8080:9090 -j ACCEPT

实际案例-1


环境说明


操作系统:CentOS Linux release 7.9.2009 (Core)
docker版本:26.1.0
使用docker-compose启动的容器:nginx、tomcat、mysql

docker-compose ps -a
NAME      IMAGE          COMMAND                  SERVICE   CREATED          STATUS          PORTS
mysql     mysql:5.7.18   "docker-entrypoint.s…"   mysql     11 minutes ago   Up 11 minutes   3306/tcp
nginx     nginx:alpine   "/docker-entrypoint.…"   nginx     5 minutes ago    Up 5 minutes    0.0.0.0:80->80/tcp
tomcat    tomcat         "catalina.sh run"        tomcat    11 minutes ago   Up 11 minutes   0.0.0.0:8080->8080/tcp

注意:每个容器对外都有映射端口。


安装iptables服务

yum install iptables-services -y

启动并保存配置

systemctl enable --now iptables
service iptables save #保存配置

配置规则

接下来,进行规则的配置。首先要考虑不要影响业务的前提下将规则配置好。

需要做以下限制:

  1. mysql不允许任何人在外部访问,仅允许 tomcat 访问;
  2. tomcat 只允许192.168.199.108访问;
  3. nginx开放给所有人访问。

第一条:mysql不允许任何人在外部访问,仅允许 tomcat 访问

分析:这里服务所指的是服务所在容器,每个容器都有IP地址,我们也知道容器的IP是随时都有可能发生变化(例如:mysql容器重建,则会生成一个不同IP的容器),这里就需要在启动时给定容器IP,然后通过 iptables 来对容器IP进行限制即可达到目的。

第一步:启动时,绑定IP

more docker-compose.yml
services:
  mysql:
    container_name: mysql
    image: mysql:5.7.18
    environment:
    - MYSQL_ROOT_PASSWORD=123123
    - MYSQL_ROOT_HOST=%
    - TZ=Asia/Shanghai
    restart: always
    volumes:
    - ./data:/var/lib/mysql
    networks:
      test_net:
        ipv4_address: 172.100.0.2	#为mysql绑定IP

  tomcat:
    container_name: tomcat
    image: tomcat
    restart: always
    ports:
    - 8080:8080
    networks:
      test_net:
        ipv4_address: 172.100.0.3	#为tomcat绑定IP

  nginx:
    container_name: nginx
    image: nginx:alpine
    restart: always
    ports:
    - 80:80
    networks:
    - test_net

networks:
  test_net:
    name: test_net
    driver: bridge
    ipam:
      config:
      - subnet: "172.100.0.0/16"

第二步:已知 mysqltomcat 的IP以后,就可以定制 iptables 规则

iptables -I DOCKER-USER ! -s 172.100.0.3 -d 172.100.0.2 -p tcp --dport 3306 -j REJECT

--仅允许tomcat(172.100.0.3)访问mysql(172.100.0.2) 的 3306端口,其他访问全部禁止,为了测试方便,我这里直接使用 REJECT

查看 iptables规则列表
iptables -nvL DOCKER-USER --line-numbers
Chain DOCKER-USER (1 references)
num   pkts bytes target     prot opt in     out     source               destination
1        0     0 REJECT     tcp  --  *      *      !172.100.0.3          172.100.0.2          tcp dpt:3306 reject-with icmp-port-unreachable
2    13049   22M RETURN     all  --  *      *       0.0.0.0/0            0.0.0.0/0

第三步:进入tomcat 容器 和 nginx容器进行 telnet mysql 3306 进行测试。

docker exec -it nginx sh
/ # ping -c1 mysql
PING mysql (172.100.0.2): 56 data bytes
64 bytes from 172.100.0.2: seq=0 ttl=64 time=0.360 ms

--- mysql ping statistics ---
1 packets transmitted, 1 packets received, 0% packet loss
round-trip min/avg/max = 0.360/0.360/0.360 ms
/ # telnet mysql 3306
telnet: can't connect to remote host (172.100.0.2): Connection refused

--- 进入 nginx 容器 telnet mysql容器3306端口失败,符合预期。---

docker exec -it tomcat bash
root@4b57c5896221:/usr/local/tomcat# ping -c1 mysql
PING mysql (172.100.0.2) 56(84) bytes of data.
64 bytes from mysql.test_net (172.100.0.2): icmp_seq=1 ttl=64 time=0.304 ms

--- mysql ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.304/0.304/0.304/0.000 ms
root@4b57c5896221:/usr/local/tomcat# telnet mysql 3306
Trying 172.100.0.2...
Connected to mysql.
Escape character is '^]'.
J
5.7.18U%yMYk:
             @C%(U^K_mysql_native_password


--- 进入 tomcat 容器 telnet mysql容器3306端口连接成功,符合预期。 ---

到此,第一个要求已经完成了。


第二条:tomcat 只允许192.168.199.108访问;

第一步:需要获得tomcat容器的IP才能做 iptables 规则限制

通过上面的操作,已知tomcat IP:172.100.0.3

第二步:定制规则

iptables -I DOCKER-USER ! -s 192.168.199.108 -d 172.100.0.3 -p tcp --dport 8080 -j REJECT
iptables -nvL DOCKER-USER --line-numbers

Chain DOCKER-USER (1 references)
num   pkts bytes target     prot opt in     out     source               destination
1        0     0 REJECT     tcp  --  *      *      !192.168.199.108      172.100.0.3          tcp dpt:8080 reject-with icmp-port-unreachable
2        2   120 REJECT     tcp  --  *      *      !172.100.0.3          172.100.0.2          tcp dpt:3306 reject-with icmp-port-unreachable
3    13069   22M RETURN     all  --  *      *       0.0.0.0/0            0.0.0.0/0

第三步:进行测试

root@localhost(192.168.199.108)~>curl -I 192.168.199.41:8080
HTTP/1.1 200
Content-Type: text/html;charset=UTF-8
Transfer-Encoding: chunked
Date: Wed, 24 Jul 2024 11:17:05 GMT

root@shate(192.168.199.10)~>curl -I 192.168.199.41:8080
curl: (7) Failed connect to 192.168.199.41:8080; Connection refused

--- 测试结果:108访问正常,10访问被拒绝,符合预期 ---

到此,第二个要求已经完成了。


第三条:nginx开放给所有人访问。

因为默认是全部放开,所有第三条规则原本就满足。


上面的示例只是为了演示,如何在单机当中对容器做限制,如果使用网络插件或者名称空间的方式会更优雅。


实际案例-2


在某些严格的环境中,单台服务器作为一个单位来进行管控,粒度并没有到容器的级别,如果物理服务器使用了容器的方式,则可以通过 iptablesDOCKER-USER 链来进行管控。

例如:对于192.168.199.41服务器,仅开放22 、80、3306、8080 端口,其他全部禁用。


问题分析: 22端口作为ssh服务,都是在本地直接进行开放,然后 80、3306、8080都以容器的方式运行,接下来实操:

第一步:在 INTPUT 链添加 目标端口22、80、3306、8080 的允许,否则就被关在外面,无法连接服务器了!!!

iptables -R INPUT 1 -m multiport -p tcp --dport 22,80,3306,8080 -j ACCEPT
iptables -nvL INPUT
Chain INPUT (policy DROP 6 packets, 539 bytes)
 pkts bytes target     prot opt in     out     source               destination
   38  2808 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            multiport dports 22,80,3306,8080

iptables -nvL INPUT
Chain INPUT (policy ACCEPT 137 packets, 13238 bytes)
 pkts bytes target     prot opt in     out     source               destination
  272 18784 ACCEPT     tcp  --  eth0   *       0.0.0.0/0            0.0.0.0/0            tcp dpt:22

第二步:设置 INPUT 默认规则为 DROP

iptables -P INPUT DROP

第三步:启动一个没有做管控的容器访问测试

docker run --name ngx-1 -d -p 8888:80 nginx:alpine
--- 启动一个nginx容器,端口映射为 8888 ,很明显 8888 并没有在规则限制中 ---

在别的主机中访问 http://192.168.199.41:8888

root@localhost(192.168.199.108)~>curl -I http://192.168.199.41:8888
HTTP/1.1 200 OK
Server: nginx/1.21.5
Date: Wed, 24 Jul 2024 11:42:43 GMT
Content-Type: text/html
Content-Length: 615
Last-Modified: Tue, 28 Dec 2021 18:48:00 GMT
Connection: keep-alive
ETag: "61cb5be0-267"
Accept-Ranges: bytes

由此得知,容器并没有走 INPUT 链,如果要对容器进行管控必须在 DOCKER-USER链进行规则设定!

因此,修改 第一步 规则,仅限制 22端口

iptables -R INPUT 1  -p tcp --dport 22 -j ACCEPT
iptables -nvL INPUT --line-numbers

Chain INPUT (policy DROP 8 packets, 846 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1       36  2692 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:22

再次,在 DOCKER-USER 中,对容器进行限制。

### 1.设置默认规则为:REJECT ###
iptables -I DOCKER-USER -i eth0 -j REJECT
iptables -nvL DOCKER-USER --line-numbers
Chain DOCKER-USER (1 references)
num   pkts bytes target     prot opt in     out     source               destination
1        0     0 REJECT     all  --  eth0   *       0.0.0.0/0            0.0.0.0/0            reject-with icmp-port-unreachable
2    13253   22M RETURN     all  --  *      *       0.0.0.0/0            0.0.0.0/0


### 2.开放80、8080、3306端口 ###
iptables -I DOCKER-USER -i eth0 -m multiport -p tcp --dport 80,3306,8080 -j ACCEPT
iptables -nvL DOCKER-USER --line-numbers
Chain DOCKER-USER (1 references)
num   pkts bytes target     prot opt in     out     source               destination
1       48 11070 ACCEPT     tcp  --  eth0   *       0.0.0.0/0            0.0.0.0/0            multiport dports 80,3306,8080
2       68  6657 REJECT     all  --  eth0   *       0.0.0.0/0            0.0.0.0/0            reject-with icmp-port-unreachable
3    13294   22M RETURN     all  --  *      *       0.0.0.0/0            0.0.0.0/0

进行测试

root@localhost(192.168.199.108)~>curl -I http://192.168.199.41:80
HTTP/1.1 200 OK
Server: nginx/1.21.5
Date: Wed, 24 Jul 2024 13:42:57 GMT
Content-Type: text/html
Content-Length: 615
Last-Modified: Tue, 28 Dec 2021 18:48:00 GMT
Connection: keep-alive
ETag: "61cb5be0-267"
Accept-Ranges: bytes

root@localhost(192.168.199.108)~>curl -I http://192.168.199.41:8080
HTTP/1.1 200
Content-Type: text/html;charset=UTF-8
Transfer-Encoding: chunked
Date: Wed, 24 Jul 2024 13:43:01 GMT

root@localhost(192.168.199.108)~>curl -I http://192.168.199.41:8888 # 为什么没有开放的 8888 也可以访问到?
HTTP/1.1 200 OK
Server: nginx/1.21.5
Date: Wed, 24 Jul 2024 13:43:05 GMT
Content-Type: text/html
Content-Length: 615
Last-Modified: Tue, 28 Dec 2021 18:48:00 GMT
Connection: keep-alive
ETag: "61cb5be0-267"
Accept-Ranges: bytes

注意:这里为什么没有开放的 8888 也可以被访问到?

启动这个容器时的命令为:
docker run --name ngx-1 -d -p 8888:80 nginx:alpine
注意这个端口映射 容器内80 -> 物理机 8888 端口,这里提出质疑,我们在 DOCKER-USER 添加规则是否只是限制容器内端口,而无法限制映射到物理机的端口?

修改该 nginx 的配置文件,使其启动为容器后也监听到 8888 端口

cat default.conf
server {
    listen       8888;
    server_name  localhost;

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }
}



启动容器并映射该配置文件
root@localhost(192.168.199.41)/root>docker rm -f ngx-1
ngx-1
root@localhost(192.168.199.41)/root>docker run --name ngx-1 -d -v /root/default.conf:/etc/nginx/conf.d/default.conf -p 8888:8888 nginx:alpine

本地测试及远程主机测试

### 本地测试 ###
root@localhost(192.168.199.41)/root>curl -I  192.168.199.41:8888
HTTP/1.1 200 OK
Server: nginx/1.21.5
Date: Wed, 24 Jul 2024 13:51:21 GMT
Content-Type: text/html
Content-Length: 615
Last-Modified: Tue, 28 Dec 2021 18:48:00 GMT
Connection: keep-alive
ETag: "61cb5be0-267"
Accept-Ranges: bytes

### 远程主机测试 ###
root@localhost(192.168.199.108)~>curl -I  192.168.199.41:8888
curl: (7) Failed connect to 192.168.199.41:8888; Connection refused

由此验证了我们的猜想,DOCKER-USER仅是对容器内监听端口进行管控,而不会对映射后的端口进行控制。



--- EOF ---
posted @ 2024-07-24 14:04  hukey  阅读(67)  评论(0编辑  收藏  举报