DockerとUFWでポート設定を行うときの注意点

目次

概要

Dockerを利用してサーバーを運用していた際に、ファイアウォールで非公開にするつもりのポートが公開されていたことがありました。この問題の原因と、対処法について説明します。

環境

  • Debian 11
  • Docker version 23.0.1

何が起きたのか

私はサーバーでDockerを利用してサービスを実行しています。具体的には、Dockerで起動したコンテナのいくつかはポートフォワードでホストマシンのポートと紐付け、さらにNginxのリバースプロキシを利用して、Webサービスを公開しています。

また、サーバーのセキュリティ強化のために、利用しているDebianに付属していたUFWというツールを利用してファイアウォールの設定をしていました。

ある時、何気なくコンテナとホストマシンを紐付けたポートにアクセスしてみました。このポートは、UFWでパケットをブロックしているため、通常であれば何も表示されることはありません。しかし、ファイウォールで設定しているにも関わらず、アクセスできてしまいました。

これは、セキュリティ的に問題があるということで、原因や対処法を調べてみました。その結果、DockerとUFWなどのファイアウォール設定ツールとの相性がとても悪いと分かりました。

Dockerは、ホストマシンとポートフォワードした際に、ファイアウォール設定ツールのiptablesもしくはnftablesを利用してポートを開放しますが、これがUFWで設定した項目よりも優先されてしまっているということです。UFWも内部的にiptablesもしくはnftablesを利用しているため、設定時の優先順位の問題があるようです。

ちなみに、iptablesとnftablesは、Linuxにおけるパケットフィルタリングのためのツールです。iptablesは昔からのツールで、nftablesはiptablesの後継として開発されたものです。最近のLinuxでは、iptablesではなくnftablesを使うことが多く、nftables自体もiptablesの構文を解釈できます。

対処方法

この問題を解決するには、Dockerのコンテナ起動時の設定を変更する、UFW以外のツールを使うなど、いくつかの方法があります。

Dockerコンテナの起動時の設定を変えるというのは、ポート指定のオプションを次のように設定するというものです。

docker run -p 127.0.0.1:8080:8080 nginx:latest

Docker Composeの場合は、docker-compose.ymlファイルを次のようにします。

version: '3'

services:
  web:
    image: nginx:latest
    ports:
      - "127.0.0.1:8080:80"

127.0.0.1というlocalhostのIPアドレスを追加することで、サーバーの外部からの通信から接続できないようにできます。しかし、このような設定ができない環境も考えられます。また、コンテナの設定を変更した際の保守が面倒ということも考えられます。

他にも、UFW以外のツールを使う方法があります。これは、iptablesやnftablesの構文を直接記述したり、それらのフロントエンドとなるfirewalldなどを使います。私は、iptablesやnftablesの構文をあまり書きたくないということで、firewalldを使って、設定を行いました。

簡単にですが、firewalldの設定方法を説明します。ファイアウォールまわりの設定を行うため、設定中にサーバーへリモートからアクセスできなくなる可能性があります。設定を行う前には、バックアップを取っておいたり、何かあった際にコンソールからアクセスをする方法を調べたりするなど準備してください。

最初にUFWの設定を無効化しておきます。

ufw disable

CentOSなどのRedHat系のディストリビューションでは、最初からfirewalldがインストールされていますが、DebianやUbuntuなどのディストリビューションではインストールされていません。そこで次のコマンドでfirewalldをインストールします。

sudo apt install firewalld

インストールを終えたら、設定をします。最初にDockerコンテナの通信の許可をします。

sudo firewall-cmd --add-masquerade --permanent

さらに、Webサーバーの通信(80番ポート、443番ポート)を許可します。

sudo firewall-cmd --add-service=http --permanent
sudo firewall-cmd --add-service=https --permanent

最後に、設定を反映させます。

sudo firewall-cmd --reload

これで、Dockerコンテナ間、コンテナから外部のサーバーへの通信、Webサーバーとして80/443番ポートの通信が許可されました。

さいごに

UFWでファイアウォール設定ができていると思っていたら、全然できていなかったときはあせりましたが、firewalldを使って、しっかりと設定できました。

サーバーのセキュリティ強化として不必要なポートの公開は絶対に防ぎたいところです。

設定の参考になれば嬉しいです。