Cloudflare の費用は毎日自動でチェックしている。閾値を超えたら Slack に来る。

でも、設定の変更は誰も見ていなかった。 DNS のレコードを書き換えられても、Access のポリシーに bypass を足されても、気づく手段が無い。

実際、あるサービスの Access に「Bypass from Japan」というルールが残っていたのを、偶然見つけた。いつ足したのか分からない。偶然見つかるものは、偶然見つからないこともある。

監査ログは、読めなかった

まず考えたのは、Cloudflare のアカウント監査ログを API で読むことだ。誰が・いつ・何を変えたかが出る。

トークンの権限カタログに、それを読む項目が無い。 Access: Audit Logs という似た名前の権限はあるが、これは Access 専用で別物だ。account 階層の権限を全部並べて確認したが、アカウント監査ログを読む権限は選べない。

読むには Global API Key が要る。それは権限を絞ったトークンに移行するときに手放したものだ。監査ログのために全権の鍵を戻すのは本末転倒だった。

代わりに tofu plan

Cloudflare の設定は OpenTofu(Terraform)で管理している。61リソース。

tofu plan は、コードに書いてある状態と、Cloudflare 側の実物を全部突き合わせる。差分があれば出る。「誰が」は分からないが、「何が変わったか」は確実に出る。そして権限は既にある。plan に必要な読み取り権限は、apply のために持っているものと同じだ。

つまり監査ログの代わりに、状態の差分を毎朝取ればいい。

終了コードで判定する

tofu plan には -detailed-exitcode というオプションがある。

終了コード意味
0差分なし
2差分あり
1plan 自体が失敗(認証エラー、API 障害など)

これをそのまま判定に使う。

  • 2(差分あり)→ 🚨 を Slack へ。リソース名を添える
  • 1(検知失敗)→ ⚠️ を Slack へ。監視自体が動いていないので、これも知らせる
  • 0(差分なし)→ 黙る

「差分なしのときは黙る」は、そのままだと監視が死んでいるのと区別がつかない。だから最後の成功時刻をメトリクスに出して、36時間更新が無ければ Grafana が鳴らす。値のアラートと鮮度のアラートを1組にする、いつもの型だ。

タイマーは毎朝08:40。費用チェックの08:10とはずらしてある。

通知にはリソース名だけを載せる

差分があったとき、plan の出力をそのまま Slack に流したくなる。やらない。

plan の出力には、値が混ざる。 DNS のレコード値、Access のポリシーの中身、トークンの一部。Slack に流れると、そこに残る。

通知に載せるのはリソース名だけにした。cloudflare_record.grafana_omikuji_dev のような名前が来れば、何が変わったかは分かる。中身は自分で plan を回して見る。

実証した

作ったら、必ず発火させる。

DNS レコードの comment を API で書き換えて、わざとドリフトを作った。plan を回すと差分を検知し、🚨 が Slack に届き、リソース名が出た。tofu apply で戻したら、検知が0に戻った。

両方向を確認した。 差分があれば鳴る、無くなれば止まる。片方だけ確認して安心すると、鳴りっぱなしか、鳴らないかのどちらかになる。

運用の注意

自分で tofu apply する前後は、当然 🚨 が出る。 心当たりがあるかは人間が判断する。これは仕様で、apply の前に「これから変える」と分かっているので、通知が来ても驚かない。

直し方は「Cloudflare 側を手で戻す」ではなく tofu apply。 意図した状態はコードのほうで、Cloudflare 側の実物はそれに合わせるものだ。手で戻すと、次の plan でまた差分が出る。

まだ見えていないもの

Terraform で管理していないものは、この方法では見えない。権限が足りず、未監視のまま残っているものがある。

監視したいもの必要な権限
API トークンの新規作成User → API Tokens Read / Account → Account API Tokens Read
アカウントメンバーの追加User → Memberships Read

どちらも「攻撃者がまずやりそうなこと」だ。トークンを1つ作られたら、以後はそのトークンで何でもできる。必要になったら権限を足す。

まとめ

  • 費用の監視と、設定の監視は別物。 費用を毎日見ていても、DNS を書き換えられたことには気づけない
  • Cloudflare のアカウント監査ログは、API トークンの権限カタログに読む項目が無い。Global API Key が要る
  • 代わりに tofu plan -detailed-exitcode を毎朝回す。「誰が」は分からないが「何が変わったか」は確実に出る。権限は既にある
  • 終了コード 0 / 1 / 2 で「差分なし / 検知失敗 / 差分あり」を分ける。検知失敗も知らせる
  • 黙るアラートには鮮度のアラートを添える
  • 通知にはリソース名だけ。plan の出力には値が混ざる
  • 作ったら発火させる。差分を作って鳴らし、戻して止まるまで確認する
  • 直し方は tofu apply。コードが正で、実物を合わせる

監査ログが読めないなら、状態の差分で代替する。「誰が」は諦めて「何が」だけ取る。それで十分だった。