OPAとRego言語によるポリシー実装
許可・拒否の判断ロジックをアプリのif文から切り離したい。OPAの評価モデルとRegoの集合演算を原理から押さえれば、K8sもAPI認可も同じエンジンで再利用できる。
- 1.OPAの評価は decision = eval(query, input, data) という純粋関数。input は都度のリクエスト、data はロードされたルール+静的コンテキストで、両者は評価時に1つのドキュメント木にマージされる。
- 2.Regoのルールは真偽ではなく集合を生成する。deny[msg]{...} は条件を満たす全解を列挙し、未束縛変数は明示ループなしに全要素を走査する(unification)。空集合なら違反なしという発想が中心。
- 3.Kubernetes admission ではOPA(Gatekeeper)がwebhookの背後で常駐評価し、API認可ではenvoyのext_authzやサイドカーからgRPC/HTTPで同期的にeval呼び出しする構成が典型。
OPAが解いている問題:判断ロジックの分離
認可判断(この操作は許可されるか)を各アプリのif文に埋め込むと、ルールが変わるたびに全サービスを再デプロイする羽目になります。Open Policy Agent(OPA) は、この判断ロジックを汎用の評価エンジンとして切り出し、アプリ・K8s・ゲートウェイなど呼び出し元を問わず同じインターフェースで問い合わせられるようにします。核となる関数は次の形です。
result = eval(query, input, data)
# query : 何を問うか(例: data.kubernetes.deny)
# input : リクエストごとに変わる値(Pod マニフェスト、HTTP リクエスト等)
# data : ロードされたRegoルール+事前投入された静的/半静的な参照データ
input と data は別の引数に見えますが、評価時には同じドキュメント木にマージされます。data 以下にルールも外部データ(許可リストなど)も置かれ、input はルートの input キーとして接続される、という位置づけの違いだけです。この統一により、Regoのルールは「今来たリクエスト」と「既知の事実(この名前空間は本番か等)」を同じ式の中で参照できます。
Regoの評価モデル:ルールは集合を返す
Regoは命令的な手続きではなく、論理クエリ言語です。最も誤解されやすい点は、ルールが真偽値ではなく**集合(またはオブジェクト、または単一値)**を生成することです。
package kubernetes.admission
deny[msg] {
input.request.kind.kind == "Pod"
some i
container := input.request.object.spec.containers[i]
not container.resources.limits
msg := sprintf("container %v にresources.limitsがありません", [container.name])
}
deny は集合であり、この評価は「ルール本体を満たす全ての解を列挙して集合に加える」処理です。some i と containers[i] の i は未束縛変数で、Regoエンジンはこれを明示的なforループとしてではなく、単一化(unification)による全解探索として展開します。containers が3要素なら3通りの束縛を試し、条件を満たすものだけが msg を生成して集合に残ります。ルール本体内の複数の式は暗黙に論理AND、同名ルールを複数書けば論理ORになります。
判定側は「集合が空か否か」だけを見ます。deny が空集合ならallow、要素が1つでもあれば違反です。これは分岐命令ではなく集合演算の結果であり、部分的な違反理由(msg の中身)を保持したまま合否を導けるのが手続き型のif連鎖と質的に異なる点です。
Regoのルールヘッドは deny[msg]{...}(集合)、deny[key]=val{...}(オブジェクト)、allow = true {...}(単一値の完全ルール)の3系統があります。同じルール名に複数の本体を書けば、いずれかが真になった時点でそのルールは成立(増分的な論理OR)。単一値ルールだけは矛盾した2つの値を生成すると評価エラーになり、これはOPAが「一貫した答えを返せない」ことを検出する安全弁です。
データ設計:input/dataとパーシャル評価
data に何を入れるかは設計判断です。頻繁に変わらない参照データ(例:許可されたイメージレジストリの一覧、環境ごとのラベル必須ポリシー)は data 配下にロードしておき、input はリクエスト固有の値だけに絞ります。こうすると同じRegoルールを、テスト時は固定の data で、本番では定期更新される data で、と切り替えて再利用できます。
| input | data | |
|---|---|---|
| 更新頻度 | 評価のたびに変わる | ロード時に固定、外部から定期更新も可 |
| 内容の例 | Pod マニフェスト、HTTP リクエストのメソッド/JWT | 許可イメージ一覧、Rego ルール本体、環境メタデータ |
| 役割 | 問い合わせの主語 | 判断の前提となる世界の状態 |
OPAはルールをコンパイルする際に**パーシャル評価(partial evaluation)**も行えます。data 側が固定なら、その部分をあらかじめ畳み込み、input だけに依存する簡約された式を生成できます。これはKubernetes admissionのように高頻度・低レイテンシが要求される経路で、評価コストを事前に削るための実務上の最適化です。
Kubernetes Admission Controllerへの組み込み
OPAをKubernetesで使う標準構成がGatekeeperで、ValidatingWebhookConfiguration の背後にOPAを常駐させます(webhookの発火順序やmutating/validatingの区別自体は/devops/admission-control-policy/で扱った通りです)。ここで重要なのはOPA固有の抽象化で、Gatekeeperは生のRegoを直接デプロイさせず、**ConstraintTemplate(ルールの型=Regoコード)とConstraint(適用パラメータ+対象範囲)**を分離します。
package k8srequiredlabels
violation[{"msg": msg}] {
provided := {label | input.review.object.metadata.labels[label]}
required := {label | label := input.parameters.labels[_]}
missing := required - provided
count(missing) > 0
msg := sprintf("必須ラベルが不足: %v", [missing])
}
ここでの required - provided は集合差です。Regoは {...} 内包表記で集合を構築でき、- 演算子で差集合を取り、count() が0より大きいかで違反有無を判定します。同じConstraintTemplate(ルール本体)を、Constraint側の input.parameters.labels を変えるだけで「本番は3つのラベル必須」「開発は1つだけ」のようにパラメータ化して使い回せるのが、Gatekeeperがテンプレートとパラメータを分けている理由です。
webhookのタイムアウトやfailurePolicyの設計はAPIサーバー側の関心事で、Rego側の集合演算の正しさとは独立した問題です。Regoルールが論理的に正しくても、webhookのレイテンシ予算を超えればfailurePolicy次第で素通り、またはAPI全体が詰まります。この2層を混同してデバッグすると原因の切り分けを誤ります。
API認可への組み込み:ext_authzパターン
OPAはKubernetes専用ではなく、汎用の判断エンジンとしてHTTP/gRPCで問い合わせられるのが本質です。API Gatewayやサービスメッシュのenvoyにはext_authzフィルタがあり、各リクエストごとにOPAへ同期的に問い合わせて許可/拒否を得ます(envoy自体のデータパスは/devops/service-mesh-sidecar-internals/を参照)。
package httpapi.authz
default allow := false
allow {
input.method == "GET"
input.path == ["orders", order_id]
input.token.sub == data.orders[order_id].owner
}
ここで default allow := false は「どのルールも成立しなければfalseを返す」というフェイルクローズの明示です。input.path を配列分解して order_id を取り出し、data.orders に事前ロードした所有者情報と input.token.sub(JWTのsubクレーム)を突き合わせる――これはRBACのような静的な役割判定ではなく、リクエストの中身とデータの関係を評価するABAC(属性ベース)的な判断で、admissionのオブジェクト検査とは異なる用途(誰が・何に対して)にRegoの同じ評価モデルを転用している例です。
(1) OPAはKubernetes専用ではない――Gatekeeperは数ある統合先の1つで、ext_authzやサイドカー経由のAPI認可も同じ eval(query, input, data) を呼ぶだけ。(2) Regoの「ルール」は真偽関数ではなく集合/値を返す式――deny が空かどうかで合否を導く発想がRego特有で、他言語のif文的な読み方をすると単一化とループ展開の意味を誤読します。
まとめ
- OPAの本質は
eval(query, input, data)という決定的な評価であり、input(都度の値)とdata(ロード済みルール+参照データ)は評価時に1つの木にマージされる。 - Regoのルールは真偽値ではなく集合/オブジェクト/単一値を生成する論理クエリで、未束縛変数は単一化による全解探索として展開される。空集合=準拠という判定様式が中核。
- KubernetesではGatekeeperがConstraintTemplate(ルール)とConstraint(パラメータ)を分離し、同じRegoをパラメータ違いで再利用する。API認可ではenvoyのext_authz等からgRPC/HTTPで同期呼び出しし、ABAC的な属性突き合わせに同じエンジンを転用できる。
Rego特有の集合演算と単一化の発想を押さえておけば、admissionでもAPIゲートウェイでも「同じエンジンに何を問い合わせているか」を読み違えなくなります。
DevOps/インフラ Article
OPAとRego言語によるポリシー実装を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
OPA
比較で見る軸
難易度: advanced / カテゴリ: DevOps/インフラ / タグ数: 6
導入後に効く点
Regoのルールは真偽ではなく集合を生成する。deny[msg]{...} は条件を満たす全解を列挙し、未束縛変数は明示ループなしに全要素を走査する(unification)。空集合なら違反なしという発想が中心。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- DevOps/インフラ
- タグ数
- 6
判断チェックリスト
- 自社の用途が「OPA / Rego」に近いか確認する。
- 強みである「OPAの評価は decision = eval(query, input, data) という純粋関数。input は都度のリクエスト、data はロードされたルール+静的コンテキストで、両者は評価時に1つのドキュメント木にマージされる。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。