10.12. CORSとCSRF¶
CORS と CSRF は、インターネットに公開されたカメラがHTTPSやログインと並んで必要とする、2つのブラウザ側の保護機能です。それぞれ数行で設定できます。以下のセクションでは、用語を定義し、microdotとの統合方法を示します。
10.12.1. CORSが行うこと¶
Cross-Origin Resource Sharing(CORS)は、サーバーが特定の他のオリジンに自分のレスポンスを読み取らせることをオプトインできる、ブラウザの仕組みです。ブラウザのデフォルトの同一オリジンポリシーはその読み取りをブロックします。https://example.com 上のJavaScriptは https://yard-cam.example.com からのレスポンスを読み取れません。ホストが異なれば異なるオリジンとみなされるためです。CORSは、選んだ相手に対して例外を許可するサーバー側の方法です。
ダッシュボードがカメラ自身から提供される場合、すべてのリクエストは同一オリジンであり、CORSは何もしていません。この設定が重要になるのは、ダッシュボードが別の場所にある場合です。https://app.example.com のような公開URLが https://yard-cam.example.com のカメラと通信する場合などです。
from microdot.cors import CORS
cors = CORS(
app,
allowed_origins=['https://app.example.com'],
allow_credentials=True,
max_age=86400,
)
allowed_origins は、カメラのレスポンスの読み取りを許可されたオリジンのリストです。ダッシュボードのオリジン、そしてダッシュボードのオリジンのみであり、* ではありません。これにより、サードパーティのサイトが誤ってカメラのレスポンスを読み取ることはできません。
allow_credentials=True は、クロスオリジンリクエストにセッションクッキーを含めることを許可します。これは、ダッシュボードがオリジンの境界をまたいでログイン状態を維持するために必要なものです。
max_age=86400 は、プリフライト結果を1日キャッシュできることをブラウザに伝えます。ブラウザは、GET/HEAD/POST以外のメソッドを使用したり、カスタムヘッダーを送信したりするクロスオリジン呼び出しの前に、追加の OPTIONS リクエストを発行します。max_age は、そのオーバーヘッドをルートごとに1日あたり1回のプリフライトに削減します。
10.12.2. CSRFが行うこと¶
Cross-Site Request Forgery(CSRF)は、悪意のあるページがユーザーのブラウザに、信頼されたサーバーへ認証済みリクエストを発行させる攻撃です。CORSが導入されていても、evil.com 上の隠された <form> が https://yard-cam.example.com/config にPOSTすると、それはカメラに到達し、ブラウザはカメラのセッションクッキーを添付します。クッキーはリクエストを行うページのオリジンではなく宛先ホストに従うためです。その結果、カメラはあたかも所有者からのものであるかのようにそのPOSTを喜んで処理してしまいます。
CSRF保護はそれらのリクエストを拒否します。microdot.csrf.CSRF は、状態を変更するすべてのリクエストの Sec-Fetch-Site ヘッダーを検査し、same-origin(またはCORSで許可されたオリジンからのもの)とラベル付けされていないものをすべて拒否するミドルウェアを追加します。
from microdot.csrf import CSRF
CSRF(app, cors=cors)
cors インスタンスを渡すと、ミドルウェアはダッシュボードの許可されたオリジンを特例として認めます。カメラは、クロスオリジンであってもダッシュボードのPOSTを引き続き受け付けます。
Sec-Fetch-Site は現代のブラウザによって自動的に設定されます。カメラはクライアント側で何もする必要はありません。このヘッダーを送信しない古いブラウザに対しては、CORSの許可リストがフォールバックのチェックとなります。
10.12.3. Webhookの除外¶
カメラが、サードパーティのクラウドサービス(たとえばアーカイブプロバイダーからのコールバックなど)からのPOSTを受け付けるwebhookエンドポイントを必要とする場合は、ルートを @csrf.exempt でマークすると、ミドルウェアはそれを通過させます。そのハンドラは、リクエストを他の方法で検証する責任を負います。通常は、カメラとサードパーティが共有する秘密鍵を使ってペイロードに対して計算されるHash-based Message Authentication Code(HMAC)であり、リクエストが秘密鍵を知っている者から来たことを証明します。裏庭のカメラにはそうしたものは何もありませんが、必要なときのためにこのデコレータが用意されています。
10.12.4. 4行のベースライン¶
HTTPSが導入されたら、インターネットに面するあらゆるカメラのデプロイに推奨されるスタックは次のとおりです。
Session(app, secret_key=SECRET,
cookie_options={'http_only': True, 'secure': True})
login = Login()
cors = CORS(app, allowed_origins=[...], allow_credentials=True)
CSRF(app, cors=cors)
この章の前半でのセッションとログイン、ここでのCORSとCSRF、前のトピックでのHTTPS。これら4つの要素は互いに積み重なり、各ルートの邪魔をしません。
カメラはこれで、オープンなインターネットに面しても安全になりました。HTTPS、ログイン、CSRF、CORSを備えています。