GA

2015/06/09

MySQL 5.7.4で導入されたdefault_password_lifetimeがじわじわくる(MySQL 5.7.11でFIX!!)

【2016/01/13 10:12】
MySQL 5.7.11でdefault_password_lifetimeのデフォルトは0に変更になりました!
それ以降のバージョンであればこの記事の内容は気にする必要はありません。

日々の覚書: MySQL 5.7.11でdefault_password_lifetimeのデフォルトが0になるらしい!



TL;DR

default_password_lifetime= 0 を秘伝のmy.cnfに入れておくつもり。



MySQL :: MySQL 5.7 Reference Manual :: 5.1.4 Server System Variables


パラメーターの意味は読んで字のごとく、「最後にパスワードが更新されてからこの期間が経つと、パスワードをEXPIREする」。暗黙のデフォルトは360で、単位は日。つまり何もいじらないと、新規作成したユーザーは360日後にパスワードがEXPIREされてアプリが止まる。。


mysql57> CREATE USER yoku0825 IDENTIFIED WITH mysql_native_password AS '*E74858DB86EBA20BC33D0AECAE8A8108C56B17FA';
Query OK, 0 rows affected (0.03 sec)

mysql57> SELECT user, host, plugin, authentication_string, password_expired, password_last_changed, password_lifetime, account_locked FROM mysql.user WHERE user= 'yoku0825'\G
*************************** 1. row ***************************
                 user: yoku0825
                 host: %
               plugin: mysql_native_password
authentication_string: *E74858DB86EBA20BC33D0AECAE8A8108C56B17FA
     password_expired: N
password_last_changed: 2015-06-09 11:06:33
    password_lifetime: NULL
       account_locked: N
1 row in set (0.00 sec)

$ mysql57 -uyoku0825 -p -e "SELECT current_user()"
Enter password:
+----------------+
| current_user() |
+----------------+
| yoku0825@%     |
+----------------+

こんなユーザーを作ってみる。360日待ってはいられないので、password_last_changedを過去に戻すことで状況を再現させてみる。


