10.11. HTTPS -- サーバーの通信暗号化

ここまではすべてポート80での平文HTTPでした。ブラウザとカメラの間でパケットをキャプチャできる者なら誰でも、ログインフォームのパスワード、そこから返されるセッションクッキー、Authorization ヘッダー内のJWT、そしてキャプチャされた各フレームのJPEGバイトを読み取れてしまいます。HTTPSは通信を端から端まで暗号化します。

証明書のワークフロー自体、つまり開発用の自己署名証明書の生成、本番用のCA署名証明書の取得、ファイルを正しい形式でカメラにコピーすることについては、TLS証明書の取り扱い で扱います。このページは すでに読み込んだ証明書をmicrodotに組み込む ことについて説明します。

10.11.1. SSLコンテキストの構築

ssl.SSLContext は、証明書、鍵、プロトコルオプションを格納する標準ライブラリのコンテナです。サーバーでは PROTOCOL_TLS_SERVER と、カメラのファイルシステムから読み込んだ証明書チェーンが必要です。

import ssl

ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ctx.load_cert_chain('/flash/cert.der', '/flash/key.der')

DERのパスは、TLSセクションで実行したワークフロー(開発用は自己署名、本番用はCA署名)に応じて決まります。ファイルは /flash 上にある必要はなく、/sdcard でも同様に機能します。

10.11.2. コンテキストをstart_serverに渡す

以前の start_server() 呼び出しとの唯一の違いは、ssl=ctx 引数とポート番号です。ポート443はHTTPSのデフォルトなので、ブラウザは :443 を入力する必要がなく、https://yard-cam.local/ だけで動作します。

async def main():
    await asyncio.gather(
        capture_loop(),
        motion_detector(),
        app.start_server(host='0.0.0.0', port=443, ssl=ctx),
    )

asyncio.run(main())

サーバー側についてはこれで完了です。既存のすべてのルート(/status/snapshot.jpg/stream.jpg/config/events/control、静的ダッシュボード)が、他のコード変更なしでTLS上で動作するようになります。

10.11.4. ここで扱わない内容

HTTP Strict Transport Security(HSTS)、証明書の自動更新、送信リクエスト用のカメラ側のCA信頼チェーン、暗号スイートの選択は、いずれもTLSセクションで扱います。ここで扱うプラグイン部分、つまり1つの SSLContext と1つの ssl=ctx だけが、microdotに固有の部分です。

ダッシュボードのURLバーは http:// から https:// に、WebSocketは ws:// から wss:// に変わります。ダッシュボードのJavaScriptは location.protocol に基づいてすでに正しいスキームを選択しているので、クライアント側の変更は必要ありません。

カメラはHTTPS経由で配信します。ログインフォーム、JWT、キャプチャされたフレームは、転送中に暗号化されます。