ポリシーアズコードと継続的コンプライアンス
「禁止構成は人のレビュー頼み」から卒業。インフラ・デプロイの制約をコードで宣言し、CI と admission で機械的に強制、ドリフト検知と監査証跡まで自動化する原理を解説。
- 1.ポリシーアズコードは『あるべき制約』を宣言的なコード(OPA/Rego・Kyverno・CEL 等)で書き、評価エンジンが入力(JSON 化した構成・リクエスト)に対し allow/deny を純粋関数として返す。判定はバージョン管理・テスト・再現が可能になる。
- 2.強制点(PEP)は CI のゲート、admission webhook、IaC の plan 検査の三層に置ける。早い層ほど安価に弾けるが、admission など実行時の層がないと経路を迂回した変更を止められない。
- 3.継続的コンプライアンスは『定期スキャン=検知』『強制=防止』『監査ログ=証拠』を分離する。ドリフトは実体を再評価して見つけ、判定の入出力を不変ログに残すことで証跡が自動生成される。
なぜ制約を「コード」にするのか
「本番の Pod は root で動かさない」「S3 バケットは暗号化必須」「タグのないリソースは作らせない」――こうした組織のルールは、長らく Wiki の文章とレビュアーの目視で守られてきました。問題は、自然言語のルールが実行可能でも検証可能でもないことです。レビュアーが見落とせば素通りし、ルールが更新されても古い構成は放置され、「いま全環境がルールに準拠しているか」を機械的に答えられません。
ポリシーアズコード(Policy as Code, PaC)は、この制約を宣言的なコードとして表現し、評価エンジンに機械判定させる考え方です。本質は、ポリシーを次の形の関数に還元することにあります。
decision = policy(input)
# input : 評価対象を JSON 化したもの(マニフェスト、Terraform plan、API リクエスト…)
# policy : 制約を記述したルール集合(Rego, CEL, YAML…)
# decision: allow / deny(+ 違反理由)
この関数は理想的には**純粋(副作用なし・決定的)**です。同じ入力には常に同じ判定を返すため、テスト・バージョン管理・コードレビュー・再現が普通のコードと同じようにできます。これが目視レビューに対する決定的な優位です。
評価エンジンの内部:Rego を例に
代表格が CNCF の OPA(Open Policy Agent) と、その言語 Rego です。Rego は宣言的なクエリ言語で、命令的な手順ではなく「違反が成立する条件」を書きます。
package kubernetes.security
# Pod が root 実行なら deny に違反メッセージが入る
deny[msg] {
input.kind == "Pod"
some c
container := input.spec.containers[c]
not container.securityContext.runAsNonRoot
msg := sprintf("container %v は runAsNonRoot=true が必須", [container.name])
}
ここで効いている原理が2つあります。第一に deny は集合(set) で、ルールを満たす全違反が要素として集まります。空集合なら準拠、要素があれば違反です。第二に、本体の各行は論理 AND で結ばれ、input.spec.containers[c] の c のような未束縛変数は全要素に対する反復として展開されます(内部的には全解の探索)。つまり「どれかのコンテナが runAsNonRoot 未設定なら…」を明示的なループなしに表現できます。
評価器は入力を**ドキュメント(木構造の JSON)**として読み、ルールを評価して仮想ドキュメントを生成します。Kubernetes の CEL も思想は近く、式が真偽を返す点は共通ですが、CEL は API サーバー内蔵で軽量・反復が限定的、Rego/Kyverno は外部エンジンで表現力が高い、というトレードオフがあります(/devops/admission-control-policy/ で強制点ごとに整理)。
強制点(PEP)をどこに置くか
ポリシーを書いても、それを**評価して実際に止める場所(Policy Enforcement Point, PEP)**がなければ絵に描いた餅です。PEP は実務上3つの層に置けます。
| 強制点 | 止めるタイミング | 止められるもの/弱点 |
|---|---|---|
| CI ゲート | マージ前・apply 前 | PR の構成を弾く。安価で速いが、CI を迂回した変更は見逃す |
| IaC plan 検査 | terraform plan の差分 | 適用前の差分を評価。実体ではなく宣言が対象 |
| admission webhook | API サーバーが etcd 保存する直前 | 経路を問わず実行時に強制。最後の砦だが運用ミスで全 API が詰まる |
重要なのは、これらが代替ではなく多層防御だという点です。CI ゲートは「速いフィードバックで開発者体験を守る」層、admission は「どの経路から来た変更も取りこぼさない」最終層です。CI だけだと、kubectl apply を直接叩く、別ツールが書き込む、といった迂回路を塞げません。逆に admission だけだと、違反に気づくのがデプロイ直前になり手戻りが大きくなります。
admission webhook は API サーバーが同期 HTTP で呼ぶため、failurePolicy を Fail にすると webhook 障害時に全 API 操作が止まります。逆に Ignore にすると障害時にポリシーが素通りします。可用性と強制力はトレードオフで、対象を matchConditions で絞り、タイムアウトを短く保つ設計が前提です。
ガードレールとポリシーの分類
PaC の制約は強制の仕方で大別できます。ハードガードレールは違反を deny で物理的に阻止します(防止的・preventive)。ソフトガードレールは警告のみで通し、後から是正を促します(発見的・detective)。組織を回す現実解は、リスクの高いルールだけハード、残りはソフトから始め、違反率を見て段階的に締める運用です。最初から全部ハードにすると、誤検知(false positive)で開発が止まり、ポリシー自体が無効化されます。
評価の観点でも、mutating(構成を改変して準拠させる。例:欠けたラベルを自動付与) と validating(改変せず合否だけ返す) を区別します。admission では mutating が先、validating が後に走り、validating 後はオブジェクトを変えられない――この順序は決定性のために重要で、検証した後で誰かが書き換える余地を残さないためです。
継続的コンプライアンス:検知・防止・証跡の三層
ここまでは「入る前に止める(防止)」話でした。継続的コンプライアンスはこれに検知と証跡を足し、3つの独立した役割に分解します。
- 防止(強制):PEP が違反を入口で止める。前述の三層。
- 検知(ドリフト):すでに存在する実体を定期的に再評価し、後から逸脱したものを見つける。
- 証跡(監査):いつ・何が・どのルールで・どう判定されたかを不変ログに残す。
検知が必要なのは、防止が完璧ではないからです。ポリシーが追加される前に作られたリソース、admission を持たない経路、緊急対応の手動変更などで、実体は静かにドリフトします。検知は実体(実クラウド・実クラスタの現状)を取得して JSON 化し、それを同じ policy(input) に通すことで、防止と同一のルールを再利用して逸脱を洗い出します。IaC では plan のリフレッシュがこの実体取得に相当します(/devops/iac-state-drift-locking/)。
GitOps とも自然に噛み合います。望ましい状態を Git に置き、収束ループが実体を Git に引き戻す仕組み(/devops/gitops-reconciliation/)に PaC を載せると、「Git にコミットされる構成」と「収束で適用される構成」の両方をポリシーで検査でき、ドリフトの是正と準拠の維持が一体化します。
監査証跡を後付けで作るのは大変ですが、PaC では policy(input) の input・ルールのバージョン・decision をそのままイベントとして不変ストレージに書き出せば、コンプライアンス証拠が判定のたびに自動生成されます。ポリシーが Git 管理なら、「この判定はどのコミットのルールで下されたか」までコミットハッシュで遡れます。これはサプライチェーンの来歴(/devops/sbom-slsa-provenance/)と同じ『主張を検証可能な記録にする』発想です。
ポリシー自体をどう正しく保つか
ポリシーはコードなので、コードと同じ品質問題を抱えます。誤って正当な構成を弾けば(false positive)開発が止まり、見逃せば(false negative)防御が穴になります。だからポリシーにはユニットテストが必須です。Rego なら test_ プレフィックスのルールで「この入力なら deny が空」「あの入力なら違反 1 件」を assert し、CI で回します。
(1) ポリシーの定義(言語/ルール) と ポリシーの強制点(PEP の場所) は別物――同じ Rego を CI でも admission でも使える。(2) 防止と検知は別の役割――admission は新規を止めるが既存のドリフトは検知側が担う。(3) mutating と validating の順序――改変が先、検証が後で、検証後は不変。この3点の取り違えが設計ミスの大半を生みます。
もう一つの原理的注意は判定の決定性です。ポリシーが現在時刻や外部 API の応答に依存すると、同じ入力でも判定が揺れ、テストも監査も再現できなくなります。外部データ(許可リスト等)が要る場合は、評価時点のスナップショットを input に焼き込み、policy(input) を純粋関数に保つのが鉄則です。
まとめ
- 制約をコード化する本質は、目視レビューを
decision = policy(input)というテスト・再現可能な純粋関数に置き換えること。 - 強制点は多層(CI ゲート・IaC plan・admission)。早い層は安く速く、実行時の層は迂回路を塞ぐ最終防壁。代替ではなく併用する。
- 継続的コンプライアンスは防止・検知・証跡を分離する。同じポリシーを実体に再適用してドリフトを検知し、判定の入出力を不変ログに残せば監査証跡が自動で揃う。
- ポリシー自体もテストし・決定的に保つ。これがなければ PaC は新たな見落としと不信の源になる。
「ルールは Wiki に書く」から「ルールはテストされ実行されるコード」へ。この転換が、準拠を人の注意力から機械の保証へ引き上げます。
DevOps/インフラ Article
ポリシーアズコードと継続的コンプライアンスを実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
Policy as Code
比較で見る軸
難易度: advanced / カテゴリ: DevOps/インフラ / タグ数: 6
導入後に効く点
強制点(PEP)は CI のゲート、admission webhook、IaC の plan 検査の三層に置ける。早い層ほど安価に弾けるが、admission など実行時の層がないと経路を迂回した変更を止められない。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- DevOps/インフラ
- タグ数
- 6
判断チェックリスト
- 自社の用途が「Policy as Code / OPA」に近いか確認する。
- 強みである「ポリシーアズコードは『あるべき制約』を宣言的なコード(OPA/Rego・Kyverno・CEL 等)で書き、評価エンジンが入力(JSON 化した構成・リクエスト)に対し allow/deny を純粋関数として返す。判定はバージョン管理・テスト・再現が可能になる。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。