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. Thekeyfile
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 形式の証明書と秘密鍵を作成する方法をこちらの記事で紹介していますので、必要であればご確認ください。
-
秘密鍵を作成します。
openssl genpkey -algorithm rsa -out priv.key -pkeyopt rsa_keygen_bits:2048
-
署名リクエストを作成します。
openssl req -new -key priv.key -sha256 -subj "/C=JP/ST=Tokyo/L=xxxx/O=xxxx/CN=xxx.xxx.xxx.xxx" -out server.csr
-
サブジェクト代替名 (Subject Alternative Name) を追加するためのファイルを作成します。
echo "subjectAltName=IP:xxx.xxx.xxx.xxx" > san.txt
-
証明書を作成します。
openssl x509 -in server.csr -out server.crt -req -signkey priv.key -days 3650 -sha256 -extfile san.txt
作成した証明書 server.crt
と秘密鍵 priv.key
をこの後使っていきます。
なお server.crt
と priv.key
は以下の各 Python スクリプトと同じディレクトリに配置されている前提としています。
ssl.SSLContext
オブジェクトを使う場合
ssl.SSLContext オブジェクトを作成し、load_cert_chain()
メソッドで証明書と秘密鍵をセットします。
作成した ssl.SSLContext
を Flask.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_context
に adhoc
と文字列を渡すだけです。
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
は結構便利そうなので今後使うかもしれません。