使用iptables管控docker容器

hukey發表於2024-07-24

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 ---

相關文章