メルカリでCDNのキャッシュに由来する情報流出があった。CDNでキャッシュしているのはリバース・プロキシで、ちょっと前にGoの練習を兼ねてリバース・プロキシを書いたので解説してみる。
リバース・プロキシのキャッシュの挙動について考えるとき、以下の2点を切り離して考える必要がある:
- どのようなリクエスト/レスポンスのときにキャッシュするか?
- どのようなリクエストのときにキャッシュから返すか?
メルカリのケースでは、そもそもキャッシュして欲しくない情報がキャッシュされ、かつそれがユーザへのレスポンスとして使われたという問題なので、ここでは前者の「どのようなリクエスト/レスポンスのときにキャッシュするか?」について考える。
リバース・プロキシはどのようなリクエスト/レスポンスのときにキャッシュするのか? RFC7234的には以下のすべての条件を満たすときにキャッシュするかもしれない。
- キャッシュ可能なメソッドある
- キャッシュ可能なステータスコードである
- リクエストヘッダの
Cache-Control
にno-store
がない - レスポンスヘッダの
Cache-Control
にno-store
やprivate
がない - リクエストヘッダに
Authorization
がない - 以下のいずれかを満たす
- レスポンスヘッダに
Expires
がある、または - レスポンスヘッダの
Cache-Control
にmax-age
がある、または - レスポンスヘッダの
Cache-Control
にs-maxage
がある、または - レスポンスヘッダの
Cache-Control
においてCache Control Extensionsでキャッシュ可能だと指定されている、または - キャッシュ可能だと定義されているステータス・コード、または
- レスポンスヘッダの
Cache-Control
にpublic
がある
- レスポンスヘッダに
メルカリのケースではレスポンス・ヘッダに以下を指定していた。
Cache-Control: no-cache
Expires: Thu, 22 Jun 2017 08:58:21 GMT (アクセスの1秒前の時間)
上に書いたキャッシュの条件で、Cache-Control: no-cache
は出てこない。RFC7234では、no-cache
はキャッシュされたレスポンスを使わないように指定するものだと書いてある。つまり、「どのようなリクエストのときにキャッシュから返すか?」の話だ。
意外なことに、古い日付のExpires
があることはキャッシュされる原因になりうる。なぜなら、リバース・プロキシはアプリケーション・サーバ等の上流のサーバが反応しないときに、古い内容であると分かっていながらキャッシュからレスポンスを返すことがあるからだ。
では、どうすればキャッシュされなくなるか?上に書いたキャッシュの条件に当てはまらなくなればいい。具体的にはレスポンスヘッダにCache-Control: private
でよい。