CORSは認証ではない
目標
オリジン・メソッド・ヘッダーの許可マトリクスを、実際のプリフライトで検証します。
なぜ重要なのか
フロントエンドでレスポンスを読めなかったので、すべてのオリジンにアスタリスクを許可しました。クッキーを送るリクエストでは、ポリシーがさらに複雑になり、開発者は、CORSを有効にするだけで外部からのリクエストが遮断されると誤解しました。このラボでは、ブラウザーの読み取りポリシーと、サーバーの認証を分離します。認証の機能を代わりに作ることはしません。
ステップ
/root/work/fa-cors-policy-lab/service.pyで、origin(value)は、httpまたはhttpsのURLで、hostがあり、path・query・fragment・ユーザー情報がなければ、入力の文字列を返します。それ以外は、ValueErrorです。末尾の/もpathなので、拒否します。
最初に一度だけ準備してください。既存のファイルは上書きしません。
mkdir -p /root/work/fa-cors-policy-lab
test -e /root/work/fa-cors-policy-lab/service.py || cp /opt/fixtures/ten_labs/fa-cors-policy-lab/service.py /root/work/fa-cors-policy-lab/service.py
cd /root/work/fa-cors-policy-lab
-
/root/work/fa-cors-policy-lab/service.pyで、origins(values)は、各項目をoriginで検証したあと、最初に出てきた順序で重複を除去した新しいリストです。 -
/root/work/fa-cors-policy-lab/service.pyで、methods(values)は、GET・POST・PUT・DELETE・OPTIONSだけを許可して、大文字に変換し、重複を除去します。空のリストやそれ以外の値は、ValueErrorです。 -
/root/work/fa-cors-policy-lab/service.pyで、policy(allowed, credentials)は、credentialsがboolであることを確認します。allowedに'*'があればValueErrorで、{allow_origins:origins(allowed), allow_credentials:credentials}を返します。 -
/root/work/fa-cors-policy-lab/service.pyで、create_app(allowed, credentials=True)は、policyを検証して、CORSMiddlewareを設定したアプリです。GET/POSTだけを許可し、Content-Type・X-Request-IDのリクエストヘッダーを許可し、X-Traceのレスポンスヘッダーをexposeします。GET /dataは、{ok:True}、X-Trace='trace-1'を返します。 -
/root/work/fa-cors-policy-lab/service.pyで、preflight_headers(source, method, requested='X-Request-ID')は、Origin、Access-Control-Request-Method、Access-Control-Request-Headersの3つのキーを持つ辞書です。methodは大文字です。 -
/root/work/fa-cors-policy-lab/service.pyで、preflight_status(app, source, method, requested='X-Request-ID')は、TestClientで/dataにOPTIONSのリクエストを送って、HTTPのステータスを返します。別のオリジン・DELETE・X-Secretヘッダーは、400でなければなりません。 -
/root/work/fa-cors-policy-lab/service.pyで、cors_observation(app, source)は、GET /dataを送って、(ステータス、Access-Control-Allow-Originの値またはNone、JSONの本文)を返します。許可されていないオリジンでも、200の本文は実行されますが、許可するオリジンのヘッダーは付いていてはいけません。
参考
- インターネットやパッケージのインストールなしで、既存のlab-dev環境で行います。
- 各ステップは、45秒の採点バジェットの中で実行されます。実際のsleepやネットワーク呼び出しを追加しないでください。
- 採点は、提出されたモジュールを新しく読み込んで、独立した入力と一時的なDBで検査します。期待値を定数で返す代わりに、契約を実装してください。
- FastAPI公式ドキュメント・pytest公式ドキュメント・Python sqlite3
- 限界: TestClientは、ブラウザーではありません。CORSのレスポンスヘッダーとプリフライトを検査しますが、ブラウザー自体の読み取りのブロックまでは実装しません。許可されていないOriginを送った通常のGETも、サーバーで実行されることがあります。機密性のある動作は、別の認証・権限・CSRFのポリシーで保護する必要があります。
オリジンの形式を検証する
/root/work/fa-cors-policy-lab/service.pyで、origin(value)は、httpまたはhttpsのURLで、hostがあり、path・query・fragment・ユーザー情報がなければ、入力の文字列を返します。それ以外は、ValueErrorです。末尾の/もpathなので、拒否します。
最初に一度だけ準備してください。既存のファイルは上書きしません。
mkdir -p /root/work/fa-cors-policy-lab
test -e /root/work/fa-cors-policy-lab/service.py || cp /opt/fixtures/ten_labs/fa-cors-policy-lab/service.py /root/work/fa-cors-policy-lab/service.py
cd /root/work/fa-cors-policy-lab
URL全体をオリジンとして許可すると、パスやユーザー情報を混同することがあります。
保存したら、bash /opt/lab/checks/fa-cors-policy-lab/01-contract.shで確認してください。
重複したオリジンを除去する
/root/work/fa-cors-policy-lab/service.pyで、origins(values)は、各項目をoriginで検証したあと、最初に出てきた順序で重複を除去した新しいリストです。
許可リストは、文字列の部分一致ではなく、正確なオリジンのリストです。
保存したら、bash /opt/lab/checks/fa-cors-policy-lab/02-contract.shで確認してください。
メソッドを許可リストで制限する
/root/work/fa-cors-policy-lab/service.pyで、methods(values)は、GET・POST・PUT・DELETE・OPTIONSだけを許可して、大文字に変換し、重複を除去します。空のリストやそれ以外の値は、ValueErrorです。
許可していないPATCHや任意のメソッドを、黙って追加しません。
保存したら、bash /opt/lab/checks/fa-cors-policy-lab/03-contract.shで確認してください。
認証情報とアスタリスクを一緒に許可しない
/root/work/fa-cors-policy-lab/service.pyで、policy(allowed, credentials)は、credentialsがboolであることを確認します。allowedに'*'があればValueErrorで、{allow_origins:origins(allowed), allow_credentials:credentials}を返します。
このラボの明示的なポリシーは、認証情報の有無にかかわらず、アスタリスクを受け付けません。
保存したら、bash /opt/lab/checks/fa-cors-policy-lab/04-contract.shで確認してください。
実際のCORSミドルウェアを付ける
/root/work/fa-cors-policy-lab/service.pyで、create_app(allowed, credentials=True)は、policyを検証して、CORSMiddlewareを設定したアプリです。GET/POSTだけを許可し、Content-Type・X-Request-IDのリクエストヘッダーを許可し、X-Traceのレスポンスヘッダーをexposeします。GET /dataは、{ok:True}、X-Trace='trace-1'を返します。
preflightと実際のレスポンスに、ヘッダーを手で別々に付けると、2つのポリシーが簡単にずれます。
保存したら、bash /opt/lab/checks/fa-cors-policy-lab/05-contract.shで確認してください。
プリフライトのリクエストを作る
/root/work/fa-cors-policy-lab/service.pyで、preflight_headers(source, method, requested='X-Request-ID')は、Origin、Access-Control-Request-Method、Access-Control-Request-Headersの3つのキーを持つ辞書です。methodは大文字です。
実際のリクエストのメソッドはOPTIONSで、検査したいメソッドは、別のヘッダーにあります。
保存したら、bash /opt/lab/checks/fa-cors-policy-lab/06-contract.shで確認してください。
拒否のマトリクスを計算する
/root/work/fa-cors-policy-lab/service.pyで、preflight_status(app, source, method, requested='X-Request-ID')は、TestClientで/dataにOPTIONSのリクエストを送って、HTTPのステータスを返します。別のオリジン・DELETE・X-Secretヘッダーは、400でなければなりません。
拒否の理由の3種類を、1つのリクエストに混ぜないようにしてはじめて、抜けているポリシーを見つけられます。
保存したら、bash /opt/lab/checks/fa-cors-policy-lab/07-contract.shで確認してください。
CORSと認証の違いを観察する
/root/work/fa-cors-policy-lab/service.pyで、cors_observation(app, source)は、GET /dataを送って、(ステータス、Access-Control-Allow-Originの値またはNone、JSONの本文)を返します。許可されていないオリジンでも、200の本文は実行されますが、許可するオリジンのヘッダーは付いていてはいけません。
curlやサーバー間のリクエストは、ブラウザーのCORSの読み取り制限に従いません。
保存したら、bash /opt/lab/checks/fa-cors-policy-lab/08-contract.shで確認してください。