mysql57> UPDATE mysql.user SET password_last_changed= '1999-07-31 00:00:00' WHERE user= 'yoku0825'\G
Query OK, 1 row affected (0.07 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql57> SELECT user, host, plugin, authentication_string, password_expired, password_last_changed, password_lifetime, account_locked FROM mysql.user WHERE user= 'yoku0825'\G
*************************** 1. row ***************************
                 user: yoku0825
                 host: %
               plugin: mysql_native_password
authentication_string: *E74858DB86EBA20BC33D0AECAE8A8108C56B17FA
     password_expired: N
password_last_changed: 1999-07-31 00:00:00
    password_lifetime: NULL
       account_locked: N
1 row in set (0.00 sec)

mysql57> FLUSH PRIVILEGES;
Query OK, 0 rows affected (0.04 sec)

$ mysql57 -uyoku0825 -p -e "SELECT current_user()"
Enter password:
ERROR 1862 (HY000): Your password has expired. To log in you must change it using a client that supports expired passwords.

うむ、これはいい。余計なお世話だけど、これはまだいい。
この時にmysql.userの中身を見てみても、


mysql57> SELECT user, host, plugin, authentication_string, password_expired, password_last_changed, password_lifetime, account_locked FROM mysql.user WHERE user= 'yoku0825'\G
*************************** 1. row ***************************
                 user: yoku0825
                 host: %
               plugin: mysql_native_password
authentication_string: *E74858DB86EBA20BC33D0AECAE8A8108C56B17FA
     password_expired: N
password_last_changed: 1999-07-31 00:00:00
    password_lifetime: NULL
       account_locked: N
1 row in set (0.00 sec)

password_expiredは'N'のままだし、password_filetimeはNULLのままだ。

ということは、


mysql57> SET GLOBAL default_password_lifetime= 36500;
Query OK, 0 rows affected (0.00 sec)

$ mysql57 -uyoku0825 -p -e "SELECT current_user()"
Enter password:
+----------------+
| current_user() |
+----------------+
| yoku0825@%     |
+----------------+

mysql57> SET GLOBAL default_password_lifetime= 0;
Query OK, 0 rows affected (0.00 sec)

$ mysql57 -uyoku0825 -p -e "SELECT current_user()"
Enter password:
+----------------+
| current_user() |
+----------------+
| yoku0825@%     |
+----------------+

default_password_lifetimeを大きくしてやればそのまま通るし、default_password_lifetime= 0はデフォルトのライフタイムを無効化する値なのでこちらでも通る。
これ、アカウントの作成時に判定しているのではなくて、ログインのたびに check_password_lifetime関数 の中で判定している。password_lifetimeが設定されていればその値を、NULLならdefault_password_lifetimeを、そして *0なら* 決してEXPIREしない。


mysql57> SELECT @@default_password_lifetime;
+-----------------------------+
| @@default_password_lifetime |
+-----------------------------+
|                         360 |
+-----------------------------+
1 row in set (0.00 sec)

いったんもとに戻して、ALTER USERをいろいろ叩いてみた。
MySQL :: MySQL 5.7 Reference Manual :: 13.7.1.1 ALTER USER Syntax


mysql57> ALTER USER yoku0825 PASSWORD EXPIRE NEVER;
Query OK, 0 rows affected (0.00 sec)

mysql57> SELECT user, host, plugin, authentication_string, password_expired, password_last_changed, password_lifetime, account_locked FROM mysql.user WHERE user= 'yoku0825'\G
*************************** 1. row ***************************
                 user: yoku0825
                 host: %
               plugin: mysql_native_password
authentication_string: *E74858DB86EBA20BC33D0AECAE8A8108C56B17FA
     password_expired: N
password_last_changed: 1999-07-31 00:00:00
    password_lifetime: 0
       account_locked: N
1 row in set (0.00 sec)

$ mysql57 -uyoku0825 -p -e "SELECT current_user()"
Enter password:
+----------------+
| current_user() |
+----------------+
| yoku0825@%     |
+----------------+

EXPIRE NEVERにすると、password_lifetimeカラムの値が0になる。default_password_lifetimeは評価されない。


mysql57> ALTER USER yoku0825 PASSWORD EXPIRE DEFAULT;
Query OK, 0 rows affected (0.00 sec)

mysql57> SELECT user, host, plugin, authentication_string, password_expired, password_last_changed, password_lifetime, account_locked FROM mysql.user WHERE user= 'yoku0825'\G
*************************** 1. row ***************************
                 user: yoku0825
                 host: %
               plugin: mysql_native_password
authentication_string: *E74858DB86EBA20BC33D0AECAE8A8108C56B17FA
     password_expired: N
password_last_changed: 1999-07-31 00:00:00
    password_lifetime: NULL
       account_locked: N
1 row in set (0.00 sec)

$ mysql57 -uyoku0825 -p -e "SELECT current_user()"
Enter password:
ERROR 1862 (HY000): Your password has expired. To log in you must change it using a client that supports expired passwords.

EXPIRE DEFAULTにするとpassword_lifetimeがNULLになり、default_password_lifetimeの影響を受ける。


mysql57> ALTER USER yoku0825 PASSWORD EXPIRE INTERVAL 36000 DAY;
Query OK, 0 rows affected (0.00 sec)

mysql57> SELECT user, host, plugin, authentication_string, password_expired, password_last_changed, password_lifetime, account_locked FROM mysql.user WHERE user= 'yoku0825'\G
*************************** 1. row ***************************
                 user: yoku0825
                 host: %
               plugin: mysql_native_password
authentication_string: *E74858DB86EBA20BC33D0AECAE8A8108C56B17FA
     password_expired: N
password_last_changed: 1999-07-31 00:00:00
    password_lifetime: 36000
       account_locked: N
1 row in set (0.00 sec)

$ mysql57 -uyoku0825 -p -e "SELECT current_user()"
Enter password:
+----------------+
| current_user() |
+----------------+
| yoku0825@%     |
+----------------+

EXPIRE INTERVAL .. DAYでライフタイムを決め打ちすると、そちらが優先される(nullでないのでdafault_password_lifetimeは読まない)


mysql57> ALTER USER yoku0825 PASSWORD EXPIRE;
Query OK, 0 rows affected (0.00 sec)

mysql57> SELECT user, host, plugin, authentication_string, password_expired, password_last_changed, password_lifetime, account_locked FROM mysql.user WHERE user= 'yoku0825'\G
*************************** 1. row ***************************
                 user: yoku0825
                 host: %
               plugin: mysql_native_password
authentication_string: *E74858DB86EBA20BC33D0AECAE8A8108C56B17FA
     password_expired: Y
password_last_changed: 1999-07-31 00:00:00
    password_lifetime: 36000
       account_locked: N
1 row in set (0.00 sec)

$ mysql57 -uyoku0825 -p -e "SELECT current_user()"
Enter password:
ERROR 1862 (HY000): Your password has expired. To log in you must change it using a client that supports expired passwords.

EXPIREだけで他に何もつけない状態だと、password_expiredが'Y'に更新される。この場合、password_last_changedは評価されず(check_password_lifetime関数の中には入るけど、最初のif文ですぐ抜ける)、EXPIRE状態になる。この状態になると、password_expired= 'N'に戻すためにSET PASSWORDで変更するか、UPDATEしてFLUSH PRIVILEGESするしかない。

思いもよらず360日後にこの罠に襲われる人が、1人でも少なくなりますように。


【2015/06/09 18:47】
デフォルトは0にするかワーニングとか出して、ってFeature Requestを上げました。
ダウンロードする時に「No thanks」を知らずにアカウント作っちゃった人とか、"Affects Me"で応援していただけるとありがたいです。

MySQL Bugs: #77277: default_password_lifetime should be set 0 as implicit default value

2015/06/04

MySQL 5.7からデフォルトになるSTRICT_TRANS_TABLEはMyISAMにも影響を及ぼす

恥ずかしながら完全に誤解してた。

MySQL :: MySQL 5.6 Reference Manual :: 5.1.7 Server SQL Modes

For nontransactional tables, the behavior is the same for either mode if the bad value occurs in the first row to be inserted or updated: The statement is aborted and the table remains unchanged. If the statement inserts or modifies multiple rows and the bad value occurs in the second or later row, the result depends on which strict mode is enabled:

For STRICT_ALL_TABLES, MySQL returns an error and ignores the rest of the rows. However, because the earlier rows have been inserted or updated, the result is a partial update. To avoid this, use single-row statements, which can be aborted without changing the table.

For STRICT_TRANS_TABLES, MySQL converts an invalid value to the closest valid value for the column and inserts the adjusted value. If a value is missing, MySQL inserts the implicit default value for the column data type. In either case, MySQL generates a warning rather than an error and continues processing the statement. Implicit defaults are described in Section 11.6, “Data Type Default Values”.


strictとして扱われるかかどうかは (STRICT_ALL_TABLES | STRICT_TRANS_TABLES) で決まり、ストレージエンジンがトランザクション対応かどうかは関係ない。

つまり、

mysql56> CREATE TABLE t1 (val varchar(1)) Engine= MyISAM;
Query OK, 0 rows affected (0.07 sec)

mysql56> SELECT @@sql_mode;
+------------+
| @@sql_mode |
+------------+
|            |
+------------+
1 row in set (0.01 sec)

mysql56> INSERT INTO t1 VALUES ('ab');
Query OK, 1 row affected, 1 warning (0.02 sec)

mysql56> SHOW WARNINGS;
+---------+------+------------------------------------------+
| Level   | Code | Message                                  |
+---------+------+------------------------------------------+
| Warning | 1265 | Data truncated for column 'val' at row 1 |
+---------+------+------------------------------------------+
1 row in set (0.00 sec)

mysql56> SELECT * FROM t1;
+------+
| val  |
+------+
| a    |
+------+
1 row in set (0.01 sec)

sql_modeが空っぽならワーニングで入るけど、


mysql56> CREATE TABLE t2 (val varchar(1)) Engine= MyISAM;
Query OK, 0 rows affected (0.10 sec)

mysql56> SELECT @@sql_mode;
+---------------------+
| @@sql_mode          |
+---------------------+
| STRICT_TRANS_TABLES |
+---------------------+
1 row in set (0.00 sec)

mysql56> INSERT INTO t2 VALUES ('ab');
ERROR 1406 (22001): Data too long for column 'val' at row 1

mysql56> SELECT * FROM t2;
Empty set (0.00 sec)

STRICT_TRANS_TABLESだと入らない。


更に、

mysql56> INSERT INTO t3 VALUES ('a'), ('ab'), ('abc');
Query OK, 3 rows affected, 2 warnings (0.01 sec)
Records: 3  Duplicates: 0  Warnings: 2

mysql56> SHOW WARNINGS;
+---------+------+-----------------------------------------+
| Level   | Code | Message                                 |
+---------+------+-----------------------------------------+
| Warning | 1406 | Data too long for column 'val' at row 2 |
| Warning | 1406 | Data too long for column 'val' at row 3 |
+---------+------+-----------------------------------------+
2 rows in set (0.01 sec)

mysql56> SELECT * FROM t3;
+------+
| val  |
+------+
| a    |
| a    |
| a    |
+------+
3 rows in set (0.01 sec)

1個目が正しいデータで、2個目移行が間違ったデータを突っ込もうとすると、warningが起こるがINSERTはされる。


mysql56> INSERT INTO t4 VALUES ('abc'), ('ab'), ('a');
ERROR 1406 (22001): Data too long for column 'val' at row 1

mysql56> SELECT * FROM t4;
Empty set (0.00 sec)

1個目が正しくなかった場合、あとに正しいデータが入っていてもそれもabortされる。


STRICT_TRANS_TABLESは操作対象がトランザクション対応の時にstrict_modeになって、トランザクション非対応な時はnon-strict_modeだと完全に誤解していた。。

2015/06/02

MySQL 5.7時代のユーザー作成について

日々の覚書: MySQL 5.7.6でCREATE USERせずにGRANTステートメントを叩くとワーニング で、結局どうすればいいのか全く書いてなかったので書き直し。

* パスワード未設定のユーザーをGRANTで作成できなくなった。
* CREATE USERでユーザー作ってからGRANTする。

mysql57> GRANT ALL ON db.* TO grant_style@localhost; -- ユーザー未作成, パスワード未指定のGRANTが転ける
ERROR 1133 (42000): Can't find any matching row in the user table

mysql57> CREATE USER create_style@localhost; -- パスワード未指定のCREATE USERは通る
Query OK, 0 rows affected (0.00 sec)

mysql57> GRANT ALL ON db.* TO create_style@localhost; -- ユーザーが存在するとGRANTが通る
Query OK, 0 rows affected (0.01 sec)


* GRANT .. IDENTIFIED BY '..'('..'は生パスワード)の構文だとワーニングが出る。
* ワーニングは出るけれど、取り敢えずまだ使える。
* ↑と同じで推奨方法は CREATE USERで作ってからGRANT。


mysql57> GRANT ALL ON db.* TO grant_style@localhost IDENTIFIED BY 'test';
Query OK, 0 rows affected, 1 warning (0.01 sec)

mysql57> SHOW WARNINGS;
+---------+------+------------------------------------------------------------------------------------------------------------------------------------+
| Level   | Code | Message                                                                                                                            |
+---------+------+------------------------------------------------------------------------------------------------------------------------------------+
| Warning | 1287 | Using GRANT for creating new user is deprecated and will be removed in future release. Create new user with CREATE USER statement. |
+---------+------+------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

mysql57> CREATE USER create_style@localhost IDENTIFIED BY 'test';
Query OK, 0 rows affected (0.01 sec)

mysql57> GRANT ALL ON db.* TO create_style@localhost;
Query OK, 0 rows affected (0.04 sec)


* IDENTIFIED BY PASSWORD '*..'('*..'は41桁ハッシュされたパスワード)の構文でもワーニングが出る。
* ワーニングは出るけれど取り敢えずまだ使える。
* IDENTIFIED WITH mysql_native_password AS '*..'構文を使う(Authentication Pluginとか使ってて他のプラグイン使う場合はプラグイン名読み替える)
* 最終的な推奨としては、CREATE USER .. IDENTIFIED WITH mysql_native_password AS '*..'
* ちなみにASはBYでもいいらしい。ワーニングメッセージに合わせてみただけ。

mysql57> GRANT ALL ON db.* TO grant_style@localhost IDENTIFIED BY PASSWORD '*94BDCEBE19083CE2A1F959FD02F964C7AF4CFC29';
Query OK, 0 rows affected, 2 warnings (0.01 sec)

mysql57> SHOW WARNINGS;
+---------+------+---------------------------------------------------------------------------------------------------------------------------------------+
| Level   | Code | Message                                                                                                                               |
+---------+------+---------------------------------------------------------------------------------------------------------------------------------------+
| Warning | 1287 | 'IDENTIFIED BY PASSWORD' is deprecated and will be removed in a future release. Please use IDENTIFIED WITH <plugin> AS <hash> instead |
| Warning | 1287 | Using GRANT for creating new user is deprecated and will be removed in future release. Create new user with CREATE USER statement.    |
+---------+------+---------------------------------------------------------------------------------------------------------------------------------------+
2 rows in set (0.00 sec)

mysql57> GRANT ALL ON db.* TO grant_style@localhost IDENTIFIED WITH mysql_native_password AS '*94BDCEBE19083CE2A1F959FD02F964C7AF4CFC29';
Query OK, 0 rows affected, 1 warning (0.00 sec)

mysql57> SHOW WARNINGS;
+---------+------+------------------------------------------------------------------------------------------------------------------------------------+
| Level   | Code | Message                                                                                                                            |
+---------+------+------------------------------------------------------------------------------------------------------------------------------------+
| Warning | 1287 | Using GRANT for creating new user is deprecated and will be removed in future release. Create new user with CREATE USER statement. |
+---------+------+------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

mysql57> CREATE USER create_style@localhost IDENTIFIED BY PASSWORD '*94BDCEBE19083CE2A1F959FD02F964C7AF4CFC29';
Query OK, 0 rows affected, 1 warning (0.01 sec)

mysql57> SHOW WARNINGS;
+---------+------+---------------------------------------------------------------------------------------------------------------------------------------+
| Level   | Code | Message                                                                                                                               |
+---------+------+---------------------------------------------------------------------------------------------------------------------------------------+
| Warning | 1287 | 'IDENTIFIED BY PASSWORD' is deprecated and will be removed in a future release. Please use IDENTIFIED WITH <plugin> AS <hash> instead |
+---------+------+---------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)


