MySQLの証明書をローテーションする方法を書いておく
前書き
MySQL certificate rotate とかでググってもよくわからなかったので書いた。 RDSの情報はあるが、ネットにはあまり書いてない話題かもしれない。
サマリー
- 再起動またはMaster昇格が必要
- 影響を最小限にするためクライアントの証明書を現Masterと新Masterの両方で使えるようにしておく
- 全てのクライアントの証明書を切り替えた上でサーバの証明書を切り替える
検証環境
https://github.com/t-cyrill/mysql-certificate-rotation-example
$ docker-compose up -d $ docker-compose exec mysql02 /bin/bash # MYSQL_PWD="power" mysql -u power -h mysql01 --ssl-mode=DISABLED -e "SELECT 1" ERROR 1045 (28000): Access denied for user 'power'@'mysql02.mysql-replication-test-by-docker-compose_default' (using password: YES) MYSQL_PWD="power" mysql -u power -h mysql01 --ssl-mode=REQUIRED -e "SELECT 1" | cat 1 1
MySQLはdatadirに証明書がない場合、自動的に証明書を作ることになっている。
$ ls -la mysql*/datadir/*.pem mysql01/datadir/ca-key.pem mysql01/datadir/ca.pem mysql01/datadir/client-cert.pem mysql01/datadir/client-key.pem mysql01/datadir/private_key.pem mysql01/datadir/public_key.pem mysql01/datadir/server-cert.pem mysql01/datadir/server-key.pem mysql02/datadir/ca-key.pem mysql02/datadir/ca.pem mysql02/datadir/client-cert.pem mysql02/datadir/client-key.pem mysql02/datadir/private_key.pem mysql02/datadir/public_key.pem mysql02/datadir/server-cert.pem mysql02/datadir/server-key.pem
--ssl-mode=REQUIRED または PREFERRED の場合
この段階でTLS通信が行われているのがわかる通り、特になんの準備も必要はない。 クライアントは mysql01 と mysql02 のどちらにもTLSでアクセスできるのでそのまま新サーバで新しい証明書を入れてMaster昇格をすれば良いし、 再起動が許容できるなら、mysql01のサーバ証明書を更新して再起動すればよい。無停止にはできない。
--ssl-mode=VERIFY_CA の場合
$ docker-compose exec mysql02 /bin/bash (mysql02) # MYSQL_PWD="power" mysql -u power -h mysql01 --ssl-mode=VERIFY_CA --ssl-ca=/var/lib/mysql/ca.pem -e "SELECT 1" | cat ERROR 2026 (HY000): SSL connection error: error:00000001:lib(0):func(0):reason(1)
mysql02 の ca.pem と mysql01 の ca.pem は別物なので使えない。
cat mysql01/datadir/ca.pem mysql02/datadir/ca.pem > certs/ca.pem
これでどちらでも使えるように思えるが、mysql02の接続では使えない
$ docker-compose exec mysql02 /bin/bash (mysql02) # MYSQL_PWD="power" mysql -u power -h mysql02 --ssl-mode=VERIFY_CA --ssl-ca=/tmp/certs/ca.pem -e "SELECT 1" | cat ERROR 2026 (HY000): SSL connection error: error:00000001:lib(0):func(0):reason(1)
これではつまらないので繋げるようにする。
https://dev.mysql.com/doc/refman/5.7/en/creating-ssl-files-using-openssl.html
ドキュメントを見ればわかるし、証明書を扱っている人ならピンとくると思うが、要は Common Name (CN) が同じだと機能しない。
Certificate: Data: Version: 3 (0x2) Serial Number: 1 (0x1) Signature Algorithm: sha256WithRSAEncryption Issuer: CN=MySQL_Server_5.7.31_Auto_Generated_CA_Certificate Validity Not Before: Jan 22 16:02:39 2021 GMT Not After : Jan 20 16:02:39 2031 GMT Subject: CN=MySQL_Server_5.7.31_Auto_Generated_CA_Certificate Certificate: Data: Version: 3 (0x2) Serial Number: 1 (0x1) Signature Algorithm: sha256WithRSAEncryption Issuer: CN=MySQL_Server_5.7.31_Auto_Generated_CA_Certificate Validity Not Before: Jan 22 16:02:39 2021 GMT Not After : Jan 20 16:02:39 2031 GMT Subject: CN=MySQL_Server_5.7.31_Auto_Generated_CA_Certificate
起動時の自動生成ではMySQLの MySQL_Server_(MySQL Version)_Auto_Generated_CA_Certificate
がCNに設定されると書いてある。
mysql02
の証明書を作る際に CN を別のものにする。opensslを使ってもよいが、ここでは簡略化のため mysql_ssl_rsa_setup
を使う。
(mysql02) # rm -f /var/lib/mysql/*.pem (mysql02) # mysql_ssl_rsa_setup --suffix 2
--suffix
オプションの部分が (MySQL Version)
に相当する箇所になる。上記の例だと、MySQL_Server_2_Auto_Generated_CA_Certificate
となる。
Certificate: Data: Version: 3 (0x2) Serial Number: 1 (0x1) Signature Algorithm: sha256WithRSAEncryption Issuer: CN=MySQL_Server_2_Auto_Generated_CA_Certificate Validity Not Before: Jan 22 16:54:13 2021 GMT Not After : Jan 20 16:54:13 2031 GMT Subject: CN=MySQL_Server_2_Auto_Generated_CA_Certificate
cat mysql01/datadir/ca.pem mysql02/datadir/ca.pem > certs/ca.pem
mysql01/datadir/ca.pem
には改行コードが残っている場合があり、ゴミが乗っている場合は消しておく
(mysql02) # MYSQL_PWD="power" mysql -u power -h mysql01 --ssl-mode=VERIFY_CA --ssl-ca=/tmp/certs/ca.pem -e "SELECT 1" | cat 1 1 (mysql02) # MYSQL_PWD="power" mysql -u power -h mysql02 --ssl-mode=VERIFY_CA --ssl-ca=/tmp/certs/ca.pem -e "SELECT 1" | cat 1 1
これでどちらにも繋げるようになった。よかったですね。
自己証明書以外でもだいたい同じ。クライアント検証をする場合もうちょっと面倒。がんばってください。