infra-failureminorverifiedfirsthand
計測スクリプトにSubresource Integrityを追加するという純粋なセキュリティ強化のはずの変更が、デプロイした当日に本番の計測を止めた。
原因: SRIはクロスオリジンスクリプトにcrossorigin属性を要求し、これによりブラウザはCORSモードでの取得を強制される。CDNのエッジキャッシュがCORSヘッダー追加前の古いレスポンスを配信し続けていたため、キャッシュがパージされるまで、オリジン側の修正が実際のユーザーに届かなかった。
結果: 本番のクリック計測が止まった。チームはCDNゾーンのキャッシュパージ権限を持っていなかったため、最速の対処はintegrity/crossorigin属性を一時撤去するホットフィックスで、事故は同日中に解決した。
対策: CDN配下のスクリプトにSRIを追加する際は、(1) オリジン側にCORSヘッダーをデプロイし、(2) 該当URLのCDNキャッシュを明示的にパージし、(3) 実際のブラウザでCORSエラーが出ないことを確認し、その後初めて (4) 読み込み側のページにintegrity/crossorigin属性をデプロイする。curlだけの確認では不十分——オリジンが直っただけでなく、エッジが実際に切り替わったことをキャッシュステータスヘッダーで確認する。
何が起きたか
セキュリティ強化——クロスオリジンの計測スクリプトへのSubresource Integrity 追加——が、デプロイした当日に本番の計測を止めた。
現場の混乱
オリジンサーバーは正しく修正され、ローカルでは動作確認済みだった。 本番では実際のブラウザがCORSエラーを吐き始め、計測用のトラッキングピクセルが 発火しなくなった。オリジンへのcurl確認は問題なく見え、それが混乱に 拍車をかけた——CDNのキャッシュステータスヘッダーを確認して、修正前に キャッシュされたレスポンスがまだ配信され続けていることが分かるまでは。
原因
SRIのintegrity属性はcrossoriginを要求し、これによりブラウザはCORSモードで
スクリプトを取得することを強制される。オリジンには正しいCORSヘッダーが
入っていたが、CDNのエッジキャッシュはそのヘッダーが存在する前にキャッシュされた
レスポンスを配信し続けていた(スクリプトのキャッシュ有効期限は4時間)。
「新たに要求されるcrossorigin」と「古いキャッシュ済みレスポンス」の組み合わせは
確実にCORSブロックを引き起こし、オリジンをどれだけ直してもエッジに既に
キャッシュされているものは変わらない。
対策
当時チームはCDNゾーンのキャッシュパージ権限を持っていなかったため、
最速の脱出策はintegrity/crossorigin属性を一時撤去するホットフィックス
だった——オリジン側のCORSヘッダーはSRIがなくても無害なのでそのまま残し、
パージ権限を得てから改めてSRIを試みることにした。そもそもこの事故を
避けるための展開順序は次の通り。まずオリジンにCORSヘッダーをデプロイし、
該当URLのCDNキャッシュを明示的にパージし、実際のブラウザでリクエストが
成功することを確認し、その後で初めてスクリプトを読み込むページに
integrity/crossoriginを追加する。オリジンへのcurl確認はオリジンが
直ったことしか証明しない——エッジがまだ何を配信しているかについては
何も語らない。