Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
48
Help us understand the problem. What are the problem?

More than 3 years have passed since last update.

iptablesでDockerコンテナへのアクセス制限をする

やりたかったこと

  • コンテナをDockerホストの外部に公開するときのアクセス制御設定(ファイアーウォール設定)

やったこと

  • Dockerのポートフォワーディングでコンテナをホスト外部に公開してiptablesでアクセス制限
    • 外部ネットワークにはコンテナ443/tcpのみをアクセスを許可
    • 内部ネットワークにはコンテナのすべてのポートを許可
  • Docker再起動時にiptablesルールを再設定するようにDockerのsystemdで設定

前提

  • Docker 1.12.3
  • CentOS7.2
  • iptables 1.4.21

Dockerのポートフォワーディング設定

Dockerでコンテナをホストの外と通信させたい時、-pオプションでポートフォワーディングを設定すると思いますが、このポートフォワーディングを設定するとiptablesに以下のような設定が追加されます。

$ docker run -p 443:443 -d nginx
$ iptables -nL
~~ 省略 ~~
Chain DOCKER (1 references)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 ACCEPT     tcp  --  !docker0 docker0  0.0.0.0/0            172.17.0.2           tcp dpt:443
$ iptables -t nat -nL
~~ 省略 ~~
Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 MASQUERADE  all  --  *      !docker0  172.17.0.0/16        0.0.0.0/0           
    0     0 MASQUERADE  tcp  --  *      *       172.17.0.2           172.17.0.2           tcp dpt:443

Chain DOCKER (2 references)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 RETURN     all  --  docker0 *       0.0.0.0/0            0.0.0.0/0           
    0     0 DNAT       tcp  --  !docker0 *       0.0.0.0/0            0.0.0.0/0            tcp dpt:443 to:172.17.0.2:443

上の設定ではnginxコンテナの443ポートをホストの443ポートにフォワードして公開してるのですが、これ、ホストでのINPUTの設定にかかわらず、ホストのもつすべてのネットワークに公開されてます。なのでIPやポートで制限を行いたいのです。

iptablesでコンテナへのアクセスを制限する

Docker Docsに以下のような記載があります。

Docker のデフォルト転送ルールは、全ての外部ソースの IP アドレス対して許可しています。コンテナを特定の IP アドレスやネットワークに対してのみ接続したい場合には、 DOCKER フィルタ・チェーンの一番上にネガティブ・ルールを追加します。例えば、コンテナが外部の IP アドレス 8.8.8.8 をソースとするもの しか 許可しない場合には、次のようなルールを追加します。
$ iptables -I DOCKER -i ext_if ! -s 8.8.8.8 -j DROP

というわけで、iptablesのDocker Chainにルールを追加してあげればホスト外からコンテナへのアクセスを制御できます。
うちはリバースプロキシコンテナとAPコンテナが載ってるDockerホストがあって、ホストサーバには外部ネットワークと内部ネットワークが接続されています。そこでリバースプロキシコンテナを外部に公開します。
こんな感じで設定しました。

  • 外部ネットワーク(eth0)には443/tcpから許可
  • 内部ネットワーク(eth1)はすべて許可(明示的な設定はありません)
$ iptables -I DOCKER -i eth0 -j DROP
$ iptables -I DOCKER -i eth0 -p tcp --dport 443 -m state --state NEW -j ACCEPT
Chain DOCKER (1 references)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 ACCEPT     tcp  --  eth0      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:443 state NEW
    0     0 DROP       all  --  eth0      *       0.0.0.0/0            0.0.0.0/0           
    0     0 ACCEPT     tcp  --  !docker0 docker0  0.0.0.0/0            172.17.0.2           tcp dpt:443
    0     0 ACCEPT     tcp  --  !docker0 docker0  0.0.0.0/0            172.17.0.3           tcp dpt:8080

172.17.0.2がリバースプロキシコンテナで、443/tcpを、172.17.0.3がAPコンテナで、8080/tcpを公開してますが、上位のルールで外部ネットワーク(eth0)では443/tcpしか通さないようになっています。

172.17.0.0/16はDockerが設定するコンテナネットワークで、コンテナから外のアクセスは許可しています。

またこの設定はコンテナの443/tcpポートのみへの接続を許可してますが、ポートバインディングでコンテナの443/tcpをホストの別のポートに割り当てた場合も許可の対象です。つまり

$ docker run -p 10443:443 -d nginx

とした場合はホストの10443/tcpポートを経由したコンテナアクセスは許可されます。

Docker起動時のiptables設定

iptablesのDocker Chainは、Docker Engineが動的にiptablesルールを追加するものなので、iptablesサービスを利用している場合、iptablesを再起動するとコンテナのポートフォワーディングのiptablesルールが吹き飛び、ホストの外からコンテナへアクセスできなくなります。
(issueにも上がってます。Docker networking fails after iptables service is restarted #12294

ポートフォワーディングのiptablesを戻すにはDocker Engineの再起動か、自力でルールを書き直すしかありません。なのでiptablesの再起動は厳禁です。

とりあえずコンテナへのアクセス制御ルールはDocker Engineの起動をトリガーに設定したいところ。
そのため、僕の環境ではsystemdでDocker Engineの起動後にiptablesルールを追加しています。(この辺のベストプラクティスがあったら教えてほしいです。)

/etc/systemd/system/docker.service.d/docker.conf
[Service]
ExecStart=
ExecStart=/usr/bin/dockerd $OPTIONS
ExecStartPost=/sbin/iptables -I DOCKER -i eth0 -j DROP
ExecStartPost=/sbin/iptables -I DOCKER -i eth0 -p tcp --dport 80 -m state --state NEW -j ACCEPT

他のアプローチ

自分でDocker用のiptables設定をすべて書く

Docker Engineの起動オプションで--iptables=falseを指定してあげれば、Dockerはiptablesを全く書き換えません。そのため、自分でiptablesのルールを書いてあげればより柔軟に高度なアクセス制限ができます。
しかしその反面、いままでdocker run-pオプションがやってくれたポートフォワーディングのルールも自分で書かなければいけないのでかなり面倒です。

--net=hostでコンテナをホストのネットワークに直接繋いで、ホストのiptablesで制御する

デフォルトだとコンテナはdocker0というブリッジネットワークに接続しますが、オプションでホストのネットワークに直接つなぐことができます。しかし使用するポートは全てホストのものを使うため、1つのコンテナでポートをたくさん使うようなものや、ホストにたくさんコンテナを立てる場合はポートが不足する可能性もあります。また、同じコンテナを複数立てる場合はコンテナ内のミドルウェアの設定でポートを競合しないように変えなくてはならないので大変です。

おわりに

これ書いてて、そもそもちゃんとしたサービスを公開する時はだいたい前段にそれなりのファイアーウォール機器やロードバランサをいれるから、Dockerホストでアクセス制限考えることは少ないからあんまり意味ないな、とも思いました。

Dockerにおけるiptables設定の知見ってあんまり見ない気がしますが、どうなんでしょう。それかdocker networkでうまく設定しろよってことなんですかね。

追記(2016/12/6)

docker run-pオプションで、公開するIPアドレスを指定できることをすっかり見落としてました…。こんな感じでコンテナ作れば公開するネットワークは1つに制限できますね。

$ docker run -p 192.168.11.5:443:443 -d nginx
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
48
Help us understand the problem. What are the problem?