Rego(レゴ/公式の発音は「ray-go」)は、Open Policy Agent(OPA)で認可やバリデーションのルールを書くための宣言的なポリシー言語です。書き方は2024年12月20日のOPA 1.0で大きく変わり、allow { ... } のような旧来の構文はいまのOPAでは構文エラーになります。さらにその後も、文字列補間(1.12.0)、and・or(1.20.0)、import rego.v2(1.22.0・2026年10月29日)と、言語仕様は追加が続いています。この記事のコードは、すべて最新版のOPA 1.22.0で opa check --strict と opa test を通したものです。
まとめ
- Regoは「どんな条件なら許可するか」を宣言する言語。OPAは、リクエスト情報の
input、事前に読み込んだdata、ポリシー本体の3つを突き合わせ、結果をJSONで返す。 - 現行の書き方はRego v1。ルール本体の前に
if、集合を返すルールにcontainsが必須。v0のコードはopa fmt --v0-v1で自動変換できる。 - OPA 1.22.0で
import rego.v2が入った。and・orと改良版のnotを1行で有効にする。1.21系以前のOPAでは読み込みエラーになるため、実行環境の版を揃えてから使う。 - Gatekeeperは既定がRego v0のまま。3.19以降はConstraintTemplateに
version: "v1"を書けばRego v1を使える。書かずにif付きのコードを貼ると動かない。 - テストは
opa test、静的検査はopa check --strictとRegal。拒否されるべき入力をnotで必ずテストに入れる。
Regoの意味と、OPAがポリシーを評価する仕組み
OPAは、アプリケーションから認可の判断を切り離して外部で評価する汎用ポリシーエンジンで、2021年にCNCFを卒業(Graduated)しています。エンジンの全体像や導入形態はOPAとは?Open Policy Agentの意味・仕組みと使い方【v1.21で実行】で扱っているため、ここではポリシーを書く言語であるRegoに絞ります。公式ドキュメントによれば、RegoはDatalogに着想を得て、JSONのような入れ子の構造化データを扱えるよう拡張した言語です。手続きを書くのではなく「クエリが何を返すべきか」を書きます。
input・data・policyの3要素
input は判定のたびに渡されるリクエスト情報(誰が・何に・どの操作をするか)、data はOPAに読み込ませておく参照データ(ロール定義や許可リスト)、policyは拡張子 .rego のファイルに書いたルールです。data にはファイル・バンドル・APIなどで読み込む参照データが含まれます。リクエストごとに変わる値は input に入れます。ここを混ぜると、値が変わるたびにデータの再配信が必要になります。
Policy as CodeによるRegoの用途横断利用
Regoで書いたポリシーはGitで管理し、レビューとテストをCIに載せられます。同じ言語でKubernetesのAdmission Control、Terraformの計画ファイルの検査、マイクロサービスのAPI認可を書ける点が、用途ごとに別のポリシー機能を覚える方式との差です。Terraform側の書式統一はTerraformコーディング規約とterraform fmtの使い方|公式スタイルガイド準拠の命名・コメント・CI強制のような静的な規約に任せ、Regoには「本番でpublicなバケットを作らない」といった意味の検査を担わせると役割が重なりません。
Rego v1の基本構文と演算子の使い分け
ルール本体のifと、複数ルールによるOR
ルールは頭部(何を決めるか)と本体(どの条件のときか)でできています。Rego v1では本体を持つルールすべてに if が必要です。本体の中で改行して並べた条件はAND、同じ名前のルールを複数書くとORになります。
package authz
default allow := false
allow if input.user == "admin"
allow if {
input.method == "GET"
input.path == ["public", "index"]
}
default allow := false は、どのルールにも当てはまらないときの値です。書かないと結果はfalseではなくundefined(値なし)になり、REST APIの応答から result キーそのものが消えます。認可ポリシーでは必ず書きます。
集合を返すcontainsと、every・notによる検査
違反メッセージの一覧のように集合を返すルール(partial set rule)には contains を使います。some x in コレクション は「どれか1つでも条件を満たせば成立」、every は「全要素が満たせば成立」です。ただし、対象が空の配列や集合でも成立するため、要素の存在も必須なら count による検査を併用します。
package deploy
deny contains msg if {
some c in input.spec.containers
not c.securityContext.runAsNonRoot
msg := sprintf("container %v must set runAsNonRoot", [c.name])
}
deny contains "all images must be pinned by digest" if not all_pinned
all_pinned if {
every c in input.spec.containers {
# 簡略例: 部分文字列だけを確認し、ダイジェストの形式や実在性は検証しない
contains(c.image, "@sha256:")
}
}
コンテナ2つ(片方だけ runAsNonRoot とダイジェスト固定あり)のPodを渡すと、data.deploy.deny は ["all images must be pinned by digest","container sidecar must set runAsNonRoot"] を返しました。not には注意点があります。参照したキーが存在しないと式はundefinedになり、not を通すと真になります。上の例では securityContext が無いコンテナも拒否されますが、これは意図どおりです。「値がfalseのときだけ拒否したい」場合は c.securityContext.runAsNonRoot == false と書きます。
:= と == と = の使い分け
:= はローカル変数への代入で、同じスコープでの再代入はコンパイルエラーになります。== は比較です。単独の = は単一化(unification)で、代入にも比較にも解決されうるため意図が読み取りにくくなります。新しく書くコードでは := と == を使い分けてください。opa check --strict は未使用の変数・未使用のimport・input や data と同名の変数をエラーにするので、CIに入れておくと取りこぼしが減ります。
内包表記・関数・組み込み関数によるデータ加工
判定の前に「イメージ名だけの一覧」「コンテナ名から上限メモリへの対応表」を作りたい場面は多く、そこで使うのが内包表記(comprehension)です。配列は [ ]、集合は { }、オブジェクトは {キー: 値 | ...} で書きます。関数は引数付きのルールとして定義します。
package deploy
images := [c.image | some c in input.spec.containers]
names := {c.name | some c in input.spec.containers}
limits := {c.name: c.resources.limits.memory | some c in input.spec.containers}
registry(image) := split(image, "/")[0]
untrusted contains image if {
some image in images
not registry(image) in {"ghcr.io", "registry.example.com"}
}
同じPodで評価すると、limits は {"app":"256Mi","sidecar":"64Mi"}、untrusted は ["docker.io/library/nginx:1.29"] になりました。split・sprintf・startswith・regex.match・time.now_ns などの組み込み関数は1.22.0で206個あり、一覧は公式のBuilt-in Functionsにまとまっています。JWTの検証(io.jwt.decode_verify)や外部HTTP呼び出し(http.send)も組み込みで持っているため、認可ポリシーの中でトークンを検証できます。
Regoによる認可ポリシーの実装(RBACとABAC)
OPAを最初に入れる場面で多いのが、アプリケーションのロールベース認可です。ロールと権限の対応は data 側のJSONに置き、ポリシーは data.rbac.user_roles のように修飾して参照します。ロールが増えるたびにポリシーを再デプロイせずに済み、権限表をIdPや人事システムの出力から生成できます。
package rbac
default allow := false
allow if "admin" in data.rbac.user_roles[input.user]
allow if {
some role in data.rbac.user_roles[input.user]
some perm in data.rbac.role_permissions[role]
perm.action == input.action
perm.resource == input.resource
}
allowed_resources contains perm.resource if {
some role in data.rbac.user_roles[input.user]
some perm in data.rbac.role_permissions[role]
}
reasons contains $"user {input.user} has no role" if {
not data.rbac.user_roles[input.user]
}
ここまでのRBACポリシーを rbac/policy.rego、次の参照データを rbac/data.json に保存します。後述のテストは rbac/policy_test.rego に保存してください。
{"rbac": {
"user_roles": {"alice": ["admin"], "bob": ["developer"]},
"role_permissions": {
"admin": [{"action": "write", "resource": "server"}],
"developer": [{"action": "read", "resource": "server"}, {"action": "read", "resource": "logs"}]
}
}}
allow は真偽値、allowed_resources は集合を返します。bobで評価すると allowed_resources は ["logs","server"] で、画面に「触れるリソースの一覧」を出す処理と判定を同じポリシーから引けます。reasons の $"..." は後述の文字列補間で、ロールを持たないcarolを渡すと ["user carol has no role"] が返りました。ロール設計そのものはRBACとは?ロールベースアクセス制御の仕組みと導入判断を実装視点で解説で扱っています。
「営業時間内のみ」「社内IPからのみ」といった属性ベース(ABAC)の条件は、input に時刻やIPを含めて渡し、allow の本体に1行ずつ足します。判定に必要な属性がすでにOPAへ渡されていれば、アプリケーション側のコードを変えずに条件を追加できます。新しい属性が必要なら、入力を組み立てる処理も変更します。属性の設計はABACとは?属性ベースアクセス制御の仕組みとRBACとの使い分けを実装視点で解説を参照してください。AWS上のアプリで認可を外出ししたい場合は、Regoではなく専用言語Cedarを使うAmazon Verified Permissionsとは?Cedarの書き方と料金・API Gateway連携の手順も候補になります。認証基盤ごと設計したい場合は認証基盤・ID管理システム開発のご相談も受け付けています。
OPAでのポリシー評価(opa eval・REST API)
opa evalによるCLIでのポリシー評価
printf '%s\n' '{"user":"bob","action":"read","resource":"server"}' > input.json
opa eval -d rbac/ -i input.json "data.rbac.allow" -f raw
-d にポリシーとデータのディレクトリ、-i に入力JSONを渡します。条件が成立しなかった理由を追うなら --explain fails、CIで結果がundefinedのとき落としたいなら --fail を付けます。ブラウザだけで試すなら公式のRego Playgroundが使え、opa run を引数なしで起動すると対話式のREPLになります。
opa runによるサーバ起動とREST APIの呼び出し
opa build -b rbac/ -o bundle.tar.gz
opa run --server --addr 0.0.0.0:8181 -b bundle.tar.gz
curl -X POST localhost:8181/v1/data/rbac/allow \
-H 'Content-Type: application/json' \
-d '{"input": {"user": "bob", "action": "read", "resource": "server"}}'
OPA 1.0以降、サーバの待受の既定は localhost:8181 です(1.22.0の opa run --help でも default [localhost:8181])。コンテナで動かしてPodの外から呼ぶ構成では --addr 0.0.0.0:8181 を明示しないと接続できません。判定は POST /v1/data/<パッケージ>/<ルール> で、応答は {"result":true} の形です。存在しないパスを問い合わせても応答は200で result キーが無いだけなので、呼び出し側は「result がtrueのときだけ許可」と実装します。
ポリシーのテストと静的検査
test_ で始まるルールが opa test の対象です。with input as で入力を差し替えます。
package rbac_test
import data.rbac
test_admin_allowed if {
rbac.allow with input as {"user": "alice", "action": "write", "resource": "server"}
}
test_developer_write_denied if {
not rbac.allow with input as {"user": "bob", "action": "write", "resource": "server"}
}
test_unknown_user_reason if {
"user carol has no role" in rbac.reasons with input as {"user": "carol"}
}
opa test rbac/ -v
opa test rbac/ --coverage --threshold 80
上のRBACポリシーとテストは PASS: 3/3 でした。拒否されるべきケースを not で書くのが要点で、許可ケースだけでは「何でも許可するルール」を見逃します。カバレッジは同じ3本で81.25%で、--threshold 100 を付けると終了コード2で落ちました。基準値は、現在のカバレッジと追加すべきテストを踏まえて決め、CIでは基準を下回る変更を検出します。
スタイルとアンチパターンの検査はRegalが担います。RegalはOPA公式のGitHub organizationで開発され、最新はv0.43.0(2026年9月29日)、READMEでは今もbeta扱いです。opa コマンドには lint サブコマンドが無いため、regal lint として別に入れます。
OPA 1.x で増えた構文(文字列補間・and/or・rego.v2)
OPA 1.0以降も、Regoには構文の追加が続いています。ここでは、OPA 1.0以降に追加された構文と、それぞれの対応バージョンを整理します。
| 追加された構文 | OPAの版 | 公開日(UTC) | 有効にする方法 |
|---|---|---|---|
文字列補間 $"..." |
1.12.0 | 2025-12-18 | import不要 |
改良版の not |
1.17.0 | 2026-05-28 | import future.keywords.not |
and・or |
1.20.0 | 2026-08-27 | import future.keywords.and / or |
| and・or・改良版notの一括有効化 | 1.22.0 | 2026-10-29 | import rego.v2 |
文字列補間によるメッセージ生成と未定義値の扱い
$"user {input.user} has no role" のように、$ を付けた文字列の中で { } に式を書けます。sprintf と違って引数の並びを数えずに済みます。公式ドキュメントのString Interpolationによると、式の値がundefinedでも評価は止まらず、そこに <undefined> という文字列が入ります。違反メッセージが欠けたまま返る可能性があるので、利用者に見せる文言では欠けうる値に default を用意しておきます。
and・orとimport rego.v2
1.20.0より前は「AかつB、またはC」を、同名の複数ルールや補助ルールなどで表現していました。and と or を使うと1行で書けます。結合は and が先です。
package example
import rego.v2
default allow := false
allow if input.user.admin == true or (input.user.active == true and not input.user.locked)
リリースノートに載っている例は allow if input.user.admin or not (input.user.suspended or input.user.locked) ですが、これに空の入力 {} を渡すと1.22.0では true が返りました。suspended も locked も存在しないため括弧の中が成立せず、not で反転して許可になるためです。認可に使うなら、上のように default allow := false を置き、許可の条件を値の明示比較で書きます。この形なら {} は false、active がtrueで locked もtrueの利用者も false でした。
import rego.v2 は、future.keywords.and・future.keywords.or・future.keywords.not の3行をまとめて置き換えるimportです(v1.22.0のリリースノート)。バンドル単位なら .manifest の rego_version を 2 にすると、ファイルごとのimportが要らなくなります。1.22.0と1.21.4で試した結果は次のとおりです。
- importを書かずに
allow if a or bと書くと、1.22.0でもrego_parse_error: var cannot be used for rule nameになる。orが変数名として読まれるため、エラー文から原因を推測しにくい。 - 同じファイルを1.21.4で読むと
rego_parse_error: invalid import `rego.v2`, must be `rego.v1`で止まる。 future.keywords.and/orの2行で書いたファイルは、1.21.4でも1.22.0でも通る。
判断ははっきりしています。本番のOPAとCIのOPA、Gatekeeperなど組み込み先のOPAがすべて1.22.0以上に揃うまでは import rego.v2 を使わない。個別importを使う場合も、and・or はOPA 1.20.0以上、改良版の not は1.17.0以上が必要です。それより古い版には、対応する構文で書き直します。公式は not を使うポリシーで future.keywords.not(または rego.v2)を入れることを推奨しています。否定の中に関数呼び出しを重ねたとき、中間の値がundefinedだとルール全体が失敗してしまう長年の挙動を直すものです。
v0コードをRego v1へ移行する手順
社内Wikiや古い記事からコピーしたRegoを1.22.0で読んで次のエラーが出たら、原因は if と contains の欠落です。
rego_parse_error: `if` keyword is required before rule body
rego_parse_error: `contains` keyword is required for partial set rules
| 種類 | v0の書き方 | v1の書き方 |
|---|---|---|
| 条件付きルール | allow { ... } |
allow if { ... } |
| 集合ルール | deny[msg] { ... } |
deny contains msg if { ... } |
| 既定値(推奨書式) | default allow = false |
default allow := false |
| キーワードのimport | import future.keywords.in |
不要 |
| 移行期のimport | import rego.v1 |
不要(書いても何もしない) |
opa fmt --v0-v1 -w policy.rego
opa fmt --drop-v0-imports -w policy.rego
表のうち構文エラーになるのは最初の2行(if と contains)だけで、default allow = false と import future.keywords.in は1.22.0でも opa check を通ります。1行目で if・contains・:= が補われ、import future.keywords.in は import rego.v1 に置き換わりました。2行目でその import rego.v1 を消すと、1.x専用のきれいなv1コードになります。すぐに直せない資産は、eval・test・run・check などに付けられる --v0-compatible でv0のまま評価できます。ただしこのフラグはサーバの待受も全インターフェース(:8181)に戻すため、延命のつもりで付けると公開範囲まで変わります。バンドルの場合は .manifest の rego_version で言語の版を宣言できます。指定がなければ通常はv1、--v0-compatible 指定時はv0として扱われます。
Kubernetes向けポリシーの設定と製品選択(Gatekeeper・VAP・Kyverno)
GatekeeperのRego v1設定とversion: “v1″の指定
Kubernetes向けのOPA GatekeeperはOPA本体とは別のプロジェクトで、最新はv3.23.1(2026年8月27日)、同梱しているOPAはv1.17.1です。GatekeeperのConstraintTemplateのドキュメントは、3.19からRego v1を使えるようになったこと、ただし既定ではRego v0しか許可されないことを明記しています。v1で書くには、旧来の rego: 欄ではなく code の下で version: "v1" を指定します。この場合 import rego.v1 は不要です。
targets:
- target: admission.k8s.gatekeeper.sh
code:
- engine: Rego
source:
version: "v1"
rego: |
package k8srequiredlabels
violation contains {"msg": msg} if {
provided := {label | some label, _ in input.review.object.metadata.labels}
required := {label | some label in input.parameters.labels}
missing := required - provided
count(missing) > 0
msg := sprintf("missing required labels: %v", [missing])
}
このRego部分を単体のファイルにしてOPA 1.22.0で評価すると、owner ラベルが欠けた入力に対して [{"msg":"missing required labels: {\"owner\"}"}] を返しました。version を書かずに if 付きのコードを貼るとv0として読まれて失敗し、逆にv0の violation[{"msg": msg}] { ... } を version: "v1" の下に置いても失敗します。Gatekeeper同梱のOPAは1.17.1なので、import rego.v2 と and・or はまだ使えません。
Kubernetes専用ならRegoを選ばない判断
用途がKubernetesのAdmission Controlだけなら、Regoを覚えずに済む道が2つあります。Kubernetes標準のValidatingAdmissionPolicy(CEL)はv1.30でGAし、追加コンポーネントなしで単純なフィールド検証を書けます。KyvernoはポリシーをYAMLのKubernetesリソースとして書くツールで、2026年3月にCNCFを卒業しています。Regoを選ぶ理由があるのは、Kubernetesの外(CI、API認可、Terraformの検査)にも同じポリシー言語を使いたいときと、Kubernetes用と同じRego資産をほかの用途でも共有したいときです。どちらにも当たらない組織が、学習コストを払ってRegoを入れる必要はありません。Kubernetesの基礎はKubernetes(クバネティス)とは?仕組み・メリット・デメリットとDockerとの違いをわかりやすく解説を参照してください。
本番運用のバンドル署名・decision logs・計測
本番ではポリシーとデータを .tar.gz のバンドルにまとめ、OPAに定期取得させます。改ざん対策の署名は opa build --signing-key で付け、署名は .signatures.json に入ります。1.22.0では署名ファイルの扱いが厳しくなりました。以前は名前が signatures.json で終わるファイルならどれでも署名として読み、しかもそれらを署名対象から外していました。1.22.0からは .signatures.json という名前だけを署名と見なし、foosignatures.json のようなファイルを含む古い署名のバンドルは検証に通らなくなりました。該当する名前のファイルを含むバンドルを配っている場合は、OPAを上げる前に1.22.0以降の opa build で署名し直します。
監査が必要ならdecision logsを有効にします。判定ごとに decision_id、input、結果、時刻が記録され、個人情報は、既定の data.system.log.mask に定義するRegoルールで削除・置換できます。別のルールを使う場合は decision_logs.mask_decision にそのパスを指定します。評価時間は、リクエストに ?metrics=true を付けて応答の timer_rego_query_eval_ns を読むか、開発時に opa eval --profile で式ごとの所要時間を見ます。opa_eval_duration_seconds というメトリクス名を載せた解説を見かけますが、OPAにそのメトリクスはありません。
よくある質問
Regoの読み方と意味は何ですか?
OPAの公式ドキュメントは「ray-go」と発音すると書いています(日本語では「レゴ」と読まれることも多くあります)。Datalogに着想を得て、JSONのような構造化データを扱えるよう拡張した宣言的なポリシー言語で、ファイルの拡張子は .rego です。
ネットのサンプルコードがopa evalでエラーになるのはなぜですか?
rego_parse_error: `if` keyword is required before rule body が出るなら、そのコードはOPA 1.0より前のv0構文です。opa fmt --v0-v1 -w ファイル名 で変換するか、暫定的に --v0-compatible を付けて評価してください。invalid import `rego.v2` が出る場合は逆で、コードが新しくOPAが1.21系以前です。
import rego.v1とimport rego.v2は書くべきですか?
import rego.v1 はOPA 1.xでは不要で、書いても何もしません。import rego.v2 は1.22.0以上で and・or・改良版の not を有効にするimportです。実行するOPAがすべて1.22.0以上なら使えますが、1.22.0未満では個別importを使いますが、and・or は1.20.0以上、改良版の not は1.17.0以上であることが条件です。
OPAとRegoの最新バージョンはいくつですか?
2026年10月30日時点の最新はOPA v1.22.0(2026年10月29日公開)です。opa version を実行すると Rego Version: v1 と表示されます。2026年10月だけで4回リリースが出ているので、正確な最新版はGitHubのリリース一覧で確認してください。
RegoとKyvernoはどちらを選ぶべきですか?
Kubernetes内の単純なフィールド検証が中心なら、まず標準のValidatingAdmissionPolicyを検討します。より広いポリシー機能が必要なら、KyvernoとGatekeeperを要件に沿って比較します。CI、API認可、Terraformの検査にも同じ言語を使いたい場合や、Kubernetes内でも既存のRego資産を共有したい場合はRego(OPA)が候補です。外部データ参照だけが要件なら、Kyvernoでも対応できます。