Dockerのコンテナのポートの公開には注意する

リバースプロキシをホスト側、バックエンドをdockerで運用している場合、外部からdockerが公開しているポートに直接アクセスできていないかを確認しました。

サーバーの構成は以下になります。

サーバーの構成イメージ

サーバーはubuntu18.04、リバースプロキシにはnginxを利用しバックエンドはdockerでWordPressを稼働させ8000番ポートに公開します。

ファイアーウォールの設定にはufwを利用し80番ポートは許可し、8000番ポートは許可しないようにします。

検証用の環境構築

それでは検証用の環境を構築していきます。

ファイアーウォールの設定

まずはufwでファイアーウォールの設定を行います。

sudo ufw enable
sudo ufw default deny
sudo ufw limit "OpenSSH"
sudo ufw allow "Nginx Full"
sudo ufw reload

上記の設定はデフォルトのポリシーを拒否に設定、HTTPサーバーが稼働する80番ポートと443番ポート、およびSSHサーバーが稼働する22番ポートのみを許可しています。

バックエンドの構築

続いてバックエンドの構築のためdockerでWordPressを稼働させます。Quickstart: Compose and WordPressにあるdocker-compose.ymlを利用してコンテナを作成して起動します。

~/my_wordpress/docker-compose.ymlを作成してQuickstartの通り以下のコマンドを実行します。

cd ~/my_wordpress
docker-compose up -d

リバースプロキシの設定

最後にnginxの設定を行います。まず/etc/nginx/nginx.confに記載してあるinclude /etc/nginx/sites-enabled/*;をコメントアウトしました。

sudo sed -i -r 's/^\s+include\s+\/etc\/nginx\/sites-enabled\/\*;/#&/g' /etc/nginx/nginx.conf

続けてWordPressへのリバースプロキシの設定を追加します。以下の内容で/etc/nginx/conf.d/wordpress.confを作成しました。(今回はリバースプロキシ用のヘッダーの設定は省略しています)

/etc/nginx/conf.d/wordpress.conf
server {
  listen 80;
  location / { proxy_pass http://127.0.0.1:8000/; }
}

念のため設定ファイルの構文チェックを行います。

sudo nginx -t

構文チェックで問題がなければ、設定ファイルの再読み込みを行います。

sudo service nginx reload

ここまでの内容をVagrantfileにしてみました。

Vagrantfile
# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|
  config.vm.box = "bento/ubuntu-18.04"
  config.vm.network "public_network", ip: "192.168.0.111"

  config.vm.provision :shell, inline: <<-'SHELL'
    # install nginx, ufw
    apt-get update
    apt-get install -y nginx ufw
    
    # install docker
    curl -fsSL https://get.docker.com -o get-docker.sh
    sh get-docker.sh

    # install docker-compose
    curl -L \
    "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" \
    -o /usr/local/bin/docker-compose
    
    chmod +x /usr/local/bin/docker-compose
    ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose
    
    gpasswd -a vagrant docker

    # ufw setting
    ufw default deny
    ufw limit "OpenSSH"
    ufw allow "Nginx Full"
    yes | ufw enable

    # nginx setting (omitted proxy headers)
    sed -i -r 's/^\s+include\s+\/etc\/nginx\/sites-enabled\/\*;/#&/g' /etc/nginx/nginx.conf

    cat << 'EOF' > /etc/nginx/conf.d/wordpress.conf
server {
    listen 80;
    location / { proxy_pass http://127.0.0.1:8000/; }
}
EOF

    service nginx reload

    # build docker WordPress container
    mkdir ~/my_wordpress
    cd ~/my_wordpress && cat << 'EOF' > docker-compose.yml && docker-compose up -d
version: "3.9"

services:
  db:
    image: mysql:5.7
    volumes:
      - db_data:/var/lib/mysql
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: somewordpress
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress
      MYSQL_PASSWORD: wordpress
    
  wordpress:
    depends_on:
      - db
    image: wordpress:latest
    volumes:
      - wordpress_data:/var/www/html
    ports:
      - "8000:80"
    restart: always
    environment:
      WORDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: wordpress
      WORDPRESS_DB_NAME: wordpress
volumes:
  db_data: {}
  wordpress_data: {}
EOF

  SHELL
end

もし利用される場合は以下の仮想マシンのネットワークの設定を自分の環境のネットワークアドレスに合わせて書き換えてください。

config.vm.network "public_network", ip: "192.168.0.111"

外部から疎通の確認

検証用の環境のIPアドレスは192.168.0.111にしました。自分でも検証用の環境を構築された場合は自分の環境のIPアドレスに読み替えてください。

まずは80番ポートへの疎通を確認します。こちらへのアクセスはリバースプロキシによってWordPressにプロキシされます。初回アクセスのためWordPressの初期設定画面に遷移する想定です。

curl -X GET -I -L http://192.168.0.111

結果は想定通りWordPressの初期設定画面に遷移していることが確認できました。次は8000番ポートへ疎通できるかの確認を行います。8000番ポートはファイアーウォールで許可していないはずなので外部から直接アクセスできない想定です。

curl -X GET -I -L http://192.168.0.111:8000

結果は想定と異なり80番ポートと同じくWordPressの初期設定画面に遷移しました。外部から直接アクセスできてしまう状態になっていることが確認できました。

コンテナのポートの公開を変更して確認

dockerはデフォルトでコンテナのポートをIPアドレス0.0.0.0に公開します。docker-compose.ymlのポートの指定を8000:80からループバックアドレスに対して公開するよう127.0.0.1:8000:80に変更しました。

cd  ~/my_wordpress
sed -i -r 's/"8000:80"/"127.0.0.1:8000:80"/' docker-compose.yml
docker-compose down && docker-compose up -d

変更後に80番と8000番のポートに対して再度疎通の確認を行い、8000番のポートに外部から直接アクセスできないことが確認できました。

まとめ

dockerはデフォルトでコンテナのポートを0.0.0.0に対して公開します。これはホストマシン上のすべてのインターフェースを意味していることに注意が必要です。

あまりないケースかとは思いますが、例えばデータベースをdockerで構築しポートを公開している場合で外部からの直接アクセスを想定していないにも関わらず直接アクセスできてしまっている場合はかなり危険です。