iptablesでdockerコンテナへのアクセスを制限するときはDOCKER-USER chainを使う

iptablesを使っている環境でdockerコンテナを立ち上げたら、アクセス許可をするルールを追加してないのに外部からアクセスできてしまうという出来事があった。調べてみたらこのドキュメントを見つけた。

Docker and iptables | Docker Documentation

By default, all external source IPs are allowed to connect to the Docker host. To allow only a specific IP or network to access the containers, insert a negated rule at the top of the DOCKER-USER filter chain.

dockerによって外部IPからのアクセスを全て許可するようにルールが自動で追加されるということだ。 例えばiptablesを使っている環境でnginxのコンテナを立ち上げると、dockerによって外部IPからアクセスできるようにDOCKERというchainにルールが自動追加される。 もしアクセス制限をかけたい場合はDOCKER-USERというchainにルールを追加する必要がある。

実際に試してみる

VagrantでCentOS7な環境を用意してそこでnginxコンテナを起動してみる。

nginxコンテナを起動すると自動でルールが追加されている

コンテナを起動していない状態ではDOCKERchainには何も追加されていない。

[vagrant@centos7 ~]$ sudo docker ps
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES
[vagrant@centos7 ~]$
[vagrant@centos7 ~]$ sudo iptables -L DOCKER
Chain DOCKER (1 references)
target     prot opt source               destination
[vagrant@centos7 ~]$

nginxのコンテナを起動してみる。

[vagrant@centos7 ~]$ sudo docker run -d --rm --name nginx -p 80:80 nginx:latest
ed01522e7ee85681bf2a6271576613200947df778f4af4ba8df3180b4966423c
[vagrant@centos7 ~]$
[vagrant@centos7 ~]$ sudo docker ps
CONTAINER ID   IMAGE          COMMAND                  CREATED         STATUS         PORTS                NAMES
ed01522e7ee8   nginx:latest   "/docker-entrypoint.…"   3 seconds ago   Up 2 seconds   0.0.0.0:80->80/tcp   nginx
[vagrant@centos7 ~]$

するとDOCKERchainに172.17.0.2:80宛のアクセスを許可するルールが追加されている。 ちなみに172.17.0.2はコンテナに割り当てられているIPアドレス。 natテーブルをみると、PREROUTINGchainから転送される形でDOCKERchainにホスト(Vagrant環境)の80ポート宛のアクセスを172.17.0.2:80宛にnatするためのルールが追加されていることがわかる。

[vagrant@centos7 ~]$ sudo iptables -L DOCKER
Chain DOCKER (1 references)
target     prot opt source               destination
ACCEPT     tcp  --  anywhere             172.17.0.2           tcp dpt:http
[vagrant@centos7 ~]$
[vagrant@centos7 ~]$ sudo iptables -L -t nat
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination
PREROUTING_direct  all  --  anywhere             anywhere
PREROUTING_ZONES_SOURCE  all  --  anywhere             anywhere
PREROUTING_ZONES  all  --  anywhere             anywhere
DOCKER     all  --  anywhere             anywhere             ADDRTYPE match dst-type LOCAL

...

Chain DOCKER (2 references)
target     prot opt source               destination
RETURN     all  --  anywhere             anywhere
DNAT       tcp  --  anywhere             anywhere             tcp dpt:http to:172.17.0.2:80

...

