MySQL道普請便り

第21回MySQLのユーザー管理について[その3]

第17回から何回かに分けて、MySQLのユーザー認証について説明しています。第19回はMySQLの名前解決によって"root@localhost"と"root@127.0.0.1"があたかも同じアカウントであるかのように振る舞うことについて説明しました。今回は(前回の説明にもちらっとだけ出てきましたが)ユーザー名を持たない「匿名アカウント」について説明したいと思います。

第17回から引き続き、今回のデモンストレーション環境は敢えて「匿名アカウント」を有効にしておくために、MySQL 5.6をyumリポジトリーからインストールしたものになっています。各バージョンのyum版、rpm版の構成の違いは 「第10回 yum, rpmインストールにおけるMySQL 5.6とMySQL 5.7の違い」を参考にしてください。

筆者がCentOS 6.6上で今回の環境を作るために実行したコマンドは以下の通りです。

$ sudo yum install -y http://dev.mysql.com/get/mysql57-community-release-el6-7.noarch.rpm
$ sudo yum install -y --disablerepo=mysql57-community --enablerepo=mysql56-community mysql-community-server
$ sudo service mysqld start

匿名アカウントとは何か

「匿名アカウント」とは、⁠ユーザー名部分に任意の文字列を取ることができる」アカウントです。言い換えれば、⁠ユーザー名部分は検証されず、接続元ホストとパスワードによってのみ認証される」アカウントが「匿名アカウント」です。

具体的には以下のように、⁠ユーザー名が空欄」のアカウントが匿名アカウントとなります。

mysql> SELECT user, host FROM mysql.user ORDER BY user, host;
+------+-----------+
| user | host      |
+------+-----------+
|      | centos    |
|      | localhost |
| root | 127.0.0.1 |
| root | ::1       |
| root | centos    |
| root | localhost |
+------+-----------+
6 rows in set (0.00 sec)

匿名アカウントの動作

"root@localhost"アカウントをDROPした状態で、mysql -uroot -hlocalhost("root@localhost"アカウントを利用して認証させる)を実行してみましょう。

mysql> DROP USER root@localhost;
Query OK, 0 rows affected (0.00 sec)

$ mysql -uroot -hlocalhost

mysql> SELECT USER();
+----------------+
| USER()         |
+----------------+
| root@localhost |
+----------------+
1 row in set (0.00 sec)

mysql> use mysql
ERROR 1044 (42000): Access denied for user ''@'localhost' to database 'mysql'

mysql> SELECT CURRENT_USER();
+----------------+
| CURRENT_USER() |
+----------------+
| @localhost     |
+----------------+
1 row in set (0.00 sec)

mysql> SHOW GRANTS;
+--------------------------------------+
| Grants for @localhost                |
+--------------------------------------+
| GRANT USAGE ON *.* TO ''@'localhost' |
+--------------------------------------+
1 row in set (0.00 sec)

