TL

OPAとRego言語によるポリシー実装

許可・拒否の判断ロジックをアプリのif文から切り離したい。OPAの評価モデルとRegoの集合演算を原理から押さえれば、K8sもAPI認可も同じエンジンで再利用できる。

応用OPARegoポリシーエンジンKubernetesAPI認可アクセス制御最終更新: 2026-06-21
TL;DR要点だけ先に
  • 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ルール+事前投入された静的/半静的な参照データ

inputdata は別の引数に見えますが、評価時には同じドキュメント木にマージされます。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 icontainers[i]i未束縛変数で、Regoエンジンはこれを明示的なforループとしてではなく、単一化(unification)による全解探索として展開します。containers が3要素なら3通りの束縛を試し、条件を満たすものだけが msg を生成して集合に残ります。ルール本体内の複数の式は暗黙に論理AND、同名ルールを複数書けば論理ORになります。

判定側は「集合が空か否か」だけを見ます。deny が空集合ならallow、要素が1つでもあれば違反です。これは分岐命令ではなく集合演算の結果であり、部分的な違反理由(msg の中身)を保持したまま合否を導けるのが手続き型のif連鎖と質的に異なる点です。

ルールヘッドの3形態

Regoのルールヘッドは deny[msg]{...}(集合)、deny[key]=val{...}(オブジェクト)、allow = true {...}(単一値の完全ルール)の3系統があります。同じルール名に複数の本体を書けば、いずれかが真になった時点でそのルールは成立(増分的な論理OR)。単一値ルールだけは矛盾した2つの値を生成すると評価エラーになり、これはOPAが「一貫した答えを返せない」ことを検出する安全弁です。

データ設計:input/dataとパーシャル評価

data に何を入れるかは設計判断です。頻繁に変わらない参照データ(例:許可されたイメージレジストリの一覧、環境ごとのラベル必須ポリシー)は data 配下にロードしておき、input はリクエスト固有の値だけに絞ります。こうすると同じRegoルールを、テスト時は固定の data で、本番では定期更新される data で、と切り替えて再利用できます。

inputdata
更新頻度評価のたびに変わるロード時に固定、外部から定期更新も可
内容の例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の外側とRego内側の責務は別レイヤー

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の同じ評価モデルを転用している例です。

混同しやすい2点

(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、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
  • 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。

次に確認する観点

OPARegoポリシーエンジンKubernetesAPI認可OPARegoポリシーエンジン