MariaDB Connector/C のcaching_sha2_passwordプラグインの仕様について

こんにちは。アンドパッドでDBREを務めている久保と申します。 今日はAurora MySQLの認証プラグインを caching_sha2_password 認証へ移行している最中に発見したMariaDB Connector/Cの挙動についてお話しします。

背景

認証プラグインの caching_sha2_password 移行の背景について説明します。 従来の mysql_native_password 認証には以下のような課題があります。

  • パスワードのハッシュ関数が古い(SHA1)
  • ソルトなしハッシュのため、レインボーテーブルによる攻撃が可能

caching_sha2_password 認証はこれらの課題を解決したよりセキュアな認証プラグインです。将来的には、mysql_native_password 認証は非推奨となり、caching_sha2_password 認証が主流になっていくと予想しています(実際、MySQL 8.4 ではデフォルトでmysql_native_password 認証は無効化されています) 。アンドパッドでは多くのアプリケーションが稼働しており、切り替えには時間がかかることが予想されるため、いち早く移行に着手することにしました。

caching_sha2_passwordの仕様

caching_sha2_password 認証では、初回の接続時にSSL/TLSなどセキュアな通信経路を必要とする点に注意が必要です。平文接続の場合は、DBサーバから公開鍵を取得し、セキュアな通信経路を確立する実装がDBドライバ側で必要です(クライアント側であらかじめ公開鍵を指定しておく方法もあります)。そのような対応がなされていないDBドライバでは、初回の接続でエラーが出てしまいます。

なお、一度、接続が成功したらその認証情報をDBサーバがキャッシュし、2回目からはそのキャッシュを用いて認証を行います。2回目以降はセキュアな通信経路は必要ありません。キャッシュを作るために平文でパスワードをやりとりする必要があることから、初回接続時に上記のような制限があります。

詳しい caching_sha2_passwordの仕様については手間味噌ですが以下スライドをご覧ください。 https://speakerdeck.com/boro1234/caching-sha2-passwordnohanasi

今回の移行では、各アプリケーションのDBドライバが、初回接続時に正しく動くことを重点的にチェックしました。

Redashのcaching_sha2_password接続が上手くいかない

移行の準備として、まず、各DBを利用しているアプリケーションがcaching_sha2_password 認証に対応しているかチェックしました。今回の話はRedash (オープンソースのBIツール) のcaching_sha2_password 認証の対応状況を調査をした時の話になります。

Redashのstaging環境やローカル環境で実際にユーザをcaching_sha2_passwordに切り替えて接続を確認しました。しかし、ここで不思議なことに、ローカル環境ではcaching_sha2_passwordでの接続が上手くいくのに、staging 環境では 上手くいかない現象に遭遇しました。

(2059, 'Plugin caching_sha2_password could not be loaded: /usr/lib/x86_64-linux-gnu/mariadb19/plugin/caching_sha2_password.so: cannot open shared object file: No such file or directory')

Redashのバージョンが異なっており、それぞれの環境でインストールされているpythonのDBドライバであるmysqlclientのバージョン差が原因と思われました。しかし、インストール済みのパッケージリストを見てみても両方の環境で同じバージョン2.2.1を使っていました。そして、このバージョンはcaching_sha2_password 非対応のバージョンでした。DBドライバがcaching_sha2_password 非対応なのに、接続出来るのは不思議です。

また、Redashはstagingもローカルも共にlibmariadb3を利用しているとわかりました。そこで、ローカルでmariadbのコマンドクライアントでも疎通できるか試してみたところ、疎通ができることが確認できました。 

よってmysqlclientのような DBドライバレベルではなく、クライアントライブラリレベルの差が原因であると予想がつきました。

MariaDB Connector/Cのプラグインがインストールされていない

MariaDB Connector/Cについて調べてみると、caching_sha2_passwordに関するプラグインが存在することがわかりました。

https://github.com/mariadb-corporation/mariadb-connector-c/blob/461a2c79ea63406065a6237bf1043a250099565d/plugins/auth/caching_sha2_pw.c

このプラグインはlibmariadb3のパッケージに含まれています。また、このプラグインはMariaDB Connector/Cのバージョン3.0.8以上の場合に存在します。

以下のようにして、ローカルではこのプラグインが含まれており、stagingでは含まれていないことを確認できました。つまり、今回stagingで確認できたエラーはこのプラグインが存在しなかったことに起因しそうだと推測しました。