特にバージョンをまたいでユーザーを移す時など、わたしは SHOW GRANTSの出力をほげほげして食わせるのが大好き なんですが、MySQL 5.7未満 => 5.7より未来でdeprecatedからremovedになると、これが出来なくなるんだよなぁ。。

$ client/mysqldump --grants -S /usr/mysql/5.6.25/data/mysql.sock --all-databases -x
..
--
-- Dumping grants
--
GRANT USAGE ON *.* TO 'create_style'@'localhost' IDENTIFIED BY PASSWORD '*94BDCEBE19083CE2A1F959FD02F964C7AF4CFC29'; -- これがワーニング。
GRANT ALL PRIVILEGES ON `db`.* TO 'create_style'@'localhost';
GRANT USAGE ON *.* TO 'grant_style'@'localhost'; -- これはパスワードなしでエラー。
GRANT ALL PRIVILEGES ON `db`.* TO 'grant_style'@'localhost';
GRANT ALL PRIVILEGES ON *.* TO 'root'@'127.0.0.1' WITH GRANT OPTION; -- これもエラー。
GRANT ALL PRIVILEGES ON *.* TO 'root'@'::1' WITH GRANT OPTION; -- これもエラー。

( ´-`).oO(パスワード設定してないのが悪いだけか。


更に5.7からはSHOW GRANTSの結果にハッシュ済みのパスワードを含めてくれなくなったので、

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

mysql57> SELECT authentication_string FROM mysql.user WHERE (user, host)= ('create_style', 'localhost');
+-------------------------------------------+
| authentication_string                     |
+-------------------------------------------+
| *94BDCEBE19083CE2A1F959FD02F964C7AF4CFC29 |
+-------------------------------------------+
1 row in set (0.00 sec)


お手製の mysqldump --grantsパッチ も使えなくなってしまうのだなぁ。。

2015/06/01

何も考えずに真っ新なCentOS 6.6にMySQL 5.7をyumで叩き込むメモ

主にバグの再現確認に使う用途。yumでもいいからクリーンな状態のCentOS 6.6にMySQL 5.7を入れたいときの。

TL;DR

コマンドはこれ。

$ sudo yum install -y https://dev.mysql.com/get/mysql-community-release-el6-5.noarch.rpm
$ sudo yum install -y --enablerepo=mysql57-community-dmr mysql-community-server
$ sudo service mysqld start
$ sudo grep password /var/log/mysqld.log
$ mysql -uroot -p


【2015/11/05 10:05】
MySQL 5.7のGAリリースに伴い、mysql-community-releaseが新しくなっています。
2015/11/05現在だと、mysql-community-releaseは mysql57-community-release-el6-7.noarch.rpm になっており、こちらはデフォルトでmysql57-communityリポジトリーが有効になっています(ので、--enablerepoいらない)



1. MySQL :: Download MySQL Yum Repository からCentOS 6.x用(いや、本当はRHEL/Oracle Linux用のだけど)のrpmパッケージをインストールする(と、2015/06/01現在では↓の5つのリポジトリーが登録される)

| repository                 | enabled |
|----------------------------|---------|
| mysql-connectors-community | yes     |
| mysql-tools-community      | yes     |
| mysql55-community          | no      |
| mysql56-community          | yes     |
| mysql57-community-dmr      | no      |


2. enabled= noになっているmysql57-community-dmrを--enablerepoで指定してmysql-community-serverをインストールする。


3. mysqldサービスを起動(今までOracleビルドのMySQLのrpmパッケージ .. ファイル名が MySQL-server-*.rpm になっているようなやつ .. はサービス名が "mysql" だったのが、yumリポジトリーの場合は"mysqld"になっている。たぶん、過去のRHELのBaseリポジトリーと命名規則を合わせたんではないだろうか)

/var/lib/mysqlはこの時点でmysqld --initializeで初期化される。MySQL-server-*.rpmはインストール時に初期化(= 5.6までしかMySQL-server-*.rpmは存在しないので、5.6当時でmysql_install_db)が走るが、mysql-community-server-*.rpmはservice mysqld startの中でdatadirの中身をチェックし、空っぽならば初期化が走るようになっている。


4. mysqld --initialize ということは、だ。初期パスワードは /root/.mysql_secret には *ない* 。エラーログに吐いている。デフォルトでは/var/log/mysqld.logだ。罠い。
日々の覚書: MySQL 5.7.6でデータベースの初期化が変わる mysql_install_dbからmysqld --initialize


$ sudo service mysqld start
Initializing MySQL database:  2015-06-01T09:35:30.031754Z 0 [Warning] TIMESTAMP with implicit DEFAULT value is deprecated. Please use --explicit_defaults_for_timestamp server option (see documentation for more details).
                                                           [  OK  ]
Starting mysqld:

デフォルトパスワードの在り処なんてビタ1文字何も教えてくれない。罠い。


$ grep password /var/log/mysqld.log
2015-06-01T09:35:31.070842Z 1 [Warning] A temporary password is generated for root@localhost: l#KCEX1;iypk
2015-06-01T09:35:37.208351Z 2 [Note] Access denied for user 'UNKNOWN_MYSQL_USER'@'localhost' (using password: NO)

そしてなんか謎のUNKWOWN_MYSQL_USERとかいうのがログイン試行してる。謎い。


110         # Spin for a maximum of N seconds waiting for the server to come up;
    111         # exit the loop immediately if mysqld_safe process disappears.
    112         # Rather than assuming we know a valid username, accept an "access
    113         # denied" response as meaning the server is functioning.
    114         ret=0
    115         TIMEOUT="$STARTTIMEOUT"
    116         while [ $TIMEOUT -gt 0 ]; do
    117             RESPONSE=$(/usr/bin/mysqladmin --no-defaults --socket="$adminsocket" --user=UNKNOWN_MYSQL_USER ping 2>&1)         && break
    118             echo "$RESPONSE" | grep -q "Access denied for user" && break
    119             if ! /bin/kill -0 $safe_pid 2>/dev/null; then
    120                 echo "MySQL Daemon failed to start."
    121                 ret=1
    122                 break
    123             fi
    124             sleep 1
    125             let TIMEOUT=${TIMEOUT}-1
    126         done

/etc/init.d/mysqldの中身を覗いてみると、ちゃんと起動したかどうかを敢えてエラーになりそうなユーザーでmysqladmin pingして"Access denied"が返されることで起動したことを確認しているぽい。


でもこれ、UNKNWON_MYSQL_USER@localhostがいたら成功しちゃって"Access denied"が返らないんじゃね? と思ったら


mysql> create user UNKNOWN_MYSQL_USER;
ERROR 1470 (HY000): String 'UNKNOWN_MYSQL_USER' is too long for user name (should be no longer than 16)

長さ制限に引っかかってそんなユーザーは作れなかった。なるほど。


何の話だかよくわからなくなってきたのでこれまで。

2015/05/28

1つのbasedirに複数のMroongaさんをぶら下げる

複数バージョンのGroonga / Mroongaで挙動の違いを調べる時に、いちいち/usr/local/mysqlを複数作るのが面倒なので手順をメモ。

コマンドの羅列はここ。 https://gist.github.com/yoku0825/a85643cd9b5a4dcd8e1c

mysqldにINSTALL PLUGINする場合、SONAMEで指定されたファイルをplugin_dirから読み出すので、plugin_dir(暗黙のデフォルトはbasedir/lib/plugin)  だけを打ち分けてやればOKなはず。


$ wget http://dev.mysql.com/get/Downloads/MySQL-5.6/mysql-5.6.24-linux-glibc2.5-x86_64.tar.gz
$ tar xzf mysql-5.6.24-linux-glibc2.5-x86_64.tar.gz
$ sudo ln -s $PWD/mysql-5.6.24-linux-glibc2.5-x86_64 /usr/local/mysql
$ wget http://dev.mysql.com/get/Downloads/MySQL-5.6/mysql-5.6.24.tar.gz
$ tar xzf mysql-5.6.24.tar.gz

$ sudo useradd mysql

ここまではフツウ。


$ sudo mkdir /usr/local/mysql/{stable_lib,nightly_lib}

plugin_dir用のディレクトリを2つ作ってやる。


$ wget http://packages.groonga.org/source/groonga/groonga-5.0.3.tar.gz
$ tar xzf groonga-5.0.3.tar.gz
$ cd groonga-5.0.3
$ ./configure --prefix=/usr/local/groonga_stable
$ make
$ sudo make install
$ cd ../

$ wget http://packages.groonga.org/nightly/groonga-5.0.3.2015.05.28.tar.gz
$ tar xzf groonga-5.0.3.2015.05.28.tar.gz
$ cd groonga-5.0.3.2015.05.28
$ ./configure --prefix=/usr/local/groonga_nightly
$ make
$ sudo make install
$ cd ../

2つのGroongaを違うディレクトリーにインストールして、


$ wget http://packages.groonga.org/source/groonga-normalizer-mysql/groonga-normalizer-mysql-1.0.9.tar.gz
$ tar xzf groonga-normalizer-mysql-1.0.9.tar.gz
$ cd groonga-normalizer-mysql-1.0.9
$ ./configure --prefix=/usr/local/groonga_stable PKG_CONFIG_PATH=/usr/local/groonga_stable/lib/pkgconfig
$ make
$ sudo make install
$ make clean

$ ./configure --prefix=/usr/local/groonga_nightly PKG_CONFIG_PATH=/usr/local/groonga_nightly/lib/pkgconfig
$ make
$ sudo make install
$ cd ../

$ wget http://packages.groonga.org/source/mroonga/mroonga-5.02.tar.gz
$ tar xzf mroonga-5.02.tar.gz
$ cd mroonga-5.02
$ ./configure --prefix=/usr/local/groonga_stable --with-mysql-source=../mysql-5.6.24 --with-mysql-config=/usr/local/mysql/bin/mysql_config PKG_CONFIG_PATH=/usr/local/groonga_stable/lib/pkgconfig
$ sed -ib 's|/usr/local/mysql/lib/plugin|/usr/local/mysql/stable_lib|' Makefile
$ make
$ sudo make install
$ make clean

$ ./configure --prefix=/usr/local/groonga_nightly --with-mysql-source=../mysql-5.6.24 --with-mysql-config=/usr/local/mysql/bin/mysql_config PKG_CONFIG_PATH=/usr/local/groonga_nightly/lib/pkgconfig
$ sed -ib 's|/usr/local/mysql/lib/plugin|/usr/local/mysql/nightly_lib|' Makefile
$ make
$ sudo make install
$ cd ..

groonga-normalizer-mysqlとMroongaを、PKG_CONFIG_PATHの値を書き換えつつmakeしてインストールする。
Mroongaのconfigureスクリプトの中でha_mroonga.soをインストールする先がmysql_config --pluginから取られているので、これをMakefileの中でいじってからmakeしてやると、それぞれのplugin_dirにインストールされます。手でコピーしてもいいです。


$ cd /usr/local/mysql
$ scripts/mysql_install_db --user=mysql --datadir=./stable_data
$ bin/mysqld_safe --no-defaults --user=mysql --datadir=./stable_data --socket=/tmp/stable.sock --port=13306 --plugin-dir=/usr/local/mysql/stable_lib &

$ scripts/mysql_install_db --user=mysql --datadir=./nightly_data
$ bin/mysqld_safe --no-defaults --user=mysql --datadir=./nightly_data --socket=/tmp/nightly.sock --port=23306 --plugin-dir=/usr/local/mysql/nightly_lib &

$ alias stable="/usr/local/mysql/bin/mysql -uroot -S /tmp/stable.sock -P 13306"
$ alias nightly="/usr/local/mysql/bin/mysql -uroot -S /tmp/nightly.sock -P 23306"

$ stable < /usr/local/groonga_stable/share/mroonga/install.sql
$ stable -e "create database d1"
$ stable stable < data.sql

$ nightly < /usr/local/groonga_stable/share/mroonga/install.sql
$ nightly -e "create database d1"
$ nightly nightly < data.sql

$ stable -e "select @@plugin_dir"
+------------------------------+
| @@plugin_dir                 |
+------------------------------+
| /usr/local/mysql/stable_lib/ |
+------------------------------+

$ nightly -e "select @@plugin_dir"
+-------------------------------+
| @@plugin_dir                  |
+-------------------------------+
| /usr/local/mysql/nightly_lib/ |
+-------------------------------+

$ ldd /usr/local/mysql/*_lib/ha_mroonga.so
/usr/local/mysql/nightly_lib/ha_mroonga.so:
        linux-vdso.so.1 =>  (0x00007fffeaf76000)
        libgroonga.so.0 => /usr/local/groonga_nightly/lib/libgroonga.so.0 (0x00007f00adc09000)
        libstdc++.so.6 => /usr/lib64/libstdc++.so.6 (0x00007f00ad8f7000)
        libm.so.6 => /lib64/libm.so.6 (0x00007f00ad673000)
        libc.so.6 => /lib64/libc.so.6 (0x00007f00ad2df000)
        libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007f00ad0c8000)
        libz.so.1 => /lib64/libz.so.1 (0x00007f00aceb2000)
        libdl.so.2 => /lib64/libdl.so.2 (0x00007f00accae000)
        librt.so.1 => /lib64/librt.so.1 (0x00007f00acaa5000)
        /lib64/ld-linux-x86-64.so.2 (0x0000003517400000)
        libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f00ac888000)
/usr/local/mysql/stable_lib/ha_mroonga.so:
        linux-vdso.so.1 =>  (0x00007fff6c7ff000)
        libgroonga.so.0 => /usr/local/groonga_stable/lib/libgroonga.so.0 (0x00007f13f4307000)
        libstdc++.so.6 => /usr/lib64/libstdc++.so.6 (0x00007f13f3ff5000)
        libm.so.6 => /lib64/libm.so.6 (0x00007f13f3d71000)
        libc.so.6 => /lib64/libc.so.6 (0x00007f13f39dd000)
        libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007f13f37c6000)
        libz.so.1 => /lib64/libz.so.1 (0x00007f13f35b0000)
        libdl.so.2 => /lib64/libdl.so.2 (0x00007f13f33ac000)
        librt.so.1 => /lib64/librt.so.1 (0x00007f13f31a3000)
        /lib64/ld-linux-x86-64.so.2 (0x0000003517400000)
        libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f13f2f86000)

あとはdatadirをそれぞれ用意してやって、データを流し込めばおしまいと。
mysqld_multiを使って起動するならこんな感じのmy.cnfにしておく。


$ sudo vim /etc/my.cnf
[mysqld_multi]
no-log

[mysqld1]
basedir   = /usr/local/mysql
mysqld    = /usr/local/mysql/bin/mysqld_safe
datadir   = /usr/local/mysql/stable_data
socket    = /tmp/stable.sock
port      = 13006
plugin-dir= /usr/local/mysql/stable_lib

[mysqld2]
basedir   = /usr/local/mysql
mysqld    = /usr/local/mysql/bin/mysqld_safe
datadir   = /usr/local/mysql/nightly_data
socket    = /tmp/nightly.sock
port      = 23006
plugin-dir= /usr/local/mysql/nightly_lib

$ mysqld_multi start 1,2
mysqld_multi log file version 2.16; run: Thu May 28 15:39:09 2015

Starting MySQL servers
150528 15:39:09 mysqld_safe Adding '/usr/lib64/libjemalloc.so.1' to LD_PRELOAD for mysqld
150528 15:39:09 mysqld_safe Adding '/usr/lib64/libjemalloc.so.1' to LD_PRELOAD for mysqld
150528 15:39:09 mysqld_safe Logging to '/usr/local/mysql/stable_data/error.log'.
150528 15:39:09 mysqld_safe Logging to '/usr/local/mysql/nightly_data/error.log'.
150528 15:39:09 mysqld_safe Starting mysqld daemon with databases from /usr/local/mysql/stable_data
150528 15:39:09 mysqld_safe Starting mysqld daemon with databases from /usr/local/mysql/nightly_data

これであとはやりたい放題。

2015/05/18

MySQL 5.6以降でクライアントに "Warning: Using a password on the command line interface can be insecure." と言われるのを防ぐいくつかの方法

MySQL 5.6以降では、同梱のクライアント(mysqlコマンドラインクライアント, mysqladmin, mysqldumpあたりがよくありそう)に-pオプションで直接パスワードを渡すと、


$ mysql56 -utest -ptest -e "SELECT current_user()"
Warning: Using a password on the command line interface can be insecure.
+----------------+
| current_user() |
+----------------+
| test@localhost |
+----------------+

こんなのが出るようになりました。
読んで字のごとく、パスワードをコマンドラインインターフェイス(つまり-pオプション)で渡すのはセキュアじゃねーぞ、というワーニングです。
これ、標準エラー出力に吐くんですよね。

標準エラー出力に吐くということは、


$ crontab -l
1 3 * * * /usr/mysql/5.6.24/bin/mysqladmin -uflush -pxxxx flush-tables

週末に仕込んだこんなcronのエントリーがぼんぼんぼんぼんメールを投げてくるわけです。午前3時に。インセキュアだぞって。 '`,、'`,、( ´∀`)'`,、'`,、


さてこのメッセージ、mysys_ssl/my_getopt.cc にハードコードされています。エラーログドリブン ソースコードリーディング ですね。


653 /**
654  * This function should be called to print a warning message
655  * if password string is specified on the command line.
656  */
657
658 void print_cmdline_password_warning()
659 {
660   static my_bool password_warning_announced= FALSE;
661
662   if (!password_warning_announced)
663   {
664     fprintf(stderr, "Warning: Using a password on the command line "
665             "interface can be insecure.\n");
666     (void) fflush(stderr);
667     password_warning_announced= TRUE;
668   }
669 }

mysql-server/my_getopt.cc at cac6fc837a5f72203058e4acc6b8b4dba8a98294 · mysql/mysql-server


( ´-`).oO(password_warning_announcedってここ以外で使ってないので、if文なしでいきなりfprintfでいいんじゃないかって気がするんだけども。


これを出力させないように、いくつかの方法を考えてみました。セキュアかどうかは別問題です。出力させないことが大事です。


1) コマンドラインオプションから渡さずに、my.cnf系(~/.my.cnfとか、~/.mylogin.cnfも含む)で渡す。

5.6からは mysql_config_editor がありますね。かつてのわたしのお気に入りでした。日々の覚書: MySQL5.6 .mylogin.cnfで遊んでみる


$ mysql_config_editor set --login-path=mysql --user=test --password
Enter password:

$ mysql56 -e "SELECT current_user()"
+----------------+
| current_user() |
+----------------+
| test@localhost |
+----------------+

使 っ て ま せ ん '`,、'`,、( ´∀`)'`,、'`,、


MySQL 5.6にできたサーバーと、まだ5.6未満で稼動しているサーバーでスクリプト作り分けるのが めんどくさい 管理が煩雑になってしまうのが嫌。if文書きたくない。~/.my.cnfなら5.6未満でも使えますが、シェルスクリプトが ~/.my.cnf に依存するようになるので管理対象が増えて嫌。

あと.mylogin.cnf別にセキュアじゃなかったし => セキュアそうでセキュアじゃない少しセキュアな気分になれるmysql_config_editor , ~/.my.cnfだって平文で書くからセキュアじゃないし。


2) MYSQL_PWD環境変数を使ってパスワードを渡す。

これ知らない人多そう。MYSQL_PWD環境変数が設定されていて かつ -pオプションが指定されていない場合、MYSQL_PWD環境変数の中身がパスワードとして扱われます。つまりがパスワード未設定時のデフォルト値をオーバーライドしてくれる。

というわけでこれを使うと


$ MYSQL_PWD="test" mysql56 -utest -e "SELECT current_user()"
+----------------+
| current_user() |
+----------------+
| test@localhost |
+----------------+

全然セキュアになってませんが、インセキュアだと怒られることはなくなります。管理対象が増えるわけでなくてもとのcronエントリーを


$ crontab -l
1 3 * * * MYSQL_PWD=xxxx /usr/mysql/5.6.24/bin/mysqladmin -uflush flush-tables

とだけ書き換えればいいですね。らくちん。


3) mysqlコマンドラインクライアントだけダウングレードする。

