MysqlConnectorで証明書によるセキュアな通信を実装する


概要

MySqlConnectorを用いたWEBアプリケーションにSSL通信を構築する実装方法
(今回は証明書の発行元をherokuにしています。)

参考にしたサイト
https://dev.mysql.com/doc/connector-python/en/connector-python-connectargs.html

バージョン

Flask==1.1.2
Python 3.8.7
mysql-connector-python==8.0.23
mysql 5.5.62
(↑ ClearDBといったクラウド上にホスティングされたDaaSを使用)

導入手順

①以下のファイルを用意します。
・SSL認証局の情報を含むssl_caファイル
・SSL証明書を含むssl_certファイル
・SSLキーを含むssl_keyファイル

herokuでClearDBを使用している場合、
作成したアプリケーションのダッシュボードから対象のアプリを選択し
ResourceタブのClearDB MySQLからNAVISITEにジャンプします。

ジャンプ先のSecurityタブから各種証明書を発行できます。


② 前手順で取得したディレクトリに各pemファイルを配置します。
(ディレクトリ構成は公式に則り、opt/mysql/sslとしています。)
また必ずメインモジュールと同階層に配置するようにします。
app.pyがメインモジュールの場合、以下のようになります。

├── app.py
├── opt
│   └── mysql
│   └── ssl
│   ├── ca.pem
│   ├── client-cert.pem
│   └── client-key.pem


③mysqlconnectorのDB接続引数を設定します。
接続引数に与える証明書のパスは、前手順で設定したものを記載します。

app.py
import mysql.connector
from mysql.connector.constants import ClientFlag

config = {
    'user': 'ssluser',
    'password': 'password',
    'host': '127.0.0.1',
    'client_flags': [ClientFlag.SSL],
    'ssl_ca': '/opt/mysql/ssl/ca.pem',
    'ssl_cert': '/opt/mysql/ssl/client-cert.pem',
    'ssl_key': '/opt/mysql/ssl/client-key.pem',
}

この設定後、私が躓いた点はファイルパスの渡し方でした。
PythonではOSによってスラッシュ記法が異なる為、windows環境で設定したファイルパスは、herokuにデプロイすると参照されずエラーを出力しました。


app[web.1]: context.load_verify_locations(ca)
app[web.1]: FileNotFoundError: [Errno 2] No such file or directory
app[web.1]:"Invalid CA Certificate: {}".format(err))
app[web.1]: mysql.connector.errors.InterfaceError: Invalid CA Certificate: [Errno 2] No such file or directory



os毎のパスのフォーマットは以下のように異なります。
windowsの場合 --> \\
POSIX(mac/linux)の場合 --> /


対処方法としては、メインモジュールの絶対パスと証明書ファイルの相対パスを連結させてパスを渡すようにしました。
その際、作動するosを判定して、osに応じてパスの取得方法をスイッチさせるようにしました。(若干冗長な気もしますが...)

app.py
import os

# 証明書のディレクトリ指定
dirname = os.getcwd()
if os.name == 'nt':
    # windows
    ca_path = os.path.join(dirname, 'opt\\mysql\\ssl\\ca.pem')
    cert_path = os.path.join(dirname, 'opt\\mysql\\ssl\\client-cert.pem')
    key_path = os.path.join(dirname, 'opt\\mysql\\ssl\\client-key.pem')
elif os.name == 'posix':
    # mac or linux
    ca_path = os.path.join(dirname, 'opt/mysql/ssl/ca.pem')
    cert_path = os.path.join(dirname, 'opt/mysql/ssl/client-cert.pem')
    key_path = os.path.join(dirname, 'opt/mysql/ssl/client-key.pem')


config = {
    'user': 'ssluser',
    'password': 'password',
    'host': '127.0.0.1',
    'client_flags': [ClientFlag.SSL],
    'ssl_ca': ca_path ,
    'ssl_cert': cert_path ,
    'ssl_key': key_path ,
}

以上で設定は完了です!

おわりに

勉強の一環として個人開発を始めてから最初のうちは、英語のドキュメントやStack over flowなどに記載されたプラクティスを避けて、なるべく日本語で要約されたサイトを参考にしていました。が、やはり本質はオリジナルのドキュメントにあると感じました。
また、Teratailには載っていない事象や解決フローも、Stack over flowには記載されていることが意外と多く、勉強になりました。
解釈によっては誤解も生じるため、それによって手戻りが発生する事もあるでしょうし、遠回りになるかもしれませんが、問題解決の為に遠回りした時間や注いだ労力はいつか必ず何かに活きるものだと信じてこれからも学んでいきたいと思います。