MySQL SSL/TLS:通信経路でデータを「丸見え」にしない(mTLS設定ガイド)

MySQL tutorial - IT technology blog
MySQL tutorial - IT technology blog

MySQL mà không có SSL? Bạn đang chơi đùa với lửa!

デフォルトでは、MySQLはデータをプレーンテキストで送信します。アプリケーションとデータベースが同じサーバー上にあり、ソケット経由で接続されている場合は問題ありません。しかし、マイクロサービスを運用したり、クラウド間をインターネット経由で接続したりする場合、これは非常に大きな脆弱性となります。Wiresharkのようなツールを使えば、わずか5分で、管理者のパスワードから顧客のクレジットカード情報まで、すべてのSQLクエリを簡単に傍受できてしまいます。

以前、システムを50ノードに拡張した際に、あるトラブルに対応したことがあります。内部ネットワークだからと暗号化を怠っていたところ、定期的なセキュリティ監査で決済時のペイロードが完全に露出していることが判明しました。これは私にとって手痛い教訓となり、それ以来、データベースの初期構築時にはSSL/TLSの実装を最優先事項としています。

知っておくべき3つの接続セキュリティレベル

プロジェクトの要件に応じて、以下の3つのレベルから選択できます:

  • 暗号化なし接続(Unencrypted): 速度は最速ですが、リスクは最大です。localhost経由の内部接続のみに使用すべきです。
  • 標準SSL(One-way TLS): サーバーが証明書を提示し、クライアントがそれを検証します。データは暗号化され、盗聴を防ぎますが、サーバーはクライアントが誰であるかを把握していません。
  • クライアント証明書認証(Two-way TLS/mTLS): 最も「ハードコア」なレベルです。サーバーとクライアントの両方が、同じCAによって署名された有効な証明書を提示する必要があります。ハッカーがパスワードを手に入れたとしても、端末内に秘密鍵ファイルがなければ、手出しすることはできません。

各手法のクイック比較:

項目 暗号化なし 標準SSL クライアント証明書 (mTLS)
データの暗号化 なし あり あり
サーバー認証 なし あり あり
クライアント認証 パスワードのみ パスワードのみ 証明書 + パスワード
安全性 最大

なぜ大規模システムでmTLSが選ばれるのか?

実際、パスワードは設定ファイルや環境変数から漏洩しがちです。クライアント証明書を導入することで、物理的なセキュリティレイヤーが追加されます。たとえログイン情報が盗まれたとしても、攻撃者はアプリケーションサーバーに安全に保管されている鍵ファイルを必要とします。これはデータベースを隔離し、特定のノードのみにアクセスを許可するための最も効果的な方法です。

実装のステップバイステップ

ステップ1:内部認証局(CA)の構築

内部接続の場合、Let’s Encryptを使用する代わりに、独自のCAを作成して管理することをお勧めします。OpenSSLを使用して以下のキーセットを作成しましょう。

# CAの作成(秘密鍵と自己署名証明書)
openssl genrsa 2048 > ca-key.pem
openssl req -new -x509 -nodes -days 3650 -key ca-key.pem -out ca.pem -subj "/CN=MySQL_Internal_CA"

# サーバー用の秘密鍵と証明書の作成
openssl req -newkey rsa:2048 -days 3650 -nodes -keyout server-key.pem -out server-req.pem -subj "/CN=mysql-server"
openssl rsa -in server-key.pem -out server-key.pem
openssl x509 -req -in server-req.pem -days 3650 -CA ca.pem -CAkey ca-key.pem -set_serial 01 -out server-cert.pem

# クライアント用の秘密鍵と証明書の作成
openssl req -newkey rsa:2048 -days 3650 -nodes -keyout client-key.pem -out client-req.pem -subj "/CN=mysql-client-app-1"
openssl rsa -in client-key.pem -out client-key.pem
openssl x509 -req -in client-req.pem -days 3650 -CA ca.pem -CAkey ca-key.pem -set_serial 02 -out client-cert.pem

その結果、5つのファイルが生成されます。ca-key.pemは安全な場所(できればオフライン)に厳重に保管してください。

ステップ2:MySQLサーバー側の設定

ca.pemserver-cert.pemserver-key.pem ファイルを /etc/mysql/ssl/ ディレクトリにコピーします。忘れずに chown mysql:mysql で所有権を設定してください。その後、設定ファイル my.cnf を更新します。

[mysqld]
ssl-ca=/etc/mysql/ssl/ca.pem
ssl-cert=/etc/mysql/ssl/server-cert.pem
ssl-key=/etc/mysql/ssl/server-key.pem

# 暗号化されていない接続を完全に遮断する
require_secure_transport = ON

変更を適用するためにサービスを再起動します:sudo systemctl restart mysql

ステップ3:X509認証を要求するユーザーの作成

ここが重要なステップです。MySQLが証明書をチェックしなければログインを許可しないユーザーを作成します。

CREATE USER 'app_user'@'%' IDENTIFIED BY 'super_secure_password_123';
GRANT ALL PRIVILEGES ON prod_db.* TO 'app_user'@'%' REQUIRE X509;
FLUSH PRIVILEGES;

REQUIRE X509 キーワードにより、たとえハッカーがパスワードを知っていたとしても、発行した証明書がなければログインできないことが保証されます。

ステップ4:クライアントからの接続

設定を確認するためにCLIでクイックテストを行います。

mysql -u app_user -p -h db.example.com \
    --ssl-ca=ca.pem \
    --ssl-cert=client-cert.pem \
    --ssl-key=client-key.pem

Pythonによる接続例:

import mysql.connector

conn = mysql.connector.connect(
    user='app_user',
    password='super_secure_password_123',
    host='10.0.0.5',
    database='prod_db',
    ssl_ca='certs/ca.pem',
    ssl_cert='certs/client-cert.pem',
    ssl_key='certs/client-key.pem'
)

cursor = conn.cursor()
cursor.execute("SHOW STATUS LIKE 'Ssl_cipher'")
print(f"接続中のアルゴリズム: {cursor.fetchone()[1]}")
conn.close()

運用に関するいくつかの「手痛い」注意点

SSLを数年間運用してきた中で、3つの重要なポイントをまとめました。

  1. パフォーマンス(CPUオーバーヘッド): SSLハンドシェイクはかなりのリソースを消費します。ベンチマークテストでは、新しい接続を頻繁に作成すると、スループットが1000 req/sから600 req/sに低下することがあります。SSL接続を維持するために、Connection Pool(ProxySQLなど)を使用してください。
  2. 証明書の期限切れに注意: 最悪なのは、証明書の期限切れで午前2時にシステムがダウンすることです。監視を設定するか、30日前に自動的に警告を出すスクリプトを使用しましょう。
  3. CAチェーンのエラー: self signed certificate in certificate chain というエラーが発生した場合、90%の確率でクライアント側の ca.pem ファイルがサーバーの署名に使用されたCAと一致していません。チェックサムを確認してください。

SSL/TLSの実装は最初は少し面倒に感じるかもしれません。しかし、ハッカーの好奇の目からデータが「露出」していないと分かれば、より安眠できるはずです。

Share: