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

2023-03-27Docker,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 を用意する。
  3. 「MySQL サーバ」には「Apache+PHP サーバ」から のみ アクセス可能な追加のユーザー app_user_additional を用意する。

検証環境の用意

「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
      - MYSQL_DATABASE=testdb
      - MYSQL_USER=app_user
      - MYSQL_PASSWORD=app_p@ssw0rd
    volumes:
      - "$PWD:/docker-entrypoint-initdb.d"
  web-server:
    image: php:7.3-apache
    volumes:
      - "$PWD:/var/www/html"

上記の YAML ファイルから Docker Compose を起動すれば、以下のユーザーで接続できます。

  • root ( testpass )
  • app_user ( app_p@ssw0rd )

YAMLファイルはカレントディレクトリに配置します。

「MySQL サーバ」コンテナ内の /docker-entrypoint-initdb.d ディレクトリにカレントディレクトリをマウントしていますが、
これはこのコンテナが /docker-entrypoint-initdb.d ディレクトリ配下にある *.sh*.sql を初回起動時に実行し、初期化する仕組みがあるためです。
この仕組を利用した初期化用スクリプトとして、 createdb.sql も作成しておきます。

createdb.sql

create user 'app_user_additional'@'web-server' identified WITH mysql_native_password by 'app_p@ssw0rd';

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

これで、先の rootapp_user に加えて、「Web サーバ」(ホスト名= web-server )から以下のユーザーで接続できる想定でした。

  • app_user_additional ( app_p@ssw0rd )

コンテナ環境の起動

以下のコマンドでコンテナを 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 default-mysql-client -y

3 つのユーザー( root / app_user / app_user_additional )で 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_additionalユーザー
$ mysql -h db-server -P 3306 -u app_user_additional -papp_p@ssw0rd testdb

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

結果は以下のとおりでした。

# root ===> 失敗
$ /var/www/html# mysql -h db-server -P 3306 -u root -ptestpass
ERROR 1045 (28000): Access denied for user 'root'@'192.168.0.3' (using password: YES)

# app_user ===> 成功!
$ /var/www/html# mysql -h db-server -P 3306 -u app_user -papp_p@ssw0rd testdb
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MySQL connection id is 12
Server version: 5.7.40 MySQL Community Server (GPL)

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MySQL [testdb]> \q
Bye

# app_user_additional ===> 失敗
$ /var/www/html# mysql -h db-server -P 3306 -u app_user_additional -papp_p@ssw0rd testdb
ERROR 1045 (28000): Access denied for user 'app_user_additional'@'192.168.0.3' (using password: YES)

root ユーザーで接続できなかったのは localhost からしか接続できない設定となっているのでしょう。

app_user_additional ユーザーで接続できなかったのはなぜでしょう?

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

今度は db-server コンテナから root ユーザーで MySQL にログインし、あるグローバル変数の値を確認してみます。

# こちらのコマンドだと接続できる
$ docker-compose exec db-server mysql -u root -ptestpass
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     # ※※※←こちらの設定が悪さをしている。

ちなみにユーザーの一覧を確認してみましたが、 app_user_additional ユーザーは存在していました。 また、 root ユーザーはローカルからの接続しか許可されていませんでした。

mysql> select Host, User, Select_priv from mysql.user;
+------------+---------------------+-------------+
| Host       | User                | Select_priv |
+------------+---------------------+-------------+
| localhost  | root                | Y           |
| localhost  | mysql.session       | N           |
| localhost  | mysql.sys           | N           |
| localhost  | healthchecker       | N           |
| %          | app_user            | N           |
| web-server | app_user_additional | N           |
+------------+---------------------+-------------+
6 rows in set (0.01 sec)

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

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

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

grant all privileges on testdb.* to 'app_user_additional'@'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 ユーザー作成時に接続元を細かく制御するのはバッドプラクティスなんでしょうか?

2023-03-27Docker,MySQL