記憶力が無い

プログラミングと室内園芸と何か

Docker コンテナの外向きの通信を制限する

はじめに

アプリを 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.confbr_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
Copyright © 2017 ttk1