あらすじ
公衆WiFiに繋いだ状態でいつものように docker container run -p 8080:80 nginx
のような感じでDockerコンテナを動かしていたら、外部からリクエストを受信した。
ファイアウォールを設定し、外部からのアクセスを拒否しているはずなのになぜアクセスできたんだ...
環境
何が起きた?
Dockerはデフォルトの設定では-p 8080:80
のようにポートマッピングするとファイアウォールの設定を書き換え、外部からそのポートへのアクセスを許可するようになっている。 その結果LAN内の他のPCから対象ポートにアクセス出来てしまう。
ちなみにこれはDocker公式からも注意が出ている。
Publishing container ports is insecure by default. Meaning, when you publish a container’s ports it becomes available not only to the Docker host, but to the outside world as well.
If you include the localhost IP address (127.0.0.1) with the publish flag, only the Docker host can access the published container port.
$ docker run -p 127.0.0.1:8080:80 nginx
docs.docker.com
上記の注意の通り、デフォルトでは全てのホストからアクセスできるようにDockerがファイアウォール設定を書き換えてしまう。
対策
ポートマッピングのコマンドライン引数などを修正する
-p 127.0.0.1:8080:80
のようにポートの公開範囲を指定すれば、LAN全体に向けてポートを公開することはない。
docker-compose.yml の場合も同じく各サービスコンテナのポートマッピングの指定時に port: "127.0.0.1:8080:80"
のように公開先IPアドレスを明示的に指定することでポートが外部に公開されなくなる。
Docker Engine の設定を書き換える (おすすめ)
コマンドライン引数やdocker-compose.ymlを書き換えるというのは毎回大変だし、サーバーで動かす時はポートのIPアドレスの指定を消さないといけないし、どうせ書き換えるのを忘れる日がいつか来るので、手元の開発マシンに関してはデフォルトでポートの公開範囲を制限するのがいいと思います。
"ip": "127.0.0.1"
をDockerEngineの設定に追加してあげれば、デフォルトの公開範囲をローカルホスト内だけにできるので設定しましょう。
Docker for Mac だったら以下のようなイメージ。
試してみる
以下のようにホスト側の8080番ポートをコンテナの80番ポートにマッピングする。(よく見かけるオプションですね)
$ docker container run -p 8080:80 nginx:latest
そんでローカルIPアドレスに対してリクエストを送ってアクセスできるか見てみる。
$ curl <local_ip>:8080 <!DOCTYPE html> <html> <head> <title>Welcome to nginx!</title> <style> html { color-scheme: light dark; } body { width: 35em; margin: 0 auto; font-family: Tahoma, Verdana, Arial, sans-serif; } </style> </head> <body> <h1>Welcome to nginx!</h1> <p>If you see this page, the nginx web server is successfully installed and working. Further configuration is required.</p> <p>For online documentation and support please refer to <a href="http://nginx.org/">nginx.org</a>.<br/> Commercial support is available at <a href="http://nginx.com/">nginx.com</a>.</p> <p><em>Thank you for using nginx.</em></p> </body> </html>
nginxのコンテナにアクセスできていることがわかる。
また、nmapで確認してみると8080ポートが開放されていることがわかる。
$ nmap 172.20.10.8 Starting Nmap 7.94 ( https://nmap.org ) at 2023-07-01 22:11 JST Nmap scan report for 172.20.10.8 Host is up (0.000036s latency). Not shown: 997 closed tcp ports (conn-refused) PORT STATE SERVICE 5000/tcp open upnp 7000/tcp open afs3-fileserver 8080/tcp open http-proxy Nmap done: 1 IP address (1 host up) scanned in 0.08 seconds
これはつまり、同じLAN内であれば他のPCやスマホからもアクセスできる。やばいわよ!
このインセキュアなデフォルト挙動は、前述した設定(-p 127.0.0.1:8080:80
または「Docker Engine の設定を書き換える (おすすめ)」)を施すと解決する。
以下Docker Engineの設定を書き換えた後のnmap
$ nmap 172.20.10.8 Starting Nmap 7.94 ( https://nmap.org ) at 2023-07-01 22:44 JST Nmap scan report for 172.20.10.8 Host is up (0.000048s latency). Not shown: 998 closed tcp ports (conn-refused) PORT STATE SERVICE 5000/tcp open upnp 7000/tcp open afs3-fileserver Nmap done: 1 IP address (1 host up) scanned in 0.05 seconds
無事8080
ポートが公開されないようになった。良かった。
感想
Publishing container ports is insecure by default.
なんでデフォルトがinsecureやねん!
Dockerを数年触っているが、Dockerがファイアウォールの設定を書き換えることは知らなかった。改めて自分が使うツールについてはしっかりと理解し、ドキュメントをちゃんと読もうと思った。
Dockerのインストール方法について解説している記事を読んでもこのようなことが書かれているのを自分は見たことがなかったし、結構この危ない仕様知らない人が多そうで気づかずポート開放してる人がいそうで怖いですね。
この記事を見て皆様のDockerが少しでも安全になれば幸いです。
ではでは〜👋👋