MySQL 8.0.14 でバイナリログ・リレーログを暗号化する

2019/01/21（公式発表）に MySQL 8.0.14 がリリースされました。
おもちゃを手にした子供の如く、早速 MySQL おじさんたちが新機能をキャッキャしながら試しています（？）が、わたしの場合はやっぱり暗号化（Qiita 最初の記事も TDE ですし）、ということでバイナリログ・リレーログの暗号化を試してみました。

# バイナリログ・リレーログの暗号化について

MySQL 8.0.14 のリリースノートに概要が書かれています。

 * **[Logging Notes - Changes in MySQL 8.0.14 (2019-01-21, General Availability)](https://dev.mysql.com/doc/relnotes/mysql/8.0/en/news-8-0-14.html#mysqld-8-0-14-logging)（version 8.0 
MySQL 8.0 Release Notes）**

これによると、

 * `binlog_encryption`を`ON`にするとバイナリログとリレーログ（レプリケーションスレーブ側のログ）が暗号化される
 * この項目はサーバ起動中に`SET`で動的に`ON`／`OFF`ができる
 * `ON`／`OFF`を切り替えると記録中のログがローテーションされる
 * バイナリログ・リレーログは 256 ビット AES で暗号化され、バイナリログ用のマスターキーはテーブルスペース暗号化用の鍵とは別に生成される（自動生成されるため、マニュアルでの設定はできない）
 * `binlog_rotate_encryption_master_key_at_startup`を`ON`にすると、サーバを起動する度にバイナリログ用のマスターキーがローテーションされる
 * この指定をしても、通信経路上の暗号化は行われないので、そちらは通信の TLS 化設定で対応する
 * `SHOW BINARY LOGS`でバイナリログが暗号化されているかどうかを確認できる

ということのようです。

※テーブルスペース等の暗号化については、以下を参照してください。

 * **[MySQL 8.0.16 でテーブルスペース・REDO ログ／UNDO ログ・システムテーブル暗号化](https://qiita.com/hmatsu47/items/bae53fd0f6d09511732c)**
 * **[MySQL 8.0.13 で一般テーブルスペース（General Tablespace）を暗号化する](https://qiita.com/hmatsu47/items/f3519532c134ba0018af)**

---

**2019/01/24 追記：**

テーブルスペース等の暗号化と同様、バイナリログの暗号化にもキーリング用プラグインの導入が必要です。
設定については↑のテーブルスペース暗号化＋REDOログ／UNDOログ暗号化の記事を参照してください。

---

# 試してみる

Master／Slave 構成で試してみます。

まずは、それぞれのサーバの`/etc/my.cnf`から。
なお、暗号化されたかどうかの判別がしやすいように、バイナリログのフォーマットを`ROW`から`MIXED`に変更しています。

 * **Master 側**

```bash:/etc/my.cnf（関連部分のみ）
server-id=1
binlog_format=MIXED
binlog_encryption=ON
binlog_rotate_encryption_master_key_at_startup=ON
```

 * **Slave 側**

```bash:/etc/my.cnf（同上）
server-id=2
binlog_format=MIXED
binlog_encryption=ON
binlog_rotate_encryption_master_key_at_startup=ON
read_only
#↑super_read_onlyのほうが良い
#skip-slave-start
```

**注：**
Slave を Master のディスクイメージからコピーして立てた場合、データディレクトリにある`auto.cnf`の`server-uuid`が重複するため、レプリケーション時にエラーが出ます。

```text:スレーブステータスで表示されるエラー
Fatal error: The slave I/O thread stops because master and slave have equal MySQL server UUIDs; these UUIDs must be different for replication to work.
```

この場合、`auto.cnf`を削除してからサーバを起動すると、`server-uuid`が自動生成されます。

また、システムテーブル等の一部が変更されていますので、MySQL 8.0.13（またはそれ以前のバージョン）から 8.0.14 にバージョンアップしたときは、忘れずに`mysql_upgrade`しておきます。

```bash:mysql_upgrade
# mysql_upgrade -u root -p
Enter password:
Checking if update is needed.
Checking server version.
Running queries to upgrade MySQL server.
Upgrading system table data.
Checking system database.
mysql.columns_priv                                 OK
（中略）
ts1.table2                                         OK
Upgrade process completed successfully.
Checking if update is needed.
```

## Master 側で実験

まずは Master 側で実験してみます。

※今回、MySQL は`root`ユーザで操作しています。別のユーザで操作する場合は、必要な権限が付与されていることを確認してください。

テスト用のデータベースとテーブルを作成します。

```sql:DB・テーブル作成
mysql> CREATE DATABASE enc_test;
Query OK, 1 row affected (0.01 sec)

mysql> USE enc_test;
Database changed
mysql> CREATE TABLE enc_test (id int(10) PRIMARY KEY AUTO_INCREMENT, value VARCHAR(100)) ENGINE=innodb ENCRYPTION='Y';
Query OK, 0 rows affected (0.03 sec)
```

テストデータを挿入します。

```sql:テストデータ挿入(1)
mysql> INSERT INTO enc_test SET value='hoge';
Query OK, 1 row affected (0.00 sec)

mysql> INSERT INTO enc_test SET value='fuga';
Query OK, 1 row affected (0.00 sec)
```

バイナリログに平文で記録されていないか確認してみます。

```bash:ログgrep確認(1)
# fgrep 'hoge' binlog.*
# fgrep 'fuga' binlog.*
```

見つかりませんでした。

バイナリログの暗号化を動的に無効にしてみます。

```sql:暗号無効化
mysql> SHOW BINARY LOGS;
+---------------+-----------+-----------+
| Log_name      | File_size | Encrypted |
+---------------+-----------+-----------+
| binlog.000001 |      1855 | Yes       |
+---------------+-----------+-----------+
1 row in set (0.00 sec)

mysql> SET GLOBAL binlog_encryption=0;
Query OK, 0 rows affected (0.00 sec)

mysql> SHOW BINARY LOGS;
+---------------+-----------+-----------+
| Log_name      | File_size | Encrypted |
+---------------+-----------+-----------+
| binlog.000001 |      1899 | Yes       |
| binlog.000002 |       155 | No        |
+---------------+-----------+-----------+
2 rows in set (0.00 sec)
```

ログがローテーションされ、暗号化が`No`に変わりました。

テストデータを挿入します。

```sql:テストデータ挿入(2)
mysql> USE enc_test;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> INSERT INTO enc_test SET value='HOGE';
Query OK, 1 row affected (0.00 sec)

mysql> INSERT INTO enc_test SET value='FUGA';
Query OK, 1 row affected (0.00 sec)
```

バイナリログを確認してみます。

```bash:ログgrep確認(2)
# fgrep 'HOGE' binlog.*
Binary file binlog.000002 matches
# fgrep 'FUGA' binlog.*
Binary file binlog.000002 matches
```

今度は平文で見つかりました。

## Slave 側で実験

次に、Slave 側で実験してみます。

その前に、Master 側に Slave 用のユーザを作成しておきます。

```sql:Slave用ユーザ作成（Master側で）
mysql> CREATE USER 'repl'@'%' IDENTIFIED BY 'T35+U53r';
Query OK, 0 rows affected (0.01 sec)

mysql> GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';
Query OK, 0 rows affected (0.01 sec)
```

続いて、Slave 側で Master への接続設定を行います。

```sql:Master接続設定（Slave側で）
mysql> CHANGE MASTER TO
    ->     MASTER_HOST='【ホスト名】',
    ->     MASTER_USER='repl',
    ->     MASTER_PASSWORD='T35+U53r',
    ->     MASTER_LOG_FILE='binlog.000001',
    ->     MASTER_LOG_POS=4;
Query OK, 0 rows affected, 2 warnings (0.01 sec)

mysql> SHOW WARNINGS\G
*************************** 1. row ***************************
  Level: Note
   Code: 1759
Message: Sending passwords in plain text without SSL/TLS is extremely insecure.
*************************** 2. row ***************************
  Level: Note
   Code: 1760
Message: Storing MySQL user name or password information in the master info repository is not secure and is therefore not recommended. Please consider using the USER and PASSWORD connection options for START SLAVE; see the 'START SLAVE Syntax' in the MySQL Manual for more information.
2 rows in set (0.00 sec)
```

**通信経路の暗号化をしていない**ことについて怒られました。
また、2 つ目の Warning は 5.6 あたりでも表示されるいつものヤツです…今回は実験なので手抜きしましたが、本番利用するときはどちらも怒られないように正しく設定しましょう。

気を取り直して…早速 Slave へのレプリケーションを開始してみます。

```sql:レプリケーション開始
mysql> START SLAVE;
Query OK, 0 rows affected (0.00 sec)

mysql> SHOW SLAVE STATUS\G
*************************** 1. row ***************************
               Slave_IO_State: Waiting for master to send event
（中略）
                   Last_Errno: 1410
                   Last_Error: Error 'You are not allowed to create a user with GRANT' on query. Default database: ''. Query: 'GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%''
（中略）
        Seconds_Behind_Master: NULL
Master_SSL_Verify_Server_Cert: No
（中略）
               Last_SQL_Errno: 1410
               Last_SQL_Error: Error 'You are not allowed to create a user with GRANT' on query. Default database: ''. Query: 'GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%''
  Replicate_Ignore_Server_Ids:
             Master_Server_Id: 1
（中略）
        Get_master_public_key: 0
1 row in set (0.00 sec)
```

権限の関係でエラーが出ました。Slave 側でレプリケーション用のユーザを作成する必要はないのでスキップします。

```sql:エラーが出たSQLをスキップ
mysql> SET GLOBAL SQL_SLAVE_SKIP_COUNTER=1;
Query OK, 0 rows affected (0.00 sec)

mysql> START SLAVE;
Query OK, 0 rows affected (0.00 sec)

mysql> SHOW SLAVE STATUS\G
*************************** 1. row ***************************
               Slave_IO_State: Waiting for master to send event
（中略）
              Master_Log_File: binlog.000002
          Read_Master_Log_Pos: 1396
（中略）
        Seconds_Behind_Master: 0
（中略）
                Last_IO_Errno: 0
                Last_IO_Error:
               Last_SQL_Errno: 0
               Last_SQL_Error:
（中略）
        Get_master_public_key: 0
1 row in set (0.00 sec)

mysql> SHOW BINARY LOGS;
+---------------+-----------+-----------+
| Log_name      | File_size | Encrypted |
+---------------+-----------+-----------+
| binlog.000001 |      2605 | Yes       |
+---------------+-----------+-----------+
1 row in set (0.00 sec)
```

レプリケーションされたようです。

バイナリログ・リレーログを`fgrep`して平文記録されていないか確認してみます。

```bash:ログgrep確認(3)
# fgrep 'hoge' binlog.*
# fgrep 'fuga' binlog.*
# fgrep 'HOGE' binlog.*
# fgrep 'FUGA' binlog.*
# fgrep 'hoge' *relay-bin.*
# fgrep 'fuga' *relay-bin.*
# fgrep 'HOGE' *relay-bin.*
# fgrep 'FUGA' *relay-bin.*
```

どれも見つかりませんでした。Master 側で各々のバイナリログが暗号化されているか否かに関わらず、Slave 側の設定通りに保存されるようです。

# おわりに

バイナリログ・リレーログが対応したことにより、必要な要素が一通り暗号化できるようになりました。
あとはダンプ…？

---

**2019/01/23 追記：**

某氏の呟きを見て、スロークエリログや一般ログ等のことを忘れていたのに気づきました。

---

**2019/01/25 追記（4/3 修正）：**

システムテーブルスペースも忘れてました（8.0.~~15~~16 で対応される模様）。

 * **[1.4 What Is New in MySQL 8.0](https://dev.mysql.com/doc/refman/8.0/en/mysql-nutshell.html)（MySQL 8.0 Reference Manual）**より引用

> * As of MySQL 8.0.~~15~~16, the InnoDB tablespace encryption feature, which provides at-rest data encryption, supports encryption of the mysql system tablespace. The mysql system tablespace contains the mysql system database and the MySQL data dictionary tables. For more information, see [Section 15.6.3.9, “Tablespace Encryption”](https://dev.mysql.com/doc/refman/8.0/en/innodb-tablespace-encryption.html).

---

**2019/07/13 追記：**

8.0.16 で`ALTER INSTANCE`によるマスターキーのローテーションに対応しました。

 **[13.1.5 ALTER INSTANCE Syntax](https://dev.mysql.com/doc/refman/8.0/en/alter-instance.html)（MySQL 8.0 Reference Manual）**

---

 * **[Qiitaに投稿したMySQL 8.0関連記事](https://hmatsu47.hatenablog.com/qiita_mysql80)**