はじめに
アプリを Docker コンテナ内で実行することで実行環境が隔離されるため、 アプリに脆弱性があった場合等にサーバーが乗っ取られるリスクを下げることが出来ます。 しかし、コンテナが乗っ取られた場合、ネットワークを介して他のコンテナやサーバーに攻撃が及ぶかもしれません。
今回はコンテナが乗っ取られた場合でも、他のコンテナやサーバーに攻撃が届かないようにコンテナ外向きの通信を制限する方法について書こうと思います。
あくまでも、こんな感じにやったらいい感じじゃない?という話なので、 このやり方を採用する場合は自己責任でお願いします。
ネットワークタイプについて
参考: Networking overview | Docker Documentation
Docker のネットワークは
- host
- bridge
- none
- overlay
- macvlan
の 5 種類ありますが、通常使われるのは host と bridge のどちらかだと思います(それ以外のタイプは使ったことがないです)。 host だとコンテナの通信がホスト側の通信と見分けがつかなくなるので、今回は bridge を使います。
サーバーに複数の NIC があり、コンテナ用に NIC を割り当てられる場合は host でもいけるとは思いますが、 今回はそのことについては言及しません。
Docker による iptables 操作を無効化
参考: Docker and iptables | Docker Documentation
通常、Docker はコンテナが通信出来るようにするために iptables にルールを勝手に追加します。 今回はその挙動が邪魔になるため、あらかじめ無効化しておきます。
cat << EOS | sudo tee /etc/docker/daemon.json { "iptables": false } EOS sudo chmod 600 /etc/docker/daemon.json sudo systemctl restart docker
br_netfilter モジュールをロード
ブリッジ内の通信に iptables のフィルタを適用させるために br_netfilter モジュールをロードします。 コンテナ間の通信をこれで制御します。
再起動時に自動でロードするようにするため、/etc/modules-load.d/modules.conf
に br_netfilter
の一行を追加しておきます。
sudo modprobe br_netfilter sudo bash -c 'echo "br_netfilter" >> /etc/modules-load.d/modules.conf'
iptables のルール設定
基本の設定
SSH 接続が出来るだけの基本的な設定は次のようになります。
sudo bash << EOS # 設定リセット iptables -t filter -F iptables -t filter -X iptables -t nat -F iptables -t nat -X # ポリシー設定 iptables -t filter -P INPUT DROP iptables -t filter -P OUTPUT ACCEPT iptables -t filter -P FORWARD ACCEPT # RELATED, ESTABLISHED なものは通す iptables -t filter -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT # ローカルからのパケットを通す iptables -t filter -A INPUT -i lo -j ACCEPT # SSH 接続用 iptables -t filter -A INPUT -p tcp --dport 22 -j ACCEPT EOS
コンテナが外部と通信できるようにする
上のルールだと、コンテナから外部にパケットが出ていけないので、出ていけるようにルールを追加します。
docker0
はデフォルトで作られる bridge ネットワークのインターフェース名です(ネットワーク名は bridge です)。
docker0
のサブネットはデフォルトだと 172.17.0.0/16
なので、そこから docker0
以外に行くパケットに対してマスカレードでアドレス変換をかけます。
-i docker0
としたいところですが、POSTROUTING チェーンでは -i
オプションが使えないのでサブネットで指定します。
sudo bash << EOS # 設定リセット iptables -t filter -F iptables -t filter -X iptables -t nat -F iptables -t nat -X # ポリシー設定 iptables -t filter -P INPUT DROP iptables -t filter -P OUTPUT ACCEPT iptables -t filter -P FORWARD ACCEPT # RELATED, ESTABLISHED なものは通す iptables -t filter -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT # ローカルからのパケットを通す iptables -t filter -A INPUT -i lo -j ACCEPT # SSH 接続用 iptables -t filter -A INPUT -p tcp --dport 22 -j ACCEPT # コンテナからのパケットが外に出るためのルール iptables -t nat -A POSTROUTING ! -o docker0 -s 172.17.0.0/16 -j MASQUERADE EOS
ping で外に出れるか確認してみます。
$ docker run -it --rm centos:8 /bin/ping -c 5 8.8.8.8 PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data. 64 bytes from 8.8.8.8: icmp_seq=1 ttl=115 time=7.18 ms 64 bytes from 8.8.8.8: icmp_seq=2 ttl=115 time=6.51 ms 64 bytes from 8.8.8.8: icmp_seq=3 ttl=115 time=6.84 ms 64 bytes from 8.8.8.8: icmp_seq=4 ttl=115 time=6.28 ms 64 bytes from 8.8.8.8: icmp_seq=5 ttl=115 time=6.23 ms --- 8.8.8.8 ping statistics --- 5 packets transmitted, 5 received, 0% packet loss, time 10ms rtt min/avg/max/mdev = 6.226/6.608/7.182/0.366 ms
プライベートアドレスへの通信を遮断する
docker0
から プライベートアドレス(10.0.0.0/8
, 172.16.0.0/12
, 192.168.0.0/16
)への通信を遮断するルールを追加します。
bridge のネットワークもプライベートアドレスの範囲内なので、このルールによってコンテナ間の通信も遮断されます(ただし br_netfilter モジュールが必要)。
sudo bash << EOS # 設定リセット iptables -t filter -F iptables -t filter -X iptables -t nat -F iptables -t nat -X # ポリシー設定 iptables -t filter -P INPUT DROP iptables -t filter -P OUTPUT ACCEPT iptables -t filter -P FORWARD ACCEPT # RELATED, ESTABLISHED なものは通す iptables -t filter -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT # ローカルからのパケットを通す iptables -t filter -A INPUT -i lo -j ACCEPT # SSH 接続用 iptables -t filter -A INPUT -p tcp --dport 22 -j ACCEPT # コンテナからのパケットが外に出るためのルール iptables -t nat -A POSTROUTING ! -o docker0 -s 172.17.0.0/16 -j MASQUERADE # コンテナからプライベートアドレス向けのパケットを遮断するルール iptables -t filter -A FORWARD -i docker0 -d 10.0.0.0/8,172.16.0.0/12,192.168.0.0/16 -j DROP EOS
プライベートアドレスへの ping が通らないことを確認します。
$ docker run -it --rm centos:8 /bin/ping -c 5 192.168.1.1 PING 192.168.1.1 (192.168.1.1) 56(84) bytes of data. --- 192.168.1.1 ping statistics --- 5 packets transmitted, 0 received, 100% packet loss, time 83ms