コンテナを増やしたいのに
1次予選でも出題したコンテナへのポートフォワード問題です。本問題のコンテナはLXCからDockerへ変わりました。コンテナ型の仮想化には基本的にiptablesのラッパーが搭載されています。構成ファイルやコマンドに引数を指定するだけでポートフォワードできるのはこのためです。 今回は特殊な環境でトラブルシューティングを行う想定だったため、素のiptablesとebtablesを触る問題になりました。
問題文
Dockerコンテナでウェブサーバを動かしているVMに、新しくコンテナを追加することになった。 docker run -d -p 8080:80 --name web2 nginx
を実行してコンテナを立ち上げたいのだが、コンテナに疎通が取れないという。 原因を究明して新しく追加されたコンテナに疎通が取れるようにしてほしい。
初期状態
- 手元のPCから
curl 192.168.13.1
を実行すると正常に応答する - VMで
docker start web2
を実行したあと手元のPCからcurl 192.168.13.1:8080
を実行しても正常に応答しない
終了状態
- VMを再起動しても以下が成り立つ
- 手元のPCから
curl 192.168.13.1
を実行すると正常に応答する - VMで
docker start web2
を実行するとVMの8080番ポートがweb2
コンテナの80番ポートへフォワードされ、手元のPCからcurl 192.168.13.1:8080
を実行すると正常に応答する
- 手元のPCから
アクセス情報
VM名 | ホスト名 | ユーザ | パスワード |
---|---|---|---|
VM | 192.168.13.1 | user | PlzComeToICTSC2020 |
解説
docker start web2
をすると特にエラーは返ってこず、正常にコンテナが起動していることはわかるかと思います。
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS
NAMES
cb9075111a78 nginx "nginx -g 'daemon of…" About an hour ago Up 1 second 0.0.0.0:8080->80/tcp
web2
d732a7925959 nginx "nginx -g 'daemon of…" 3 hours ago Up 3 minutes 0.0.0.0:80->80/tcp
web1
そこで iptables
を見ると DOCKER-USER
チェインに以下のルールが入っていることがわかります。
Chain DOCKER-USER (1 references)
target prot opt source destination
ACCEPT all -- anywhere vm
ACCEPT tcp -- anywhere 10.213.213.213 tcp dpt:http
ACCEPT all -- anywhere anywhere ctstate RELATED,ESTABLISHED
DROP all -- anywhere anywhere
RETURN all -- anywhere anywhere
Dockerはiptablesを使ってポートフォワード等を実行しますが DOCKER-USER
はDockerの作ったルールの前に適用されるチェインで、ユーザが利用するためのものです(参考: https://docs.docker.com/network/iptables/)。
これを見ると 10.213.213.213
へのパケット転送以外は基本的に許可されていないことがわかります。 このIPアドレスは web1
コンテナのものです。つまり web1
以外へのパケット転送を許可していないということです。 そこで web2
コンテナにもパケットの転送が許可されるようにルールを編集します。 例えば、以下のようにすればいかなる場合でもパケットの転送が許可されます。
sudo iptables -I DOCKER_USER 1 -j ACCEPT
しかし、これだけでは問題が解決しません。実はebtablesというパケットフィルタがARPパケットを落としているからです。 sudo ebtables -L
とすると以下のルールが見えます。
Bridge table: filter
Bridge chain: INPUT, entries: 4, policy: ACCEPT
-i eth0 -j ACCEPT
-p ARP --arp-ip-src 10.213.213.213 --arp-ip-dst 10.213.213.1 -j ACCEPT
-p ARP --arp-ip-src 10.213.213.1 --arp-ip-dst 10.213.213.213 -j ACCEPT
-p ARP -j DROP
Bridge chain: FORWARD, entries: 0, policy: ACCEPT
Bridge chain: OUTPUT, entries: 4, policy: ACCEPT
-o eth0 -j ACCEPT
-p ARP --arp-ip-src 10.213.213.213 --arp-ip-dst 10.213.213.1 -j ACCEPT
-p ARP --arp-ip-src 10.213.213.1 --arp-ip-dst 10.213.213.213 -j ACCEPT
-p ARP -j DROP
これによると 10.213.213.1
と 10.213.213.213
の間でしかARPのやり取りができないことがわかります。 これは 10.213.213.1
は web1
コンテナが繋がっているブリッジです。
最も手っ取り早い方法としては sudo ebtables -F
としてebtablesのルールを消し去ってしまうことです。
問題の終了条件としては、再起動しても web2
へ疎通が取れる必要がありました。 iptablesとebtablesのルールは通常再起動すれば消えますが、この問題ではそうではありません。
/etc/systemd/system/secure-firewall.service
というサービスがVMの起動時に自動でルールを書き込むようになっています。 このサービスは /opt/secure_firewall.sh
を実行するようになっています。 したがって secure-firewall.service
を無効化するか secure_firewall.sh
を書き換えて、パケットフィルタへの変更が永続化されるようにします。
本来は、既存のiptables/ebtablesの設定にならって、必要最小限の条件を許可するように設定するべきだと考えられますが、この問題では終了状態が達成されるようにルールを編集していればよしとしました。
環境
Dockerコンテナ起動
docker network create -d bridge my-net --gateway 10.213.213.1 --subnet 10.213.213.0/24
docker run --restart=always -p 80:80 --network=my-net --ip=10.213.213.213 --mac-address=02:42:ac:11:00:02 --name web1 nginx
設定永続化のためサービスを定義
/etc/systemd/system/secure-firewall.service
[Unit]
Description=secure firewall
After=docker.service
[Service]
ExecStart=/opt/secure_firewall.sh
[Install]
WantedBy=multi-user.target
sudo systemctl enable secure-firewall
設定スクリプト
/opt/secure_firewall.sh
#!/bin/bash
iptables -I DOCKER-USER 1 -j DROP
iptables -I DOCKER-USER 1 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -I DOCKER-USER 1 -p tcp --dport 80 -d 10.213.213.213 -j ACCEPT
iptables -I DOCKER-USER 1 -d 10.213.213.1 -j ACCEPT
ebtables -A INPUT -i eth0 -j ACCEPT
ebtables -A INPUT -p ARP --arp-ip-src 10.213.213.213 --arp-ip-dst 10.213.213.1 -j ACCEPT
ebtables -A INPUT -p ARP --arp-ip-src 10.213.213.1 --arp-ip-dst 10.213.213.213 -j ACCEPT
ebtables -A INPUT -p ARP -j DROP
ebtables -A OUTPUT -o eth0 -j ACCEPT
ebtables -A OUTPUT -p ARP --arp-ip-src 10.213.213.213 --arp-ip-dst 10.213.213.1 -j ACCEPT
ebtables -A OUTPUT -p ARP --arp-ip-src 10.213.213.1 --arp-ip-dst 10.213.213.213 -j ACCEPT
ebtables -A OUTPUT -p ARP -j DROP
終わりに
皆さんiptablesについては報告してくださっていましたが、ebtablesについては2チームしか発見できていなかったようです。 あまり素で触ることのない技術ですが、楽しんでいただければ幸いです。