インフラ設定は「人の目」に頼るな──Policy as CodeでCIを番人にした話
セキュリティグループのCIDR設定について、同じ指摘を3回レビューコメントに書いた。1回目は「うっかり」で済ませ、2回目で「仕組みで防げるな」と思い始め、3回目のとき「これはレビューで防ぐ問題ではない」と腹が決まった。
アプリ開発者からキャリアをスタートしSREに転向し、Webサービスの立ち上げからPlatform SREまで4社で担ってきた。あるWebサービスのSREチームで、Policy as CodeをCIパイプラインに組み込んだ。この記事はその記録だ。
何が問題だったか
当時、複数の開発チームがTerraformでインフラを管理していた。変更のたびにSREチームへのレビュー依頼が来る運用だったが、問題が2つあった。
一つは「レビュアーによって指摘内容が変わる」ことだ。自分がいるときはセキュリティグループの開放範囲を必ずチェックするが、別のメンバーが担当した日は見落とされることがあった。「チェックすべきこと」が個人の記憶に依存していた。
もう一つは「同じ指摘が繰り返される」ことだ。指摘されたその人は直すが、別のチームの別の担当者が1ヶ月後に同じ設定をする。レビューで指摘できても、組織全体の設定品質は上がらない。
コードレビューは発見のツールだ。防止のツールではない。この違いを当時の私は軽く見ていた。
Policy as Codeとは何か
一言で言うなら、「インフラやアプリケーションの設定が満たすべきルールを、コードとして記述し、自動的にテストできる形にすること」だ。
人間がレビュー時に目でチェックしていた「このポートは開けてはいけない」「このタグは必須」「暗号化は有効にすること」といったルールを、テストコードとして定義する。人の記憶ではなくコードがルールを保持し、CIがそれを実行する。
ツールの選定ではいくつか候補を比べた。Checkovのような専用ツールはセキュリティベストプラクティスの観点で豊富な組み込みルールを持つが、「このタグは必須」「このIAMロールはこの範囲まで許可」のような組織固有のルールを柔軟に定義するには向いていなかった。OPA(Open Policy Agent) と Conftest の組み合わせはRego言語で任意のルールを記述できる汎用性があった。学習コストを払う価値があると判断し、この2つを選んだ。
OPAはTerraformもKubernetesも対象を選ばずポリシーを書けるエンジンで、ConftestはそのCLIラッパーだ。conftest test terraform.tfplan.json の一行でCIから呼び出せる手軽さが決め手だった。
「OPAとConftestを選べばうまくいく」わけではなかった。選定より先に決めることがあった。
実際にやったこと:3ステップ
ステップ1:ポリシーを洗い出す
最初にやったのは「今まで人がレビューで指摘してきたルール」をすべて書き出すことだった。
過去のレビューコメントを遡り、繰り返し登場する指摘を抽出した。セキュリティグループの開放範囲、必須タグの抜け、S3やRDSの暗号化設定、IAMの過剰権限──繰り返し同じ指摘が出ていた。
「Regoで書けるか」より「何をテストすべきか」を先に決めることが重要だった。ルールの洗い出しをスキップすると、言語の学習と設計を同時進行させることになり、収拾がつかなくなる。
ステップ2:Regoポリシーを書く
Regoは独特な言語で、最初の学習コストがある。RubyでもPythonでもない宣言的な書き方に慣れるまで、しばらく時間がかかった。
セキュリティグループのingress設定をチェックするポリシーのイメージはこんな形だ(概念的な例):
package terraform.security_group
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_security_group_rule"
resource.change.after.cidr_blocks[_] == "0.0.0.0/0"
resource.change.after.type == "ingress"
msg := sprintf(
"セキュリティグループ %v に 0.0.0.0/0 のingress設定があります",
[resource.address]
)
}
エラーメッセージに「何がどのリソースに問題なのか」を含めることは、後で運用する開発チームへの配慮として特に重要だった。「DENY」とだけ表示されても、何を直せばいいかわからない。
ステップ3:CIに組み込む
GitHub Actionsに組み込んだ。terraform show -json でplanをJSONに変換し、そのJSONに対してConftestを実行する流れだ。
ポイントは「PRを出した段階でチェックを走らせる」ことだ。terraform apply の前ではなく、レビュー依頼の時点でポリシー違反があれば自動的にCIが落ちる。レビュアーが指摘する前に、作業者自身がCI結果で気づける状態になった。
運用して気づいた落とし穴
CIが動き始めてから、想定外の問題が出た。
一番痛かったのは誤検知だ。最初に書いたポリシーの条件が広すぎて、許可してよいケースまで弾いてしまった。開発チームから「CIが通らないが設定は正しいはずだ」という問い合わせが増え、ポリシーへの信頼が落ちた。判定条件が広すぎたのが原因で、Regoのdenyルールを絞れば直せる問題だった。
ところが、誤検知を減らそうとして例外条件を次々に追加したら、今度はRegoが複雑になりすぎて誰も手を入れられなくなった。前者とはまた別の問題だ。シンプルなルールから始めて、実際に誤検知が出たときだけ例外を足せばよかった。最初から例外を設計しようとしたから複雑になった。
もう一つ想定が甘かったのは、Regoの保守だ。最初に書いた人間しかRegoを読めない状態になった。ポリシーを書いた本人が不在のときに「ポリシーを修正してほしい」という依頼が来て、初めてそれに気づいた。OPAのドキュメントとサンプルをREADMEに置き、他のメンバーが修正できる状態を維持することが必要だと、手遅れになってから学んだ。
リポジトリの分離も後悔した。TerraformコードはGit管理されているが、ポリシーを別リポジトリに置いていた。設定の変更とポリシーの変更を同時にレビューできない状態は、後から「この変更に対応するポリシー修正がどこにあるか」を追いにくくした。設定コードとポリシーは同じリポジトリに置く方が、変更の文脈が見えやすかった。
何が変わったか
Policy as CodeをCIに組み込んでから、「セキュリティグループが0.0.0.0/0になっている」という指摘をコードレビューで書かなくなった。
正確に言うと、書けなくなったのではなく、書く前にCIが止めるようになった。
レビュアーの経験値に依存していたチェック項目が、コードに移った。新しいチームメンバーが設定を変更しても、ルールを知らなくても、CIが教えてくれる。レビューは「ルールに合っているか」の確認から「この設計判断は妥当か」という議論に集中できるようになった。
AIコードレビューが普及した今も、Policy as Codeの価値は変わらないと思っている。AIレビューは確率的で、同じコードに対して毎回同じ指摘をするわけではない。Policy as Codeは決定論的だ──条件を満たせば必ず検知する。コンプライアンス要件や監査の文脈では、「AIがOKと言った」ではなく「このポリシーに照らして合格した」という記録が求められる場面がある。それに、AIが生成したコード自体がポリシー違反を含む可能性もある。AIがインフラ設定を書く時代だからこそ、設定品質を機械的に担保する仕組みの意味は薄れないと考えている。
ただし、Policy as Codeで防げるのは「言語化できたルール」だけだ。「なんとなくこの設計は後で問題になりそう」という経験から来る勘は、コードにできない。そこは引き続き人が判断する領域だ。
まとめ
インフラ設定のレビュー品質がレビュアー依存になっているなら、Policy as Codeは構造的な解決策になりえる。
OPAとConftestの組み合わせは、導入のハードルは高くない。もっとも、やってみてわかったのは、Regoの学習コストと誤検知・保守コストは最初から想定に入れておかないと後で詰まる、ということだった。
「人がレビューで指摘してきたルール」を洗い出すことから始めると、何をテストするかが明確になる。ツールの選定はその後でいい。
Policy as Codeをこれから入れる人がいたら、過去のレビューコメントを遡って繰り返し出ている指摘を3つ洗い出すところから始めるのを薦めたい。何をテストすべきかが先に決まれば、ツール選定もRegoの学習もずっと楽になる。
この記事は、複数の組織でSREとして働いてきた筆者の経験をもとにしています。一部、経験から推測される行動・思考パターンを含む記述があります。
Discussion