Dockerの公式MySQLイメージでは接続元ホスト名によるアクセス制御ができない( `skip-name-resolve`)

Docker, MySQL

はじめに

MySQLでは、接続元ホスト名ごとに「接続できる」「接続できない」の制御が可能ですが、設定方法を正しく把握していなかったのでDocker環境を使って検証したところハマってしまいました。

検証環境

$ docker --version
Docker version 18.09.2, build 6247962

$ docker-compose --version
docker-compose version 1.23.2, build 1110ad01

環境

「Docker」と「Docker Compose」を使って以下のような環境を構築し検証を行いたいと思いました。

  1. サーバは2台。
    • 「MySQLサーバ」である db-server
    • 「Apache+PHPサーバ」である web-server
  2. 「MySQLサーバ」には「Apache+PHPサーバ」からのみアクセス可能なユーザ app_user を用意する。

検証環境の用意

「Webサーバ」用のDockerコンテナ、「MySQLサーバ」用のDockerコンテナを立ち上げるために、「Docker Compose」を利用しました。

docker-compose.ymlの設定は以下のようになりました。

docker-compose.yml

version: "2"
services:
  db-server:
    image: mysql:latest
    command: --default-authentication-plugin=mysql_native_password
    restart: always
    environment:
      - MYSQL_ROOT_PASSWORD=testpass
    volumes:
      - "$PWD:/docker-entrypoint-initdb.d"
  web-server:
    image: php:7.3-apache
    volumes:
      - "$PWD:/var/www/html"

カレントディレクトリに配置します。
「MySQLサーバ」コンテナ内の /docker-entrypoint-initdb.d ディレクトリにカレントディレクトリをマウントしていますが、
これはこのコンテナが /docker-entrypoint-initdb.d ディレクトリ配下にある *.sh*.sql を初回起動時に実行し、初期化する特性があるためです。

初期化のようスクリプトとして、 createdb.sql も作成しておきます。

createdb.sql

drop database if exists testdb;
create database testdb default character set utf8;

drop user if exists 'app_user'@'web-server';
create user 'app_user'@'web-server' identified WITH mysql_native_password by 'app_p@ssw0rd';

grant all privileges on testdb.* to 'app_user'@'web-server';

これで「Webサーバ」(ホスト名= web-server )から app_user というユーザでの接続が可能となる想定です。

コンテナ環境の起動

以下のコマンドでコンテナをdeamon起動します。

# 起動
$ docker-compose up -d

# コンテナログを監視し、DBが完全に起動し終わるまで待つ
$ docker-compose logs -f

「Webサーバ」( web-server )からの接続

「Webサーバ」コンテナに接続して db-server ホスト名を利用した接続を試してみます。

# コンテナ内に接続
$ docker-compose exec web-server bash

mysql コマンドで接続したいのですが、この環境には mysql コマンドが存在しないためインストールします。

$ apt-get update -y && apt-get install mysql-client -y

2つのユーザ( root / app_user )でMySQLにログインを試みてみます。
両ユーザともログインができる想定でした。

# rootユーザ
$ mysql -h db-server -P 3306 -u root -ptestpass

# app_userユーザ
$ mysql -h db-server -P 3306 -u app_user -papp_p@ssw0rd testdb

app_user ユーザではなぜか接続できない

接続できたのは root ユーザのみ。 app_user ユーザはログインに失敗してしまいます。

原因は公式のMySQLコンテナの設定

root ユーザでMySQLにログインし、あるグローバル変数の値を確認してみます。

mysql> show global variables like '%skip%';
+------------------------+-------+
| Variable_name          | Value |
+------------------------+-------+
| skip_external_locking  | ON    |
| skip_name_resolve      | ON    |
| skip_networking        | OFF   |
| skip_show_database     | OFF   |
| slave_skip_errors      | OFF   |
| sql_slave_skip_counter | 0     |
+------------------------+-------+
6 rows in set (0.02 sec)

上から2つ目の skip_name_resolve グローバル変数の値が ON となっています。
これはユーザのアクセス制御を行う際にホスト名が利用できないことを表しています。

設定を変更するには、MySQLコンテナの /etc/mysql/conf.d/docker.cnf を変更しないといけません。
つまり一度コンテナをビルドし直す必要があります。

# コンテナ内に接続
$ docker-compose exec db-server bash

# 現在の設定内容を確認
root@d711c2a0404b:/# cat /etc/mysql/conf.d/docker.cnf
[mysqld]
skip-host-cache
skip-name-resolve     # ※※※←こちらの設定が悪さをしている。

接続元ホスト名指定をやめて、接続元IPアドレス指定をすれば対応可能

結局、 createdb.sql を書き換えて接続元ホスト名を指定している箇所を接続先IPアドレスに変更してやれば問題が解消します。

drop database if exists testdb;
create database testdb default character set utf8;

drop user if exists 'app_user'@'192.168.176.%';                     -- IPアドレス指定
create user 'app_user'@'192.168.176.%' identified WITH mysql_native_password by 'app_p@ssw0rd';
                                                                    -- IPアドレス指定

grant all privileges on testdb.* to 'app_user'@'192.168.176.%';     -- IPアドレス指定

※ホストIPアドレスやサブネットIPアドレスを調べるには、 docker inspect コマンドでコンテナの詳細情報を確認します。

# WebサーバのコンテナIDを特定する(9dfb145b27cc)
$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                               NAMES
d711c2a0404b        mysql:latest        "docker-entrypoint.s…"   26 minutes ago      Up 26 minutes       0.0.0.0:3306->3306/tcp, 33060/tcp   haldata-security-design_db-server_1
9dfb145b27cc        php:7.3-apache      "docker-php-entrypoi…"   26 minutes ago      Up 26 minutes       80/tcp                              haldata-security-design_web-server_1
b43b4bd655ac        mysql:latest        "docker-entrypoint.s…"   7 hours ago         Up 7 hours          3306/tcp, 33060/tcp                 relaxed_diffie

# GatewayとIPAddressからサブネットIPは "192.168.176.0" であるとわかる
$ docker inspect 9dfb145 | jq '.[].NetworkSettings.Networks'
{
  "haldata-security-design_default": {
    "IPAMConfig": null,
    "Links": null,
    "Aliases": [
      "9dfb145b27cc",
      "web-server"
    ],
    "NetworkID": "91e47977a22feff67f1bf9f5d3ab891f3913cdbf78dbcb32f88ad5f66ea3c7c8",
    "EndpointID": "73cb4b511ea9577b695f80d8e4a271f561dbcd43f3427837525b3f249b87fe6d",
    "Gateway": "192.168.176.1",
    "IPAddress": "192.168.176.3",
    "IPPrefixLen": 20,
    "IPv6Gateway": "",
    "GlobalIPv6Address": "",
    "GlobalIPv6PrefixLen": 0,
    "MacAddress": "02:42:c0:a8:b0:03",
    "DriverOpts": null
  }
}

ひとこと

Docker Composeで構成されたコンテナ郡以外からDBサーバコンテナに接続市に来るケースは殆ど無いでしょうし、MySQLユーザ作成時に接続元を細かく制御するのはバッドプラクティスなんでしょうか?

Docker, MySQL