web-dev-qa-db-ja.com

Ubuntuでのdocker + ufwのベストプラクティスは何ですか

Dockerを試しました。それは素晴らしいですが、ufwでうまく動作しないようです。デフォルトでは、dockerはiptablesを少し操作します。結果はバグではなく、私が期待したものではありません。詳細については、 FW + Dockerの危険性 をご覧ください。

私の目標は、次のようなシステムをセットアップすることです

    Host (running ufw) -> docker container 1 - nginx (as a reverse proxy)
                       -> docker container 2 - node web 1
                       -> docker container 3 - node web 2
                       -> .......

Ufwを介して着信トラフィックを管理(アクセスの制限など)したいので、ドッカーがiptablesに触れないようにします。これが私のテストです

環境:

  • 新しくインストールされたUbuntu 14.04(カーネル:3.13.0-53)
  • Docker 1.6.2
  • ufw転送が有効になっています。( FW転送を有効にする
  • --iptables=falseがDockerデーモンに追加されました。

最初の試み

docker run --name ghost -v /home/xxxx/ghost_content:/var/lib/ghost -d ghost
docker run --name nginx -p 80:80 -v /home/xxxx/nginx_site_enable:/etc/nginx/conf.d:ro --link ghost:ghost -d nginx

運がありません。最初のコマンドは問題ありませんが、2番目のコマンドはエラーをスローします

Error response from daemon: Cannot start container

2回目の試行

それから私はこれを見つけました: コンテナを--iptables = false#12701でリンクできません

次のコマンドを実行すると、すべてが正常に見えます。

Sudo iptables -N DOCKER

しかし、コンテナ内でアウトバウンド接続を確立できないことに気付きました。例えば:

xxxxg@ubuntu:~$ Sudo docker exec -t -i nginx /bin/bash
root@b0d33f22d3f4:/# ping 74.125.21.147
PING 74.125.21.147 (74.125.21.147): 56 data bytes
^C--- 74.125.21.147 ping statistics ---
35 packets transmitted, 0 packets received, 100% packet loss
root@b0d33f22d3f4:/# 

--iptables=falseをDockerデーモンから削除すると、コンテナーのインターネット接続は通常に戻りますが、ufwは「正しく」機能しません(定義により...)。

では、docker + ufwのベストプラクティスは何ですか?誰でも助けを提供できますか?

ありがとう。

バート。

33
Yi-Chien Chang

私は数ヶ月前のようにこのような問題を抱えていましたが、最近、私のブログで解決策とともに問題を説明することにしました。これがショートカットです。

--iptables=falseを使用しても、説明したケースではあまり役に立ちません。ここでは単に十分ではありません。デフォルトでは、どのコンテナも発信接続を行うことはできません。

ここでUFWの背後にコンテナを置くために途中で省略している小さなステップがあります。次のように、--iptables=falseを使用するか、コンテンツを含む/etc/docker/daemon.jsonファイルを作成できます

{
  "iptables": false
}

結果は同じになりますが、後者のオプションでは、service docker restartを使用してdockerサービス全体を再起動するか、この機能を無効にする前にdockerがiptablesルールを追加する機会がある場合は再起動する必要があります。

完了したら、さらに2つのことを行います。

$ sed -i -e 's/DEFAULT_FORWARD_POLICY="DROP"/DEFAULT_FORWARD_POLICY="ACCEPT"/g' /etc/default/ufw
$ ufw reload

そのため、UFWでデフォルトの転送ポリシーを受け入れ用に設定し、次を使用します。

$ iptables -t nat -A POSTROUTING ! -o docker0 -s 172.17.0.0/16 -j MASQUERADE

そのようにして、iptablesルールでdockerの乱雑な動作を無効にし、同時にdockerに必要なルーティングを提供することで、コンテナーが発信接続を正常に行えるようにします。ただし、この時点からUFWルールは引き続き制限されます。

これがあなたと、答えを求めてここに来た人のために問題を解決することを願っています。

https://www.mkubaczyk.com/2017/09/05/force-docker-not-bypass-ufw-rules-ubuntu-16-04/ で問題と解決策をより包括的に説明しました

28
mkubaczyk

問題

この問題は長い間存在していました。

Dockerでiptablesを無効にすると、他の問題が発生します。

最初にロールバックの変更

インターネットで見つかった現在のソリューションに従ってサーバーを変更した場合は、まず次の変更をロールバックしてください。

  • Dockerのiptables機能を有効にします。構成ファイル--iptables=falseを含む/etc/docker/daemon.jsonなどのすべての変更を削除します。
  • UFWのデフォルトのFORWARDルールは、DROPではなくデフォルトのACCEPTに戻ります。
  • UFW構成ファイル/etc/ufw/after.rulesでDockerネットワークに関連するルールを削除します。
  • Docker構成ファイルを変更した場合は、最初にDockerを再起動します。後でUFW構成を変更し、再起動できます。

UFWとDockerの問題を解決する

このソリューションは1つのUFW構成ファイルのみを変更する必要があり、すべてのDocker構成とオプションはデフォルトのままです。 docker iptables機能を無効にする必要はありません。

UFW構成ファイル/etc/ufw/after.rulesを変更し、ファイルの最後に次のルールを追加します。

# BEGIN UFW AND DOCKER
*filter
:ufw-user-forward - [0:0]
:DOCKER-USER - [0:0]
-A DOCKER-USER -j RETURN -s 10.0.0.0/8
-A DOCKER-USER -j RETURN -s 172.16.0.0/12
-A DOCKER-USER -j RETURN -s 192.168.0.0/16

-A DOCKER-USER -j ufw-user-forward

-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 192.168.0.0/16
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 10.0.0.0/8
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 172.16.0.0/12
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 192.168.0.0/16
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 10.0.0.0/8
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 172.16.0.0/12

-A DOCKER-USER -j RETURN
COMMIT
# END UFW AND DOCKER

ファイルを変更した後、コマンドSudo systemctl restart ufwを使用してUFWを再起動します。これで、パブリックネットワークは公開されたドッカーポートにアクセスできなくなり、コンテナとプライベートネットワークは相互に定期的にアクセスでき、コンテナは内部から外部ネットワークにもアクセスできます。

たとえば、パブリックネットワークがDockerコンテナによって提供されるサービスにアクセスできるようにする場合、コンテナのサービスポートは80です。次のコマンドを実行して、パブリックネットワークがこのサービスにアクセスできるようにします。

ufw route allow proto tcp from any to any port 80

このコマンドにより、パブリックネットワークは、コンテナポートが80であるすべての公開ポートにアクセスできます。

注:オプション-p 8080:80を使用してポートを公開する場合、ホストポート80ではなく、コンテナポート8080を使用する必要があります。

サービスポートが80のコンテナが複数あるが、外部ネットワークから特定のコンテナにのみアクセスしたい場合。たとえば、コンテナのプライベートアドレスが172.17.0.2である場合、次のコマンドを使用します。

ufw route allow proto tcp from any to 172.17.0.2 port 80

サービスのネットワークプロトコルがUDP(DNSサービスなど)の場合、次のコマンドを使用して、外部ネットワークが公開されているすべてのDNSサービスにアクセスできるようにすることができます。

ufw route allow proto udp from any to any port 53

同様に、IPアドレス172.17.0.2などの特定のコンテナーのみの場合:

ufw route allow proto udp from any to 172.17.0.2 port 53

使い方?

次のルールにより、プライベートネットワークが相互にアクセスできるようになります。通常、プライベートネットワークはパブリックネットワークよりも信頼されています。

-A DOCKER-USER -j RETURN -s 10.0.0.0/8
-A DOCKER-USER -j RETURN -s 172.16.0.0/12
-A DOCKER-USER -j RETURN -s 192.168.0.0/16

次のルールにより、UFWはパブリックネットワークがDockerコンテナによって提供されるサービスへのアクセスを許可されるかどうかを管理できます。そのため、すべてのファイアウォールルールを1か所で管理できます。

-A DOCKER-USER -j ufw-user-forward

次のルールは、すべてのパブリックネットワークによって開始された接続要求をブロックしますが、内部ネットワークから外部ネットワークへのアクセスを許可します。 TCPプロトコルの場合、パブリックネットワークからのTCP接続をアクティブに確立できません。UDPプロトコルの場合、32767未満のポートへのアクセスはすべてブロックされます。このポートはなぜですか?UDPプロトコルはステートレスなので、TCPのように。GNU/ Linuxの場合はローカルポート範囲を見つけることができます。ファイル内の/proc/sys/net/ipv4/ip_local_port_range。デフォルトの範囲は32768 60999です。実行中のコンテナからUDPプロトコルサービスにアクセスする場合、ローカルポートはポート範囲からランダムに選択され、サーバーはデータを返します。したがって、すべてのコンテナ内のUDPプロトコルのリッスンポートは32768未満であると想定できます。これが、パブリックネットワークが32768未満のUDPポートにアクセスしないようにする理由です。

-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 192.168.0.0/16
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 10.0.0.0/8
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 172.16.0.0/12
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 192.168.0.0/16
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 10.0.0.0/8
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 172.16.0.0/12

-A DOCKER-USER -j RETURN

もっと

https://github.com/chaifeng/ufw-docker

Sudo wget -O /usr/local/bin/ufw-docker https://github.com/chaifeng/ufw-docker/raw/master/ufw-docker
chmod +x /usr/local/bin/ufw-docker

使用法

ufw-docker help
ufw-docker install
ufw-docker status
ufw-docker allow webapp
ufw-docker allow webapp 80
ufw-docker allow webapp 53/udp
ufw-docker list webapp
ufw-docker delete allow webapp 80/tcp
ufw-docker delete allow webapp

更新:2018-09-10

ufw-user-forwardではなくufw-user-inputを選択した理由

ufw-user-inputを使用

プロ:

使いやすく、理解しやすく、古いバージョンのUbuntuをサポートしています。

たとえば、コンテナポートが8080である公開されたポートに公衆がアクセスできるようにするには、次のコマンドを使用します

ufw allow 8080

短所:

コンテナのポートを公開するだけでなく、ホストのポートも公開します。

たとえば、ホストでサービスが実行されており、ポートが8080である場合。コマンドufw allow 8080を使用すると、パブリックネットワークはサービスと、コンテナのポートが8080であるすべての公開されたポートにアクセスできます。ただし、ホスト上で実行されているサービス、またはコンテナ内で実行されているサービスのみを公開し、両方ではありません。

この問題を回避するには、すべてのコンテナに対して次のようなコマンドを使用する必要があります。

ufw allow proto tcp from any to 172.16.0.3 port 8080

ufw-user-forwardを使用

プロ:

同じコマンドでホストとコンテナで同時に実行されているサービスを公開することはできません。

たとえば、コンテナのポート8080を公開する場合は、次のコマンドを使用します。

ufw route allow 8080

パブリックネットワークは、コンテナポートが8080であるすべての公開されたポートにアクセスできます。

ただし、ホストのポート8080にはまだパブリックネットワークからアクセスできません。そうする場合は、次のコマンドを実行して、ホストのポートへのパブリックアクセスを個別に許可します。

ufw allow 8080

短所:

Ubuntuの古いバージョンはサポートしていません。コマンドはもう少し複雑です。しかし、私のスクリプト https://github.com/chaifeng/ufw-docker を使用できます。

結論

Ubuntuの古いバージョンを使用している場合、ufw-user-inputチェーンを使用できます。ただし、公開すべきでないサービスを公開しないように注意してください。

ufw routeサブコマンドをサポートするUbuntuの新しいバージョンを使用している場合は、ufw-user-forwardチェーンを使用し、ufw routeコマンドを使用してコンテナーのファイアウォールルールを管理することをお勧めします。


更新日:2018年10月6日

スクリプト fw-docker はDocker Swarmをサポートするようになりました。詳細については、最新のコードを参照してください https://github.com/chaifeng/ufw-docker

Swarmモードで使用する場合、このスクリプトはマネージャーノードでのみ使用してファイアウォールルールを管理できます。

  • マネージャーとワーカーを含むすべてのノード上のすべてのafter.rulesファイルの変更
  • このスクリプトをマネージャーノードに展開する

Docker Swarmモードで実行すると、このスクリプトはグローバルサービスufw-docker-agentを追加します。イメージ chaifeng/ufw-docker-agent もこのプロジェクトから自動的にビルドされます。

43
Feng

価値があるのは、セットアップ全体にさらに多くのブリッジネットワークが関係している場合の @ mkubaczyk's answer の補足です。これらはDocker-Composeプロジェクトによって提供される場合があり、これらのプロジェクトがsystemdによって制御される場合、適切なルールを生成する方法は次のとおりです。

/etc/systemd/system/[email protected]

[Unit]
Description=Docker-Compose project: %I
After=docker.service
BindsTo=docker.service
AssertPathIsDirectory=/<projects_path>/%I
AssertFileNotEmpty=/<projects_path>/%I/docker-compose.yml

[Service]
Type=simple
Restart=always
WorkingDirectory=/<projects_path>/%I
ExecStartPre=/usr/bin/docker-compose up --no-start --remove-orphans
ExecStartPre=+/usr/local/bin/update-iptables-for-docker-bridges
ExecStart=/usr/bin/docker-compose up
ExecStop=/usr/bin/docker-compose stop --timeout 30
TimeoutStopSec=30
User=<…>
StandardOutput=null

[Install]
WantedBy=multi-user.target

/usr/local/bin/update-iptables-for-docker-bridges

#!/bin/sh

for network in $(docker network ls --filter 'driver=bridge' --quiet); do
  iface=$(docker network inspect --format '{{index .Options "com.docker.network.bridge.name"}}' ${network})
  [ -z $iface ] && iface="br-${network}"
  subnet=$(docker network inspect --format '{{range .IPAM.Config}}{{.Subnet}}{{end}}' ${network})
  rule="! --out-interface ${iface} --source ${subnet} --jump MASQUERADE"
  iptables --table nat --check POSTROUTING ${rule} || iptables --table nat --append POSTROUTING ${rule}
done

明らかに、これはそれほどうまくスケーリングしません。

また、基本的な概念全体が、コンテナで実行されているアプリケーションの接続のソースを偽装することも注目に値します。

0
funky-future

あなたの質問はよくわかりませんが、私が収集できるものから、Docker内で実行しているアプリにアクセスできるユーザーをより適切に制御したいと思いますか?ここでは、IPテーブルではなくフロントエンドプロキシを介してトラフィックを制御する同様の質問に回答しました Dockerコンテナへの外部アクセスをブロックする

お役に立てれば

ディラン

編集

上記のアプローチを使用すると、UFWを使用して、ポート80(つまり、プロキシ)への着信接続のみを許可できます。これにより、プロキシ設定とDNSを介してトラフィックを制御できるというボーナスが追加され、ポートの露出が最小限に抑えられます。

0
Dylan Scott