yumでrpmを突っ込んでいる環境だとつらい気がしますが、バイナリーやソースコードから突っ込んでいるならこれも手です。
5.5 => 5.6のmysqlコマンドラインクライアントの新機能ってhistignoreや.mylogin.cnf対応くらいじゃないかと思うので(5.6 => 5.7はCtrl + C対応とsyslogがある)、別に最新じゃなくてもいいやって割り切りは十分アリじゃないかと思います。


4) ほげる。

オチは想像がついていると思いますが、print_cmdline_password_warningをほげります。もうバッサリreturn (void) 0; だけでいいんじゃないかな。

$ diff -c mysys_ssl/my_getopt.cc{.orig,}
*** mysys_ssl/my_getopt.cc.orig 2015-03-26 01:34:52.000000000 +0900
--- mysys_ssl/my_getopt.cc      2015-05-18 12:35:56.314531829 +0900
***************
*** 657,671 ****

  void print_cmdline_password_warning()
  {
!   static my_bool password_warning_announced= FALSE;
!
!   if (!password_warning_announced)
!   {
!     fprintf(stderr, "Warning: Using a password on the command line "
!             "interface can be insecure.\n");
!     (void) fflush(stderr);
!     password_warning_announced= TRUE;
!   }
  }


--- 657,663 ----

  void print_cmdline_password_warning()
  {
!   return (void) 0;
  }


