Cookie・セッション管理シリーズ 第5回 / 全5回
CloudFront署名付きCookieは削除すれば安全? 認可と失効の限界
CloudFront署名付きCookieを使えば、APIサーバーが認可したユーザーにだけ、Amazon S3に置いたファイルを配信できる。APIサーバー自体が大量のファイルデータを中継しなくてよい点も魅力だ。
では、ダウンロード完了後にブラウザからAPIサーバーへCookieの削除を要求すれば、そのCookieは再利用できなくなるのだろうか?
答えは 「正規のブラウザから消すことはできるが、発行済みの署名そのものを失効させることはできない」 だ。
この記事では、ロールに応じたS3ファイル配信を題材に、署名付きCookieの仕組みと限界をアーキテクチャ図・シーケンス図で整理する。
この記事は「Cookieとは何か」「Cookieの有効期限」「Cookieのセキュリティ設計」「CloudFront署名付きCookieでS3を配信する」の応用編にあたる。図はMermaidで記載している。
1. 想定するアーキテクチャ
ユーザーのロールによって、取得できるS3オブジェクトが異なるシステムを考える。
- 一般ユーザー:
/files/public/report.pdfを取得できる - 管理者:
/files/admin/secret.pdfも取得できる - APIサーバー:ユーザー認証とロール判定を行い、許可されたURLに対する署名付きCookieを発行する
- CloudFront:署名と有効期限を検証してファイルを配信する
- S3:非公開バケットとしてファイルを保存し、CloudFrontのOAC経由でのみ取得可能にする
CloudFrontのキャッシュに対象オブジェクトがあれば、毎回S3へアクセスするわけではない。一方、CloudFrontで署名付きCookieの有効性を確認することと、APIサーバーに現在のロールを問い合わせることは別である。
署名付きCookieは何を証明している?
カスタムポリシー方式では、主に次の3つのCookieを使う。
| Cookie名 | 役割 |
|---|---|
CloudFront-Policy | 許可するCloudFront URLや有効期限などを含むポリシー |
CloudFront-Signature | ポリシーが正当な秘密鍵で署名されたことを検証するための値 |
CloudFront-Key-Pair-Id | 検証に使う公開鍵のID(ユーザーIDでもファイルIDでもない) |
署名方式によっては CloudFront-Hash-Algorithm も使用する。
例えば、APIが管理者向けの特定ファイルを許可する場合、ポリシーの概念は次のようになる。
Copied!!{ "Statement": [ { "Resource": "https://cdn.example.com/files/admin/secret.pdf", "Condition": { "DateLessThan": { "AWS:EpochTime": 1790000000 } } } ] }
1790000000は説明用のUnix時刻。実装では発行時に現在時刻から適切な有効期限を計算する。このJSONをそのままCookieに入れるのではなく、CloudFrontの指定形式でエンコードし、秘密鍵で署名する。
ここに「ユーザーIDが現在も管理者であることを、リクエストのたびに確認する」という機能はない。CloudFrontは、標準の署名付きCookie検証では署名・対象URL・期限などの条件を確認する。
2. ダウンロード終了後にCookieを削除する案
考えられる実装は次のとおりだ。
- ブラウザがAPIへダウンロード権限を要求する。
- APIが認可し、短時間だけ有効な署名付きCookieを発行する。
- ブラウザがCloudFrontからファイルをダウンロードする。
- ダウンロード完了を検知したブラウザが、APIへCookie削除を要求する。
- APIが期限切れの
Set-Cookieを返し、ブラウザがCookieを削除する。
この方法はブラウザに不要な権限を残さないという意味では有効である。しかし、セキュリティ上の「失効」と同義ではない。
3. なぜCookie削除だけでは完全に防げないのか
3.1 Cookieのコピーを削除できない
署名付きCookieは、基本的に有効な値を持っているクライアントが利用できるBearer型の認可情報である。CloudFront標準の署名付きCookie検証は、その値が「最初に発行されたブラウザから来たか」を判定しない。
仮にCookieの3つの値が別の環境にコピーされていたとする。正規ブラウザのCookieを削除しても、コピー先のCookieは消えない。
このとき、API側で「削除依頼を受け取った」と記録しても、その情報をCloudFrontが参照しない限り、CloudFrontの判断は変わらない。
3.2 ブラウザの削除リクエストは保証できない
ブラウザが閉じられた、通信が切れた、ページが遷移した、あるいはクライアントが意図的にリクエストを送らなかった場合、削除処理は実行されないことがある。
また、通常のリンクによるファイルダウンロードでは、Webページ側でOSへのファイル保存完了を確実に検知できるとは限らない。fetch でレスポンスを受信しきった時点も、利用者がファイルを保存し終わったことと必ずしも同じではない。
つまり、「ダウンロード完了イベントを待って必ず削除する」という設計自体にも制約がある。
3.3 ロール変更の直後も旧Cookieが使える
管理者として発行したCookieがまだ有効なまま、ユーザーが一般ロールへ切り替わったケースを考える。
重要なのは、APIのロール管理状態とCloudFrontのポリシー検証結果が自動的に同期されるわけではないことだ。
ここでの最後のアクセスは「旧Cookieの値を保持して再利用した場合」を示す。通常のブラウザで削除が成功し、その値を保持していなければ、同じブラウザから自動送信されることはない。
4. ブラウザのCookie削除とCloudFrontの有効期限は別物
次の2つは明確に分ける必要がある。
| 項目 | 誰が管理する? | 何を制御する? |
|---|---|---|
Set-Cookie の Max-Age / Expires | ブラウザ | Cookieを保存・送信する期間 |
署名付きポリシーの DateLessThan | CloudFront | その署名を使うリクエストを許可する期限 |
例えば、CloudFrontポリシーの期限が5分後であっても、1分後にブラウザのCookieを削除することはできる。しかし、削除済みCookieのコピーを持つ別のクライアントは、ポリシー期限までは使える可能性がある。
逆に、ブラウザがCookieを保持していても、ポリシー期限が過ぎればCloudFrontは新しいリクエストを拒否する。
また、CloudFrontはHTTPリクエストの開始時点で有効期限を判定する。大きなファイルのダウンロード中に期限を迎えても、進行中のダウンロードは通常継続できる。一方、有効期限後に開始したRange GETや再開リクエストは拒否される。
Cookieを削除するHTTPレスポンスの例
3つのCookieに対して、発行時と同じ Domain と Path を指定して削除する。
Copied!!HTTP/1.1 204 No Content Set-Cookie: CloudFront-Policy=; Domain=example.com; Path=/files/admin/secret.pdf; Max-Age=0; Secure; HttpOnly Set-Cookie: CloudFront-Signature=; Domain=example.com; Path=/files/admin/secret.pdf; Max-Age=0; Secure; HttpOnly Set-Cookie: CloudFront-Key-Pair-Id=; Domain=example.com; Path=/files/admin/secret.pdf; Max-Age=0; Secure; HttpOnly Cache-Control: no-store
これは、api.example.com から cdn.example.com に送るために、あらかじめ親ドメイン example.com を指定して発行した場合の例である。
注意: api.example.com のレスポンスから、兄弟ドメイン cdn.example.com を Domain に直接指定することはできない。親ドメイン共有にもCookie送信範囲が広がるリスクがあるため、ドメイン構成とセキュリティ要件を合わせて検討したい。
さらに、ファイルごとに Path を変えて署名付きCookieを発行した場合、対象となる各 Path のCookieを正確に削除する必要がある。Path はCookieを送信する範囲を決めるものであり、それ自体を強固な認可境界とみなしてはいけない。
5. 要件別に考える対策
「どこまで防ぎたいか」を先に決めるのが設計のポイントだ。
| 要件 | 主な対策 | 限界・トレードオフ |
|---|---|---|
| 誤操作や通常利用での再ダウンロードを減らす | ダウンロード後のCookie削除 | 削除前の複製や削除失敗までは防げない |
| 流出したCookieの悪用時間を短縮 | 署名付きポリシーの短い有効期限 | 有効期限内の再利用は可能。Range GETにも影響 |
| 特定ファイルへのアクセスだけを許可 | カスタムポリシーで正確な Resource を指定 | 利用者本人への束縛や回数制限にはならない |
| ロール変更後、すぐに旧権限を無効にする | CloudFrontのリクエストごとに現在の権限を追加検証 | Lambda@Edge等の設計・運用・遅延・コストが増える |
| S3への直接アクセスを遮断する | 非公開S3 + OAC + 適切なバケットポリシー | 署名付きCookieの再利用そのものは防げない |
| 1ファイル単位で簡単に権限を渡す | CloudFront署名付きURLを検討 | URLもBearer型であり、共有・コピーや即時失効の問題は残る |
パターンA:短命な署名付きCookieで十分な場合
「ロール変更から数分間の旧権限の残存は許容できる」なら、次の設計が現実的だ。
- APIで毎回、認証とファイル単位の認可を行ってから発行する
- CloudFrontの
Resourceを必要最小限にする - ポリシーの有効期限を短くする
Secure、HttpOnlyを設定し、ブラウザ内で不要になったら削除する- OACでS3直接アクセスを防ぐ
- 署名値・Cookie値をアプリケーションログに不用意に出力しない
ブラウザのCookie削除は防御の追加策であり、アクセス失効の最終的な保証にはしない。
パターンB:即時失効が必須の場合
ロール変更・アカウント停止・権限剥奪を次のリクエストから反映したい場合、署名付きCookie単体では不足する。
候補は、CloudFrontのviewer-request段階での追加認可(例:Lambda@Edgeから現在の権限や失効状態を参照する)や、認可を必ず通過するアプリケーション側ダウンロードゲートウェイだ。
この構成では、キャッシュヒット時も認可を通すように実行段階を選ぶことが重要だ。なお、追加認可に使うトークンも盗まれれば再利用され得るため、「一切のなりすましを防げる」という意味ではない。
CloudFrontの署名鍵を入れ替えたり公開鍵の信頼設定を外したりする方法は、個別ユーザー向けの即時失効手段ではない。同じ鍵で署名された他の利用者にも影響し、設定反映にも時間を要し得る。
パターンC:本当に「1回だけ」ダウンロードさせたい場合
短時間の有効期限と1回限りの利用は別要件だ。
標準のCloudFront署名付きCookieに「使用回数を1回に制限する」機能はない。サーバー側に利用済み状態を持たせ、各リクエストで原子的に判定するような追加構成が必要になる。並行リクエスト、途中失敗、再試行、Range GETも含めた仕様を定義しなければならない。
また、一度端末へ正常にダウンロードされたファイルの複製や持ち出しを、Cookie削除によって防ぐことはできない。
6. どう検証すればよい?
検証環境で次の観点を確認すると、設計上の限界が見えやすい。
- 通常の削除:ダウンロード後に同名・同Domain・同PathのCookieが消えているか。
- 削除失敗:削除リクエストを送らなかった場合、ポリシー期限内に再取得できるか。
- ロール変更:発行後にロールを下げても、既発行の署名は期限まで有効か。
- 時間切れ:署名付きポリシーの期限後は、新規GETが拒否されるか。
- 大容量ファイル:ダウンロード途中で期限切れになった場合と、その後のRange GETの挙動。
- 通信観測:HTTPSパケットキャプチャだけではCookieの平文が読めないこと。
実験にはダミーファイルと検証用アカウントを用い、実際の署名やCookieをログ・記事・スクリーンショットに載せないようにする。
7. 結論:Cookieの削除は「発行済み権限の失効」ではない
CloudFront署名付きCookieは、APIの認可結果を、短時間だけCloudFrontで検証可能な形に委譲する仕組みとして理解すると整理しやすい。
- Cookieの削除:そのブラウザが今後Cookieを送らないようにする操作
- CloudFrontポリシーの期限切れ:新しいリクエストで署名を使えなくする条件
- 現在のロールを使った再認可:権限変更を次のリクエストへ反映する仕組み
これらは互いに異なる。
「発行時に許可されていれば数分間は使える」でよいのか。それとも「ロールが変わった瞬間にアクセス不可」にすべきなのか。
この違いこそが、CloudFront署名付きCookieを採用するときの大きな設計判断になる。
