なんかかきたい

プログラミングなどの個人的なメモやサークル「ゆきいろパラソル」の情報を載せてます

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

起動時の自動生成ではMySQLMySQL_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

これでどちらにも繋げるようになった。よかったですね。

自己証明書以外でもだいたい同じ。クライアント検証をする場合もうちょっと面倒。がんばってください。