$ client/mysql -utest -ptest -S /usr/mysql/5.6.24/data/mysql.sock -e "SELECT current_user()"
+----------------+
| current_user() |
+----------------+
| test@localhost |
+----------------+

ちょっと別件でmysqlコマンドラインクライアントをほげってrpmにすることが決まってるので、まさかの4) を採用するかも知れません。

2015/05/11

MySQL 5.7.5からはmysql.sock.lockというのを作るようになった

A server could have its socket file taken over by a second server listening on different TCP/IP port but the same socket file. The socket file also would be unlinked by normal shutdown of the second server. To avoid this, the server now uses a lock file with the same name as the socket file and a .lock suffix. (For example, /tmp/mysql.sock has a lock file of /tmp/mysql.sock.lock.) The lock file contains the process ID of the server process that created the socket file. (Bug #17286856)
MySQL :: MySQL 5.7 Release Notes :: Changes in MySQL 5.7.5 (2014-09-25, Milestone 15)

やっぱり誰しも、--socketの設定間違えてUNIXソケットつぶしたことありますよねわかりますん! orz



$ bin/mysqld --no-defaults --initialize --datadir=/tmp/mysql1
2015-05-10T17:04:32.148319Z 0 [Warning] TIMESTAMP with implicit DEFAULT value is deprecated. Please use --explicit_defaults_for_timestamp server option (see documentation for more details).
2015-05-10T17:04:32.526383Z 0 [Warning] InnoDB: New log files created, LSN=45790
2015-05-10T17:04:32.562985Z 0 [Warning] InnoDB: Creating foreign key constraint system tables.
2015-05-10T17:04:32.622323Z 0 [Warning] Failed to setup SSL
2015-05-10T17:04:32.622341Z 0 [Warning] SSL error: SSL context is not usable without certificate and private key
2015-05-10T17:04:32.622849Z 1 [Warning] A temporary password is generated for root@localhost: gOxQq0_u2l)B