[vagrant@centos7 ~]$
[vagrant@centos7 ~]$ sudo docker network inspect bridge
[
    {
...
        "Containers": {
            "ed01522e7ee85681bf2a6271576613200947df778f4af4ba8df3180b4966423c": {
                "Name": "nginx",
                "EndpointID": "c5e4775c43de4e80355a24a4f1be4b7fc91969236fcf53dbb0bdb47054f764d6",
                "MacAddress": "02:42:ac:11:00:02",
                "IPv4Address": "172.17.0.2/16",
                "IPv6Address": ""
            }
...
    }
]
[vagrant@centos7 ~]$

この状態でホスト(Vagrant環境)の外からcurlを実行すると200が返ってくる。

$ curl --head localhost
HTTP/1.1 200 OK
Server: nginx/1.21.5
Date: Fri, 31 Dec 2021 06:21:54 GMT
Content-Type: text/html
Content-Length: 615
Last-Modified: Tue, 28 Dec 2021 15:28:38 GMT
Connection: keep-alive
ETag: "61cb2d26-267"
Accept-Ranges: bytes

INPUTchainにルールを入れても制限はできない

PREROUTINGにnatするためのルールが追加されているので、INPUT chainにDROPするルールを追加しても効果はなく外からアクセスできてしまう。

[vagrant@centos7 ~]$ sudo iptables -I INPUT -p tcp -m tcp --dport 80 -j DROP
[vagrant@centos7 ~]$
[vagrant@centos7 ~]$ sudo iptables -L INPUT
Chain INPUT (policy ACCEPT)
target     prot opt source               destination
DROP       tcp  --  anywhere             anywhere             tcp dpt:http
ACCEPT     all  --  anywhere             anywhere             ctstate RELATED,ESTABLISHED
ACCEPT     all  --  anywhere             anywhere

...
$ curl localhost
HTTP/1.1 200 OK
Server: nginx/1.21.5
Date: Fri, 31 Dec 2021 06:35:46 GMT
Content-Type: text/html
Content-Length: 615
Last-Modified: Tue, 28 Dec 2021 15:28:38 GMT
Connection: keep-alive
ETag: "61cb2d26-267"
Accept-Ranges: bytes

DOCKER-USERというchainにルールを挿入すれば制限をかけられる

冒頭に書いたとおり、アクセス制限をかけたいときはDOCKER-USERというchainにルールを挿入すればOK。

何もルールを追加していない状態だとこうなっている。 FORWARDchainの先頭にDOCKER-USERchainが挿入されている。

[vagrant@centos7 ~]$ sudo iptables -L DOCKER-USER
Chain DOCKER-USER (1 references)
target     prot opt source               destination
RETURN     all  --  anywhere             anywhere
[vagrant@centos7 ~]$
[vagrant@centos7 ~]$
[vagrant@centos7 ~]$ sudo iptables -L FORWARD
Chain FORWARD (policy DROP)
target     prot opt source               destination
DOCKER-USER  all  --  anywhere             anywhere
DOCKER-ISOLATION-STAGE-1  all  --  anywhere             anywhere
ACCEPT     all  --  anywhere             anywhere             ctstate RELATED,ESTABLISHED
DOCKER     all  --  anywhere             anywhere
ACCEPT     all  --  anywhere             anywhere
ACCEPT     all  --  anywhere             anywhere
ACCEPT     all  --  anywhere             anywhere             ctstate RELATED,ESTABLISHED
ACCEPT     all  --  anywhere             anywhere
FORWARD_direct  all  --  anywhere             anywhere
FORWARD_IN_ZONES_SOURCE  all  --  anywhere             anywhere
FORWARD_IN_ZONES  all  --  anywhere             anywhere
FORWARD_OUT_ZONES_SOURCE  all  --  anywhere             anywhere
FORWARD_OUT_ZONES  all  --  anywhere             anywhere
DROP       all  --  anywhere             anywhere             ctstate INVALID
REJECT     all  --  anywhere             anywhere             reject-with icmp-host-prohibited
[vagrant@centos7 ~]$

DOCKER-USERに80ポートへのアクセスをDROPするルールを追加する。

[vagrant@centos7 ~]$ sudo iptables -I DOCKER-USER -p tcp -m tcp --dport 80 -j DROP
[vagrant@centos7 ~]$
[vagrant@centos7 ~]$ sudo iptables -L DOCKER-USER
Chain DOCKER-USER (1 references)
target     prot opt source               destination
DROP       tcp  --  anywhere             anywhere             tcp dpt:http
RETURN     all  --  anywhere             anywhere
[vagrant@centos7 ~]$

アクセス制限をかけられていることを確認できた。

$ curl --head localhost
curl: (56) Recv failure: Connection reset by peer

ちなみにDOCKER-USERSchainではなくFORWARDchainにルールを追加しても同じことができる。 でもFORWARDchainに直接書くと傍から見たらdocker用のアクセス制限であることがわかりにくいので、ドキュメントどおりにDOCKER-USERchainにルールを足したほうが良いだろう。


というわけで、iptablesを使っている環境でコンテナを立ち上げる場合は、DOCKER-USERchainにアクセス制限をかけるためのルールを追加することをお忘れなく。