はじめに
こんにちは。バックエンドエンジニアの小笠原です。
今回は、2022年2月18日から2022年3月4日にかけて発生していたこちらの障害に対し私達開発チームが実施した、session.cookieで定義しているCookieのkey名を変更するという影響範囲の大きい対応について、実施に至るまでの経緯や対応過程についてご紹介したいと思います。
背景
全ては iOS14.5から端末識別子の取得に同意が必要になったことから始まった
ことの発端は、iOS14.5以降からIDFA(端末ごとに持つ固有識別子)の取得に端末所有者の許可が必要になったことでした。
この変更は、端末所有者側から見ると情報の活用範囲を自身で管理できることでよりプライバシーに配慮されるようになった良い変更と言えるでしょう。 一方で、広告出稿側から見た場合は拒否をしたユーザーの広告トラッキングが出来なくなることで広告の効果測定が大幅に制限される、という問題が発生してしまいます。
この問題に対して、Facebookピクセルという広告効果測定ツールを提供しているMeta社(旧Facebook社)は、広告効果測定の仕様を変更して合算イベント測定による集計を行うことでIDFAの取得を拒否したユーザーについても広告の効果測定ができるように対策を行いました。 BASEにおいてもInstagram広告Appがこの影響を受けるので、何らかの対応を行う必要に迫られました。
合算イベント測定に対応する際の詳細については本記事の主題ではないのでここでは省略させていただきますが、結論としてeTLD+1なドメインを認証することで合算イベント測定を使用可能になるということがわかったため、当時開発チームはショップ開設時に選択することができるドメイン群をPublic Suffix List(PSL)に登録するという対応を行っていました。
Public Suffix List(PSL)とは
Public Suffix List(PSL)とは、jpやcomなどのTop Level Domain(TLD)と、co.jpやmeguro.tokyo.jpのような実質的にTLDのように振る舞うことが期待されるeffective Top Level Domain (eTLD)を管理しているリストのことで、GitHub上で管理・運営されています。 このリストに対して必要な情報を添えてPull Requestを送ることで、誰でも任意のドメインの追加を申請することが可能です。
https://github.com/publicsuffix/list
つまり、PSLに任意のドメインを登録することでそのドメインをeTLDとして扱うようにすることができ、これによってショップのURLがeTLD+1と認識されるため、ショップ単位でドメイン認証を行うことで合算イベント測定を使用可能になる、ということです。
この対応のため、開発チームはショップ開設時に選択することができる以下のドメイン群をPSLに登録する申請を行いました。
base.ec official.ec buyshop.jp fashionstore.jp handcrafted.jp kawaiishop.jp supersale.jp theshop.jp shopselect.net base.shop
リポジトリのPull Request履歴を確認すると、登録申請をしたのは2021年9月14日で、マージされたのは2021年12月5日だということがわかります。 https://github.com/publicsuffix/list/pull/1420
PSLに登録されたドメインにはCookieを保存できない
ところで、 PSLに登録したドメインにはCookieを保存することができなくなってしまいます。
仮にjpのようなTLDに対してCookieを保存可能にしてしまうと、そのドメインを使用している全てのWebサイトでそのCookieを共有できることになってしまいます。TLDは不特定多数の利用者が様々な目的でサブドメインを取得して運用していることが多く、このような広範囲に対してCookieを参照可能な状態にしてしまうことはセキュリティリスクが高いため推奨されるものではありません。
そのため、TLDにはCookieを保存できないルールになっています。そして、TLDと同様の振る舞いをするeTLDに対しても同じことが言えるため、TLDと同様にeTLDに対してもCookieを保存できません。
つまり、PSLにドメインを登録するということは、そのドメインに対してCookieを保存できなくなる、ということを意味します。
PSLへドメインを登録したことによってどのような影響が出てしまったのか
BASEのショップでも例に漏れずCookieを利用しており、例えば「シークレットECショップへのログイン情報」「カートへ商品を追加する際の商品情報」などはCookieのTHEBASE
というkey名に保存して管理していました。そして、これらのCookieはショップ毎に割り当てられているサブドメインに対してではなく、前項で紹介したPSLに登録したドメインに対してCookieを保存する処理になっていました。
つまり、これらの情報をCookieに保存できなくなったことで「シークレットECにログインできない」「カートへ商品を入れてもカートの中が空のまま」といった不具合が発生していたことが今回の障害の裏側で発生していた事象でした。
なぜPSLにドメインを登録してから数ヶ月経過してから問題が顕在化し始めたのか
PSLにドメインを登録したのは2021年12月5日ですが、この障害を開発チームが認識したのは2022年2月19日の段階でした。 なぜおよそ2ヶ月ほど経過するまでこの不具合に気がつくことができなかったのかというと、それはブラウザが最新のPSLを取り込んだタイミングが関係していたようでした。
実は、各ブラウザは常に最新のPSLを参照しているわけではなく、任意のアップデートのタイミングでその時点の最新のPSLのスナップショットをビルドに含めて参照しています。 さらに、以下の表のように必ずしもアップデート時に最新のPSLへと更新しているわけではなく、その更新周期には規則性がないこともわかりました。
ブラウザ名 | PSLの更新周期 |
---|---|
FireFox | Firefox96(2022/01/12リリース)時点ではBASEの登録したドメインは含まれておらず、Firefox97(2022/02/08)には含まれていた |
Chrome | chrome97(2022/01/04リリース)時点で 2021/10/27 のPSLを取り込んで以後、更新されていない |
上記の通り、直近のFirefox97のリリースによってこの障害に遭遇する購入者が徐々に増えてきたのではないか、と推測されました。
障害への対応内容
base.shopなどのeTLDに対してCookieが保存できないという問題に対して、今回はショップのURLに該当するサブドメインに対してCookieを保存するように変更するという方法を採用しました。
これは、ショップ毎にサブドメインを割り当てているBASEの仕組みを考えると、基本的にはショップの中でsessionが保持できれば購入者の買い物体験は阻害されないであろう、という判断によるものです。
実現方法として、チームでは以下2点の選択肢が挙がりました。
- Cookieのdomain属性でサブドメインを指定する
- Cookieのdomain属性を指定しない
domain属性を指定しなかった場合は一番狭い範囲に対してCookieが保存されるため、挙動としては「サブドメインに対してCookieを発行するように変更する」という点でどちらの対応を実施しても同じ意味となります。
今回はセキュリティの入門書として有名である『体系的に学ぶ 安全なWebアプリケーションの作り方 第2版 脆弱性が生まれる原理と対策の実践』にdomain属性を指定しない状態が最もCookieの送信範囲が狭く安全な状態であるという言及があったことから、後者のdomain属性を指定しないように修正する方針に決まりました。
問題点
さて、前置きが長くなってしまいましたが、ここからが本記事の本題となります。
対応方針が決まったところで検証環境で動作確認をしていると「対応後のソースコードでもシークレットECにログインできない」という障害が稀に発生することがありました。
この現象は eTLD+1をドメイン属性にもつ THEBASE
のCookieと、eTLDをドメイン属性にもつTHEBASE
のCookieが同時に送信されているケースで発生していることがわかりました。
これは、今まで不具合が発生していたショップ(Cookieが保存できていなかったショップ)では修正後のCookieのみが保存されているために不具合が解消された一方で、今まで正常にログインできていたショップ(Cookieが保存できていたショップ)で新たに不具合が発生するようになった、ということです。
そもそもなぜ同じkeyのCookieが二種類できてしまうのか
THEBASE
のCookieは有効期限をセッションに設定していたため、この現象に遭遇した場合はブラウザを再起動すれば古いCookieが削除されて問題を解消することができると予想されました。
ところが、実際にブラウザを閉じてショップを開きなおしても、本来であれば消えるはずの前回アクセスした際のCookieが残ったままになってしまっていることが発生していました。
実は、この問題はブラウザが「前回開いたサイトを復元する」機能を実現するために、ブラウザを閉じた後も有効期限がセッションになっているCookieを保持し続ける挙動をすることが原因で発生しているらしいことがわかりました。
さらに、同名のCookieが存在する場合のCookieの取扱もブラウザによって異なっていることが私たちを混乱させました。
ブラウザ | Cookieの並び順 | 同名のCookieが複数ある時シークレットECにログインできるか |
---|---|---|
FireFox | 古いCookieが新しいCookieよりも先に並ぶ | できない |
Chrome | 古いCookieが新しいCookieよりも先に並ぶ | できない |
Safari | 新しいCookieが古いCookieよりも先に並ぶ | できる |
このように、ブラウザによって挙動が異なっており、いつCookieが削除されるのかがブラウザ依存であるという状態であることから、Cookieのdomain指定方法を変更するだけでは障害から復旧できないことがわかってしまいました。
解決案の模索
この問題に対して、私達のチームでは2つの案について検討しました。一つ目の案はこの現象を許容したままで対応をリリースすること、そして二つ目の案はsession.cookieで定義しているCookieのkey名を変更した上で対応をリリースすること、でした。
この2つの案の比較検討と障害の影響範囲の把握のため、開発メンバーで協力してソースコード上でsession.cookieの定義を使用している全ての参照箇所を洗い出しました。
以下の表は、この2つの案に対してそれぞれ比較検討した内容を表にまとめたものです。
案1:Cookieの重複を許容する | 案2:Cookieのkey名を変更する | |
---|---|---|
影響範囲 | FireFox97とSafari以外のブラウザを使用している購入者 | 全ての購入者 |
メリット | すぐにリリースできる | 完全に不具合が発生しなくなる |
デメリット | Cookieが二重で登録されてしまった購入者には、ブラウザキャッシュを削除してもらう必要がある | セッションがリセットされるので、再ログイン等が必要になる |
工数 | なし | リグレッションテストが膨大 |
影響期間 | ブラウザの旧Cookieが消えるまで(つまりいつ収束するか不明) | デプロイのタイミングのみ |
どちらの案でも発生するデメリットとして、デプロイを跨いで購入者が操作した際に以下の影響が出るという問題がありました。
- 改善リリースデプロイ前に抽選・定期便・コミュニティ限定商品をカートへ追加して未購入状態の場合、改善リリースデプロイ後にはカート内の商品が全て消えてしまう
- コミュニティ会員ページへログイン済みの状態でも、再度ログインが必要になってしまう
- シークレットECがかかったショップページへアクセスをしている状態でも、再度PW入力が必要になってしまう
- デプロイをまたいで購入をしたユーザーの場合、決済が走っているもののCookieを持ち越せないために購入完了画面が表示されないことで、購入に失敗したと誤解をして重複購入してしまう
そして我々はsession.cookieを修正してCookieのKey名を変えた
最終的に、Cookieのkey名を変更する案2の方がより購入者に優しいだろう、ということで決まりました。
一時的な不便を全購入者に要求してしまうことにはなるのですが、購入者に要求する操作としては再ログインやカートへの再度の商品追加など、通常のWebブラウジングの操作の範囲内で対処できるものとなっています。
逆に、案1の場合はブラウザのキャッシュを削除するという通常のWebブラウジングでは行わない操作を購入者に要求してしまう上に、BASE以外のサイトのキャッシュも削除してしまうことになります。もちろん特定のサイトのみのCookieを削除することもブラウザの機能としては可能ですが、その操作はさらに難易度の高いものです。
また、案1については重複したCookieが削除されるタイミングがブラウザ依存であるため、インシデントの収束タイミングを把握できないという問題点がある以上避けるべきだろう、という意見もありました。
そうして、上記のような理由から安全かつ完全な形で障害から復旧させる方法である、Cookieのkey名を変更してからリリースする、という方法を実施する決断を行いました。
リリースに当たっては、万全を期すために調査で判明したsession.cookieの定義を使用している処理を全て網羅するテストケースを作成すると共に、QAチームが使用しているリグレッションテスト項目を共有してもらい、購入者の一般的な操作を全て動作確認することでより安全性を高めました。
これによってさらに障害の復旧までに時間を要することにはなりますが、より安全かつ完全に対応するためには必要な作業だというのが開発チームの共通認識でした。
おわりに
今回、障害の発生を認識してから収束するまでの間に2週間という時間がかかってしまった点と、対応の副作用によって購入者様の皆様にご迷惑をおかけしてしまったことは大変申し訳なかったと感じています。
サービスを提供していく上で、障害を起こさないように気をつけることは重要なことです。そして、万が一障害が発生してしまった際には、如何に素早く影響を最小限に留めて適切な対処で障害を解決することができるか、という点もまた重要なことです。
BASEでは、このようにBASEが提供するサービスを利用してくださる購入者やショップオーナーの皆様のことを第一に考えてサービスを共に発展させていく仲間を募集しております。 カジュアル面談も実施しておりますので、ぜひお気軽にお問い合わせください。