$ bin/mysqld --no-defaults --socket=/tmp/mysql.sock --port=3306 --datadir=/tmp/mysql1
2015-05-10T17:04:54.881779Z 0 [Warning] TIMESTAMP with implicit DEFAULT value is deprecated. Please use --explicit_defaults_for_timestamp server option (see documentation for more details).
2015-05-10T17:04:54.881843Z 0 [Warning] Insecure configuration for --secure-file-priv: Current value does not restrict location of generated files. Consider setting it to a valid, non-empty path.
2015-05-10T17:04:54.881879Z 0 [Note] bin/mysqld (mysqld 5.7.7-rc) starting as process 22478 ...
..
Version: '5.7.7-rc'  socket: '/tmp/mysql.sock'  port: 3306  Source distribution

$ ll /tmp/mysql.sock*
srwxrwxrwx 1 yoku0825 yoku0825 0 May 11 02:04 /tmp/mysql.sock
-rw------- 1 yoku0825 yoku0825 6 May 11 02:04 /tmp/mysql.sock.lock

$ cat /tmp/mysql.sock.lock
22478

こうして起動したmysqldと
(ちなみに--no-defaultsで--log-timestamps=SYSTEMが打ち消されているので、ログの時間(UTF)とファイルシステムの時間(JST)が盛大にズレて見えている)
See also, 日々の覚書: MySQL 5.7で絶対に必要になると思うlog_timestampsの設定