## ローカル
$ ls /usr/lib/aarch64-linux-gnu/libmariadb3/plugin
caching_sha2_password.so  client_ed25519.so  dialog.so  mysql_clear_password.so  sha256_password.so
## staging
$ ls /usr/lib/x86_64-linux-gnu/libmariadb3/plugin
client_ed25519.so  dialog.so  mysql_clear_password.so

MariaDB Connector/Cが 公開鍵をやりとりしている

実際に、MariaDB Connector/C が caching_sha2_password 認証をしている ことを確認するためにソースコードを見てみました。

なお、libmariadb3に含まれるMariaDB Connector/Cのバージョンを確認するには、以下のMariaDB Connector/CとMariaDBサーバーのバージョン対応表を参照すると良いと思います。libmariadb3のバージョンはMariaDBサーバーのバージョンに対応しています。 https://mariadb.com/kb/en/about-mariadb-connector-c/

このプラグインで実際何をしているのか詳しくソースを見てみると、次のように公開鍵をリクエストしている部分がありました。

/* if no public key file was specified or if we couldn't read the file,
       we ask server to send public key */
    if (!filebuffer)
    {
      unsigned char request= REQUEST_PUBLIC_KEY;
      if (vio->write_packet(vio, &request, 1) ||
         (packet_length=vio->read_packet(vio, &packet)) == -1)
      {
        mysql->methods->set_error(mysql, CR_AUTH_PLUGIN_ERR, "HY000", "Couldn't read RSA public key from server");
        return CR_ERROR;
      }
    }

https://github.com/mariadb-corporation/mariadb-connector-c/blob/461a2c79ea63406065a6237bf1043a250099565d/plugins/auth/caching_sha2_pw.c#L316-L327

また、このソースで公開鍵が実際に受け渡されているか確認するため、VScodeでgdbステップ実行を実施することにしました。ただし、簡略化するためにMariaDBコマンドクライアントでの接続で確認をすることにしました。

まず、適当なubuntuのdockerコンテナを用意して、MariaDB Serverをソースインストールしてデバッグビルドします。

// 必要なパッケージのインストール
apt update && apt install cmake build-essential git libssl-dev libgnutls28-dev libncurses-dev bison mariadb-client gdb

// ソースインストール&ビルド
git clone https://github.com/MariaDB/server.git
cd server
mkdir build && cd build
cmake .. \
  -DCMAKE_BUILD_TYPE=debug \
  -DCMAKE_INSTALL_PREFIX=/opt/mariadb \
  -DWITH_SSL=system \
  -DWITH_DEBUG=ON \
make -j4
make install

次に、VScode側で以下のような設定をします。また、該当のソース部分にブレークポイントを貼ります。

{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "(gdb) Launch",
            "type": "cppdbg",
            "request": "launch",
            "program": "/opt/mariadb/bin/mariadb",
            "args": ["-uayumu","-h172.17.0.4","--password='password'","--ssl=0"],
            "stopAtEntry": false,
            "cwd": "${fileDirname}",
            "environment": [],
            "externalConsole": false,
            "MIMode": "gdb",
            "setupCommands": [
                {
                    "description": "Enable pretty-printing for gdb",
                    "text": "-enable-pretty-printing",
                    "ignoreFailures": true
                },
                {
                    "description": "Set Disassembly Flavor to Intel",
                    "text": "-gdb-set disassembly-flavor intel",
                    "ignoreFailures": true
                }
            ]
        },

そして、認証情報のキャッシュをクリアするために以下のコマンドをMySQL側で実行します。

FLUSH PRIVILEGES;

最後に、VScodeのデバッグ再生ボタンを押し、MariaDBコマンドラインを実行させます。すると該当のソースでブレークし、以下のように公開鍵を得ることができました。

  • ステップ実行で得られた公開鍵
x/3s packet
0x555555ae2561: "-----BEGIN PUBLIC KEY-----\MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4pJle3GYRafNYcTw9kwr\JI9THlQVc6aq1u78jBcu7cKHUpnPhQE3Jn7auqPJapJz/UHTRwrtpEApIdO6AzRq\ER54HyoDu5Bh0wYg9Q1oDRZnE3rMVN1s0ZeGA8D3Y2d"...
0x555555ae2629: "JjZue4kKztiZLyDvniWbo\i2iRbgK3sXiC3cXYeoapURlDHYoweqshfiNjbF5pyHIZDAwwddzil+LGDydXdKTC\saG/1rO4uCTALKBvOsv49WH9oUhDdIZ7mZrjQ+LSPN2ebVjhPH5cvv2oCTO+dc9T\pHCv89QIlVv7uKym+LyHiQzZ26axjRJXXIbrIntS/3FUlh7V"...
0x555555ae26f1: "C41oc7l04OhKm7i5\hQIDAQAB\-----END PUBLIC KEY-----\"