"root@localhost"にマッチするアカウントが存在しないため、"''@localhost"アカウントで認証されました。mysqlコマンドラインクライアントからは"root@localhost"で接続したつもりUSER関数クライアントが指定したユーザー名を返却する関数です)になっていますが、MySQLサーバー上では"''@localhost"アカウントとして認証されているCURRENT_USER関数MySQLサーバー上で認証したユーザー名を返却します)ため、rootとしての特権は使えません(このようなアカウントの"すり替え"をクライアント側では特に検知しません⁠⁠。

ここまでは、⁠"root@localhost"アカウントが存在している間は完全一致の"root@localhost"が優先され、"root@localhost"アカウントが存在しなくなると部分一致の"''@localhost"アカウントが選択された」という判りやすい動作だと思います。

ホスト名部分のワイルドカード

匿名アカウントは「ユーザー名部分はなんでも良い」アカウントですが、それと似た概念で、各アカウントはホスト名にワイルドカード(任意の1文字を表す"_"と任意の0文字以上を表す"%")を利用することができます。

たとえば"192.168.1.0/24"ネットワークからの接続元全てを表現するのに、"192.168.1.%"という書き方ができます(ネットワーク単位であれば、"192.168.1.0/255.255.255.0"という表記の仕方もできますが、これについてはドキュメントに説明を譲ることにします⁠⁠。

またたとえば、任意のホストから"user1"というユーザー名、"password1"というパスワードでアクセスできるアカウントは以下のように作成します(おな、ホスト名を指定しなかった場合には暗黙に"%"が割り当てられます⁠⁠。

mysql> CREATE USER user1@'%' IDENTIFIED BY 'password1';
Query OK, 0 rows affected (0.01 sec)

それでは、他のホスト(筆者の環境では"172.17.42.1"からアクセスしてみます)からログインしてみましょう。

$ mysql -h172.17.1.149 -uuser1 -p
Enter password:

mysql> SELECT USER(), CURRENT_USER();
+-------------------+----------------+
| USER()            | CURRENT_USER() |
+-------------------+----------------+
| user1@172.17.42.1 | user1@%        |
+-------------------+----------------+
1 row in set (0.00 sec)

期待した通り、"user1@%"アカウントで認証されました。

混ぜるな危険、匿名アカウントとホスト名ワイルドカード

ここまでは、それぞれ別の機能である匿名アカウントとホスト名部分のワイルドカードを敢えて交じり合わない組み合わせを選んで説明してきました。

それでは混ぜてみましょう。具体的には、先ほどまでと同じMySQLにmysql -uuser1 -hlocalhost -pで接続してみます。

$ mysql -uuser1 -hlocalhost -p
Enter password:
ERROR 1045 (28000): Access denied for user 'user1'@'localhost' (using password: YES)

おや…… "user1@localhost"アカウントの認証に失敗しました。筆者がパスワードを打ち間違えたのでしょうか?

残念ながらそうではありません。コマンドラインクライアントのオプションから-pを抜いて、パスワードなしでログインしようとしてみてください。

$ mysql -uuser1 -hlocalhost

mysql> SELECT USER(), CURRENT_USER();
+-----------------+----------------+
| USER()          | CURRENT_USER() |
+-----------------+----------------+
| user1@localhost | @localhost     |
+-----------------+----------------+
1 row in set (0.00 sec)

なんと、ログインできてしまいました。しかも、クライアントからは"user1@locahost"のつもりで接続していますが、サーバー側では"''@localhost"(匿名アカウント)として認識されています。"user1@%"のパスワード("password1"として設定しました)は"''@localhost"のパスワード(未設定のまま、つまり空文字です)とは違うため、認証に失敗していたのでした。

第17回のMySQLへのログイン試行判定の説明を思い出してください。

MySQLへのログイン試行は、以下のように判定されます。

  1. 接続元ホストの検証
  2. アカウントの検証
  3. パスワードの検証

MySQLはまず最初に接続元ホストの検証を行います。"user1@localhost"の要求に対して"''@localhost"と"user1@%"を比較する際、ホスト名が最長マッチする"''@localhost"が優先されるのです

ホスト名のマッチに失敗して初めて、"user1@%"が評価されるチャンスが回ってきます(ワイルドカード"_"と"%"のマッチ順位は低く設定されています⁠⁠。そのため、そのホストから接続可能な匿名ホストが1つでも存在する場合ホスト名ワイルドカードを期待してログインアカウントを指定する匿名アカウントが優先されてしまい、ホスト名ワイルドカードが使えていないように見えます。

仕組み(接続元ホストの検証が先に行われる)を知っていればこれは当たり前の動作なのですが、MySQL初心者あるあるのハマりどころらしいので、憶えておいて損はないと思います(ハマっている人がいたらぜひ教えてあげてください⁠⁠。

まとめ

MySQLには「ユーザー名を判定せず、接続元ホスト名とパスワードだけで認証する」匿名アカウントと、⁠接続元ホスト名をワイルドカードで表記する」機能があります。これらは本来別の機能ですが、どちらの影響も受けるようなアカウントと接続元の場合は一見意図しない動作となってしまいます。

そもそもユーザー名を判定しない(認証の要素を1つ減らしてしまう)匿名アカウントは利用すべきではありませんが、MySQL 5.6とそれ以前のインストールではこれらが残されている場合があります(MySQL 5.7では匿名アカウントは作成されません⁠⁠。

おすすめ記事

記事・ニュース一覧