Webhook の信頼性について書かれたものの多くは、失われたイベントをデータの問題として扱います。行が欠け、いずれ気づき、補完します。面倒ではあっても取り戻せて、エンジニア以外にはほとんど見えない問題です。
ID 関連のイベントは、この捉え方を壊します。2 つの方向が対称ではないからです。
届かなかった access.granted イベントは、アクセスできないユーザーを生みます。アクセスできないユーザーは、数分以内にそれを知らせてきます。問題が自己申告されるのです。このシステムには、人間によるアラートの仕組みがタダで組み込まれています。
届かなかった access.revoked イベントは、まだアクセスできるユーザーを生みます。それを報告する人はいません。得をする本人にはわざわざ言う理由がなく、ほかの全員は、ID プロバイダーが完了と表示しているのでオフボーディングは済んだと思っています。この穴は監査で見つかるまで残り、それが 1 年後になることもあります。
TL;DR
- 権限付与の取りこぼしは、サポートへの問い合わせで済みます。権限剥奪の取りこぼしは、警報の付いていない開いたドアです
- ID プロバイダーのダッシュボードには、権限剥奪は成功したと表示されます。プロバイダー側では実際に成功しているからです
- Clerk をはじめ、Svix の上に作られたサービスは、約 27.5 時間で 8 回の試行を行い、すべて失敗する状態が 5 日続くとエンドポイントを無効にします
- エンドポイントが無効になった状態そのものが、何の知らせもない状態です。そして、いちばん重要なのはこの状態です
- Webhook を受信する側の可用性は、インフラの衛生管理ではなく、アクセス制御として扱ってください
ダッシュボードには表示されない障害
管理者が誰かを組織から外します。ID プロバイダーは自分の記録を更新し、自分のセッションを無効にし、organizationMembership.deleted、user.deleted、session.revoked のいずれかをエンドポイントに送ります。
そのときエンドポイントはデプロイの途中で、502 を返します。
ID プロバイダーのコンソールでは、メンバーシップは消えています。管理者から見れば、オフボーディングは完了しています。アプリケーションには、この人がその組織のメンバーだという行がまだ残っていて、最初の権限付与をもとに発行したキャッシュ済みのセッションや API トークンも、まだ有効です。
人が目にする場所のどこにも、エラーは表れません。プロバイダーの Webhook ログには失敗した配信が残っていますが、すでに何かを疑っていない限り、Webhook のログを読む人はいません。
リトライは時間を稼ぐが、いずれ止まる
Clerk は Webhook を Svix 経由で送っていて、ほかの ID 関連やインフラ系の製品にも同じものが少なくありません。そのスケジュールは公開されていて、正確に知っておく価値があります。権限剥奪のリトライを Svix がやめるまでに、エンドポイントがどれだけ壊れていてよいかが、それで正確に決まるからです。
試行は 8 回です:「直後、5 秒、5 分、30 分、2 時間、5 時間、10 時間、10 時間(いずれも前回からの間隔)」。
合計で約 27.5 時間をカバーし、これは本当に寛大で、Webhook プロバイダー別リファレンスにある送信元の中でも最長クラスです。デプロイの時間帯も、インシデントも、さんざんな金曜日も、余裕で収まります。8 回目の試行の後、Svix は message.attempt.exhausted タイプの運用 Webhook を送ります。ただし役に立つのは、運用 Webhook の届け先を用意している場合だけです。
次に、2 つ目のしきい値があります:「特定のエンドポイントへのすべての試行が 5 日間失敗し続けると、そのエンドポイントは無効になり、運用 Webhook(EndpointDisabledEvent)がアカウントに送られます」。この時計には条件があります:「時計が動き始めるのは、24 時間以内に複数の配信が失敗し、かつ最初と最後の失敗の間が 12 時間以上空いている場合だけです」。
無効化された状態の意味は、注意して読んでください。エンドポイントが無効になると、新しいイベントはそのエンドポイント向けにキューに入りません。エンドポイントを直した後で配信されることもありません。その間の権限剥奪はすべて、誰かが気づいて再送するまで未配信のままです。そして唯一の知らせは、誰も届け先を設定していないアカウントに送られる運用 Webhook です。
非対称性を設計に組み込む
2 つの方向でコストが違うと受け入れると、ふだんの判断がいくつか変わります。
アクセスを取り除くイベントでは、フェイルクローズにしてください。ハンドラーが session.revoked を処理できないとき、200 を返して警告をログに出すのは正しい振る舞いではありません。5xx を返してリトライスケジュールを動かし、自分のアラートが確実に発火するようにします。権限付与なら楽観的でもかまいません。権限剥奪ではそうはいきません。
Webhook だけに頼らないでください。プッシュされるイベントはレイテンシーの最適化であって、信頼できる唯一の情報源ではありません。ID プロバイダーの API に対して「今アクセスできるべきなのは誰か」を定期的に照合すれば、永久に取りこぼしたままの権限剥奪が、次の照合で自然に直るものに変わります。これは「Webhook とポーリングの違い」で書いたハイブリッド構成と同じ議論で、ID はそれが任意ではなくなるケースです。1 時間ごとに実行すれば、最悪のケースは永遠ではなく、1 時間分の古いアクセス権で済みます。
自分のセッションには有効期限を設けてください。ID のイベントをもとにアプリケーションが独自のセッションやトークンを発行しているなら、TTL を短くし、更新時にメンバーシップを確認し直せば、取りこぼした権限剥奪もそのうち自然に期限切れになります。有効期間の長いトークンは、取りこぼした Webhook 1 件を、無期限のアクセスに変えてしまいます。
ここでは順序も重要です。member.removed の後にリトライされた member.added は、外したばかりの人を再び追加してしまいます。しかも、2 回処理されたイベントはありません。タイムスタンプが行のものより新しい場合だけ更新を適用するバージョンガードのパターンは、同じ Webhook を 2 回受け取ったときの記事にあります。アクセス制御では、タイムスタンプの比較に頼るより、不正な状態遷移をはっきり拒否するほうを選んでください。
エンドポイントが無効になると何が見えるのかを知っておいてください。これは、この分野全体でいちばん価値のあるアラートです。ID の Webhook がふだんの間隔より長く静かなら、何かがおかしいのです。そして「静か」こそが、無効化された状態のそのままの症状です。「営業時間中に 6 時間 ID イベントがない」というチェックなら、それを捉えられます。ほかの方法では捉えられません。
監査の質問にどう答えるか
SOC 2、ISO 27001、あるいは顧客からのセキュリティ質問票に対応していれば、適時のプロビジョニング解除についての質問があるはずです。権限剥奪の経路が照合のない Webhook 1 本だけなら、正直な答えはこうです:プロビジョニング解除はすぐに行われる、ただし何の知らせもなく行われないときを除いて。しかも後から、その違いを見分けることはできません。
これは書面に残しにくい答えです。同時に、2 つの要素で直せます:届いたすべての ID イベントの永続的なログと、その結果を示せる照合ジョブです。ログは「何を、いつ知らされたか」に答え、照合は「それに対応したか」に答えます。1 つ目の質問に対して、変更可能なデータベースの行が弱い答えにしかならない理由は、改ざん検知可能な Webhook ログの記事に書きました。
AnyHook の位置づけ
構造的な問題は、受信側の可用性がアクセス制御の一部になっていることです。そして、アプリケーションのインフラの上に作った受信側は、アプリケーションの稼働率をそのまま引き継ぎます。
ID プロバイダーの送信先を in.anyhook.net/you/identity にすれば、この 2 つを切り離せます。AnyHook はエッジでイベントを保存したうえで、約 1 秒で応答します。そのため、そちら側のデプロイ、コールドスタート、データベース障害があっても、配信の失敗にはまったくなりません。その後は次のとおりです:
- 自分のエンドポイントへのリトライは、5 分 → 30 分 → 3 時間 → 18 時間 → 以降は 1 日ごとに行われます。Scale(約 6 日)なら Svix の 27.5 時間より長く続きます。Pro では最後の試行が約 22 時間後、Free では 35 分後で、それ以降は保存済みのイベントを再送します
- 権限剥奪のイベントはすべて、配信を試みる前に保存され、保存時に暗号化されます。そのため「そもそも受け取ったのか」に、アプリケーションのログとは無関係に答えられます
- 期間を指定して再送できるので、デプロイで壊れていた時間帯への対処は、誰がアクセスを失うべきだったかを手作業で監査することではなく、配信のやり直しになります
- 1 回、5 回、20 回連続の失敗で届く通知は、どのプロバイダーの無効化のしきい値よりもずっと前に届きます
それでも照合は不要になりません。照合は引き続き実行してください。中継サービスは、受け付けたイベントをすべて保存します。一度も送られなかったものを捉えられるのは、照合だけです。
まとめ
権限付与は、影響を受けた本人が苦情を言うので自然に直ります。権限剥奪は直りません。影響を受けた本人が何も言わないからです。ペイロードは同じように見えても、ID の Webhook にほかの連携より厳しい信頼性の基準が必要なのは、この非対称性ひとつのためです。
削除のイベントではフェイルクローズにし、自分のセッションに有効期限を設け、照合を定期的に実行し、予期しない静けさにアラートを設定してください。そうしない場合に残るのは、壊れたときに沈黙するセキュリティ対策です。