Flask にサーバー証明書を適用し https 対応する

Flask に自己署名サーバー証明書と秘密鍵を適用し https 対応する方法を一次情報を明記しながら紹介します。また開発時に便利な機能も見つけたので併せて紹介します。

やりたいこと

Flask で https 対応する必要があったのですが、やり方はわかったものの、一次情報に行きつくのに結構苦労したので、纏めてみました。

環境

証明書作成環境

OS
Amazon Linux 2
OpenSSL
1.0.2k-fips

Windows で openssl 無しで PEM 形式の証明書と秘密鍵を作成する方法をこちらの記事で紹介していますので、必要であればご確認ください。

Flask 稼働環境

OS
Microsoft Windows 11 22H2
Python
Python 3.11.3
Flask
Flask 2.2.2

Flask でサーバー証明書と秘密鍵を設定する方法

まず Flask で SSL サーバー証明書と秘密鍵を設定する方法を見ていきます。

Flask の公式ドキュメントを探しても、https 対応に関連するような記述や API は見つかりません。実は Flask そのものは https に対応する機能は持っていません。

Flask は Werkzeug を使って作られています。Flask の API は Werkzeug をラップして作られているものもあり、そのうちの一つが Flask.run() です。

run(host=None, port=None, debug=None, load_dotenv=True, **options)

Flask.run の引数は上記の通りですが、options は以下の通り記載があります。werkzeug.serving.run_simple() の引数として渡されるようです。

options (Any) – the options to be forwarded to the underlying Werkzeug server. See werkzeug.serving.run_simple() for more information. - API — Flask Documentation (2.3.x)

では、Werkzeug のドキュメントの werkzeug.serving.run_simple() を確認してみます。

werkzeug.serving.run_simple(hostname, port, application, use_reloader=False, use_debugger=False, use_evalex=True, extra_files=None, exclude_patterns=None, reloader_interval=1, reloader_type='auto', threaded=False, processes=1, request_handler=None, static_files=None, passthrough_errors=False, ssl_context=None)

かなり引数が多いのですが、最後に気になる引数があります。

ssl_context (_TSSLContextArg | None) – Configure TLS to serve over HTTPS. Can be an ssl.SSLContext object, a (cert_file, key_file) tuple to create a typical context, or the string 'adhoc' to generate a temporary self-signed certificate. - Serving WSGI Applications — Werkzeug Documentation (2.3.x)

ビルトインライブラリの ssl.SSLContext オブジェクトか、証明書と秘密鍵ファイルのパスのタプルを渡すことで、証明書と秘密鍵を設定できるようです。

また、adhoc と文字列を渡すと自己署名証明書を作成して適用してくれるようです。ローカルで開発中に https 接続が必要な場合に結構便利そうな機能ですね。ただし、adhoc を使用する場合は、cryptography パッケージが必要ですので、インストールされていない場合はインストールしてください。

pip install cryptography

cryptography がインストールされていないと、TypeError: Using ad-hoc certificates requires the cryptography library. というエラーがでます。

なお、ssl.SSLContext オブジェクトに証明書と秘密鍵を設定するには load_cert_chain() メソッドを使用します。

SSLContext.load_cert_chain(certfile, keyfile=None, password=None)

certfile は以下の通り PEM 形式の証明書ファイルのパスを指定する必要があるので注意です。openssl を使用する場合はデフォルトが PEM 形式なので問題ないと思います。Windows の場合、pkcs#12 形式の .pfx ファイルで証明書、秘密鍵を扱うことが多いですが、pkcs#12 形式は対応してません。こちらの記事で Windows で pkcs#12 形式から PEM 形式の証明書、秘密鍵を取得する方法を紹介していますので、必要であればご確認ください。

The certfile string must be the path to a single file in PEM format containing the certificate as well as any number of CA certificates needed to establish the certificate’s authenticity. The keyfile string, if present, must point to a file containing the private key. - ssl — TLS/SSL wrapper for socket objects — Python 3.11.5 documentation

実装

準備

とりあえず自己署名証明書(所謂オレオレ証明書)を用意します。ここでは openssl を使用します。繰り返しになりますが Windows で openssl 無しで PEM 形式の証明書と秘密鍵を作成する方法をこちらの記事で紹介していますので、必要であればご確認ください。

  1. 秘密鍵を作成します。

    openssl genpkey -algorithm rsa -out priv.key -pkeyopt rsa_keygen_bits:2048   
  2. 署名リクエストを作成します。

    openssl req -new -key priv.key -sha256 -subj "/C=JP/ST=Tokyo/L=xxxx/O=xxxx/CN=xxx.xxx.xxx.xxx" -out server.csr
  3. サブジェクト代替名 (Subject Alternative Name) を追加するためのファイルを作成します。

    echo "subjectAltName=IP:xxx.xxx.xxx.xxx" > san.txt 
  4. 証明書を作成します。

    openssl x509 -in server.csr -out server.crt -req -signkey priv.key -days 3650 -sha256 -extfile san.txt

作成した証明書 server.crt と秘密鍵 priv.key をこの後使っていきます。 なお server.crtpriv.key は以下の各 Python スクリプトと同じディレクトリに配置されている前提としています。

ssl.SSLContext オブジェクトを使う場合

ssl.SSLContext オブジェクトを作成し、load_cert_chain() メソッドで証明書と秘密鍵をセットします。 作成した ssl.SSLContextFlask.run()ssl_context 引数に渡します。

作成した証明書 server.crt と秘密鍵 priv.key は以下の各 Python スクリプトと同じディレクトリに配置されている前提としています。

from flask import Flask
import ssl

app = Flask(__name__)

@app.route("/", method=["GET"])
def index():
  return "<h1>Hello World!</h1>"

context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
context.load_cert_chain("server.crt", "priv.key")

app.run(host="0.0.0.0", port=5000, ssl_context=context)

証明書、秘密鍵のファイルパスをタプルで渡す場合

Flask.run()ssl_context 引数に証明書と秘密鍵のファイルパスのタプルを渡します。 こちらのほうがシンプルですね。

from flask import Flask

app = Flask(__name__)

@app.route("/", method=["GET"])
def index():
  return "<h1>Hello World!</h1>"

app.run(host="0.0.0.0", port=5000, ssl_context=("server.crt", "priv.key"))

adhoc を使用する場合

最後に自己署名証明書を生成してくれる adhoc を使用するパターンも紹介します。 Flask.run()ssl_contextadhoc と文字列を渡すだけです。

from flask import Flask

app = Flask(__name__)

@app.route("/", method=["GET"])
def index():
  return "<h1>Hello World!</h1>"

app.run(host="0.0.0.0", port=5000, ssl_context="adhoc")

あとがき

Flask で https 対応するにあたり、一次情報を探すのに結構苦労しましたので、この機会に纏めてみました。adhoc は結構便利そうなので今後使うかもしれません。