$ bin/mysqld --no-defaults --initialize --datadir=/tmp/mysql2
2015-05-10T17:06:46.667942Z 0 [Warning] TIMESTAMP with implicit DEFAULT value is deprecated. Please use --explicit_defaults_for_timestamp server option (see documentation for more details).
2015-05-10T17:06:47.268897Z 0 [Warning] InnoDB: New log files created, LSN=45790
2015-05-10T17:06:47.307634Z 0 [Warning] InnoDB: Creating foreign key constraint system tables.
2015-05-10T17:06:47.367505Z 0 [Warning] Failed to setup SSL
2015-05-10T17:06:47.367526Z 0 [Warning] SSL error: SSL context is not usable without certificate and private key
2015-05-10T17:06:47.368373Z 1 [Warning] A temporary password is generated for root@localhost: au*pq3)kf6oR

$ bin/mysqld --no-defaults --socket=/tmp/mysql.sock --port=13306 --datadir=/tmp/mysql2
2015-05-10T17:07:18.521509Z 0 [Warning] TIMESTAMP with implicit DEFAULT value is deprecated. Please use --explicit_defaults_for_timestamp server option (see documentation for more details).
2015-05-10T17:07:18.521566Z 0 [Warning] Insecure configuration for --secure-file-priv: Current value does not restrict location of generated files. Consider setting it to a valid, non-empty path.
2015-05-10T17:07:18.521601Z 0 [Note] bin/mysqld (mysqld 5.7.7-rc) starting as process 22603 ...
..
2015-05-10T17:07:18.648282Z 0 [ERROR] Another process with pid 22478 is using unix socket file.
2015-05-10T17:07:18.648287Z 0 [ERROR] Unable to setup unix socket lock file.
2015-05-10T17:07:18.648301Z 0 [ERROR] Aborting

$ ll /tmp/mysql.sock*
srwxrwxrwx 1 yoku0825 yoku0825 0 May 11 02:04 /tmp/mysql.sock
-rw------- 1 yoku0825 yoku0825 6 May 11 02:04 /tmp/mysql.sock.lock

$ cat /tmp/mysql.sock.lock
22478

datadir, portをずらしてsocketだけ一致させたmysqldを起動しようとすると、後から起動しようとした方はスタートアップ処理でmysql.sock.lockの存在を検知してAbortしてくれる。
なんか導入直後の5.7.5で試した時に転けるはいいけどmysql.sockはやっぱり消しちゃうみたいなダメな動作だった気がしていたんだけど、少なくとも5.7.7では正しく動作している。