またMySQL側の公開鍵を確認すると以下のようになり、ステップ実行で得た公開鍵と一致することが分かります。

  • サーバー側に存在する公開鍵
root@bd12e241a28e:/var/lib/mysql# cat public_key.pem
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4pJle3GYRafNYcTw9kwr
JI9THlQVc6aq1u78jBcu7cKHUpnPhQE3Jn7auqPJapJz/UHTRwrtpEApIdO6AzRq
ER54HyoDu5Bh0wYg9Q1oDRZnE3rMVN1s0ZeGA8D3Y2dJjZue4kKztiZLyDvniWbo
i2iRbgK3sXiC3cXYeoapURlDHYoweqshfiNjbF5pyHIZDAwwddzil+LGDydXdKTC
saG/1rO4uCTALKBvOsv49WH9oUhDdIZ7mZrjQ+LSPN2ebVjhPH5cvv2oCTO+dc9T
pHCv89QIlVv7uKym+LyHiQzZ26axjRJXXIbrIntS/3FUlh7VC41oc7l04OhKm7i5
hQIDAQAB
-----END PUBLIC KEY-----

これによって、DBドライバ側で 公開鍵の受け渡しに対応していなくても、MariaDB Connector/C が自動的にやりとりし、初回のセキュアな経路は確保できるという理屈になります。

staging環境ではlibmariadb3のバージョンが低くなっていたので、最新のバージョンをインストールしました。これによってプラグインがインストールされて、万事解決と思われたのですが、MariaDB Connector/Cの次のような仕様に出くわしました。

GnuTLSを使っているとプラグインが使えない

最新のバージョンをインストールし、再び接続を確認したところ、以下のようなエラーに出くわしました。

RSA Encryption not supported - caching_sha2_password plugin was built with GnuTLS support

先ほどのcaching_sha2_passwordプラグインのソースで言うと以下になります。

#if defined(HAVE_GNUTLS)
     mysql->methods->set_error(mysql, CR_AUTH_PLUGIN_ERR, "HY000", 
                               "RSA Encryption not supported - caching_sha2_password plugin was built with GnuTLS support");
     return CR_ERROR;

https://github.com/mariadb-corporation/mariadb-connector-c/blob/461a2c79ea63406065a6237bf1043a250099565d/plugins/auth/caching_sha2_pw.c#L303-L306

このコードでは MariaDB Connector/Cをビルドする際にcmakeのオプションでWITH_SSL=GNUTLSが指定されている場合、エラーを常に返します。確認したところ、stagingのlibmariadb3はGnuTLSを指定してビルドしていそうなことが、共有ライブラリの依存関係からわかりました。

ここで、MariaDB Connector/Cをソースからインストールして、OpenSSLを使うようにビルドする案が出ましたが、管理が煩雑になるため取りやめました。そのため、素直にSSL/TLS接続に変更することにしました。SSL/TLSにすると接続時のラウンドトリップが増えますが、Redashの利用用途上、問題はないと判断をしました。

一方、ローカルで問題なくcaching_sha2_password接続ができた理由は、GnuTLSでなくOpenSSLを使うようにビルドされていたためでした。どうやらDebian 12(bookworm)でのlibmariadb3はOpenSSLを使うようにビルドされているようです。GnuTLSを使うようにビルドされていたstagingはDebian 10(buster)を利用していました。以下に各環境における使用SSL/TLSライブラリがまとめられています。

https://mariadb.com/kb/en/tls-and-cryptography-libraries-used-by-mariadb/#mariadb-clients-and-utilities-on-linux

まとめ

  • MariaDB Connector/C は caching_sha2_password 認証の初回接続時のセキュアな通信経路確保を自動的に行なってくれる
  • しかし、これには OpenSSLでビルドされている必要がある

右往左往しながらの調査ではありましたが、結果的にMariaDB Connector/Cの仕様を深掘りできました。引き続きcaching_sha2_passowrd移行の準備を頑張っていこうと思います。

最後に、アンドパッドではデータベースの挙動を深堀りしながら、アプリケーションとデータベースの信頼性を確保する DBRE を募集しています。 hrmos.co