OPA
Open Policy Agent (OPA) 完全ガイド — アーキテクチャ・機能・設定の全容
1. はじめに
1.1 OPA とは何か
Open Policy Agent (OPA) は、Cloud Native Computing Foundation (CNCF) の卒業プロジェクトとして管理されている、汎用的なポリシーエンジンである。Styra 社が 2016 年に開発を開始し、2021 年に CNCF Graduated ステータスを取得した。OPA は「ポリシーをコードとして管理する (Policy as Code)」というパラダイムを実現するためのオープンソースツールであり、ソフトウェアスタック全体にわたって統一的なポリシーの記述・適用・テストを可能にする。
OPA の核となるアイデアは極めてシンプルである。ポリシーの判断 (decision) をサービスのビジネスロジックから切り離す ということだ。従来、認可ロジックや入力バリデーションはアプリケーションコード内にハードコーディングされることが多かった。しかし、マイクロサービスの普及やクラウドネイティブ環境の複雑化に伴い、各サービスに散在するポリシーロジックを一元管理し、監査可能な形で運用する必要性が増している。
OPA はその課題を解決するために設計されている。Go 言語で実装された軽量なバイナリとして提供され、以下の特徴を持つ。
- 汎用ポリシーエンジン: 特定のドメイン(例: Kubernetes のみ)に縛られず、API 認可、データフィルタリング、Terraform のプランバリデーション、CI/CD パイプラインなど、あらゆるシステムにポリシーを適用できる
- Rego 言語: OPA 専用の宣言型ポリシー言語。Datalog をベースにしており、構造化データに対する問い合わせとポリシー判断を簡潔に記述できる
- デカップリングアーキテクチャ: ポリシーの判断を行う OPA エンジンと、判断を施行 (enforce) するシステムを明確に分離する。これにより、ポリシーの変更がアプリケーションの再デプロイなしに反映される
- 軽量・高速: インメモリで動作し、マイクロ秒〜ミリ秒オーダーでポリシー評価を返す。サイドカーとしてのデプロイに適している
1.2 OPA が解決する課題
現代のクラウドネイティブアーキテクチャでは、以下のような課題が頻繁に発生する。
| 課題 | 詳細 |
|---|---|
| ポリシーの散在 | 各マイクロサービスに認可ロジックが分散し、一貫性の確保が困難 |
| 変更の遅延 | ポリシー変更のたびにアプリケーションの再デプロイが必要 |
| 監査の困難さ | ポリシーがコードに埋め込まれているため、何がどのように判断されているかを把握しにくい |
| テスト不足 | ポリシーロジックのユニットテストが困難で、回帰テストが行われていない |
| 言語・フレームワーク依存 | Java、Python、Go など言語ごとに異なる認可ライブラリを使用し、統一が困難 |
OPA はこれらすべてに対して統一的な解を提供する。
1.3 本記事の構成
本記事は以下の構成で OPA の全体像を網羅する。
- はじめに (本章)
- コアコンセプト — ポリシー、データ、クエリの三位一体
- Rego 言語の詳細 — 文法、パターン、実践的なポリシー記述
- アーキテクチャと動作原理 — 内部構造と評価エンジン
- デプロイメントパターン — サイドカー、デーモン、Go ライブラリ、WASM
- Kubernetes との統合 (OPA Gatekeeper) — Admission Control の実践
- マイクロサービス API 認可 — HTTP API とサービスメッシュ統合
- Envoy / Istio との統合 — External Authorization フィルタ
- Terraform との統合 — インフラストラクチャポリシー
- ポリシーテスト — ユニットテスト、カバレッジ、CI/CD 統合
- バンドルとポリシー配布 — 大規模運用のためのポリシー管理
- パフォーマンスチューニング — ベンチマーク、プロファイリング、最適化
- セキュリティ考慮事項 — OPA 自体のセキュリティ設定
- エコシステムとツール — Conftest, Gatekeeper, Styra DAS など
- まとめとベストプラクティス
2. コアコンセプト
OPA を理解するうえで最も重要な概念は、ポリシー (Policy)、データ (Data)、クエリ (Query) の三つである。これらの関係を正確に理解することが、OPA を効果的に活用するための基盤となる。
2.1 三位一体モデル
OPA の動作は以下のシンプルなモデルで表現できる。
┌──────────────┐
│ Query │
│ (Input) │
└──────┬───────┘
│
▼
┌────────────────────────┐
│ │
│ OPA Engine │
│ │
│ ┌──────────────────┐ │
│ │ Policy (Rego) │ │
│ └──────────────────┘ │
│ ┌──────────────────┐ │
│ │ Data (JSON) │ │
│ └──────────────────┘ │
│ │
└────────────┬───────────┘
│
▼
┌──────────────┐
│ Decision │
│ (Output) │
└──────────────┘
- ポリシー (Policy): Rego 言語で記述されたルールの集合。「何が許可されるか」「何が禁止されるか」を宣言的に定義する
- データ (Data): ポリシー評価に必要な外部情報。JSON 形式で OPA に事前にロードされる。例: ユーザーのロール情報、リソースの所有者情報、組織のコンプライアンス要件など
- クエリ (Query / Input): ポリシー評価のトリガーとなる入力データ。API リクエストの内容、Kubernetes の Admission Review オブジェクト、Terraform のプランデータなどが該当する
OPA はポリシーとデータをもとにクエリを評価し、判断 (Decision) を返す。判断は JSON 形式で返され、単純な true/false から複雑な構造化データまで対応する。
2.2 Document モデル
OPA は内部的にすべてのデータを ドキュメント (Document) として管理する。ドキュメントは以下の 3 種類に分類される。
2.2.1 Base Document
外部から OPA にロードされるデータ。data ルートの下に格納される。
{
"data": {
"users": {
"alice": {"role": "admin", "department": "engineering"},
"bob": {"role": "viewer", "department": "marketing"}
},
"roles": {
"admin": {"permissions": ["read", "write", "delete"]},
"viewer": {"permissions": ["read"]}
}
}
}
2.2.2 Virtual Document
Rego ルールによって生成される計算済みのドキュメント。ルールの評価結果として動的に生成される。
package authz
# Virtual Document: data.authz.allow
allow if {
some role in input.user.roles
some permission in data.roles[role].permissions
permission == input.action
}
この例では data.authz.allow が Virtual Document であり、入力と Base Document をもとに true または false を返す。
2.2.3 Input Document
クエリとともに提供されるデータ。input キーワードでアクセスする。
{
"input": {
"user": {
"name": "alice",
"roles": ["admin"]
},
"action": "write",
"resource": "/api/v1/users"
}
}
2.3 ポリシーのライフサイクル
OPA におけるポリシーのライフサイクルは以下のとおりである。
記述 (Author)
│
▼
テスト (Test)
│
▼
レビュー (Review)
│
▼
公開 (Publish)
│
▼
配布 (Distribute)
│
▼
適用 (Enforce)
│
▼
監視 (Monitor)
│
▼
改善 (Improve)
│
└──→ 記述 に戻る
- 記述: Rego でポリシーを記述する
- テスト:
opa testコマンドでユニットテストを実行する - レビュー: Git のプルリクエストを通じてコードレビューを行う
- 公開: バンドルとしてパッケージングし、OCI レジストリやオブジェクトストレージに公開する
- 配布: OPA のバンドル API を通じて各エージェントにポリシーを配布する
- 適用: OPA がポリシーを評価し、判断を返す
- 監視: Decision Log を通じて判断結果を監視・監査する
- 改善: 監視結果をもとにポリシーを改善する
2.4 ポリシーの分離原則
OPA を効果的に運用するための重要な設計原則として、以下の分離がある。
| 分離 | 説明 |
|---|---|
| 判断と施行の分離 | OPA はポリシーの判断のみを行い、施行は呼び出し元のシステムが行う |
| ポリシーとデータの分離 | ポリシーのルール (Rego) と参照データ (JSON) は別々に管理する |
| ポリシーとアプリケーションの分離 | ポリシーのロジックはアプリケーションコードから完全に独立する |
この分離により、各コンポーネントが独立してバージョン管理・テスト・デプロイできるようになる。
3. Rego 言語の詳細
3.1 Rego の概要
Rego は OPA 専用の宣言型ポリシー言語であり、名前は「ルールのための言語 (re-go: rules for Go)」に由来する。Datalog をベースに設計されており、JSON/YAML などの構造化データに対して直感的に問い合わせやルール定義を行える。
Rego の主な特徴:
- 宣言的: 「何が真であるか」を宣言する。手続き的な制御フローは使用しない
- JSON ネイティブ: 入力・出力ともに JSON 形式。JSON データの操作に最適化されている
- 完全評価: ルールは完全に評価され、結果がキャッシュされる
- 部分評価: 入力が不完全な場合でも、既知の情報に基づいて部分的に評価できる
3.2 基本文法
3.2.1 パッケージとインポート
# パッケージ宣言: すべての Rego ファイルはパッケージに属する
package httpapi.authz
# インポート: 他のパッケージやデータを参照する
import data.users
import data.roles
import input.request
パッケージはドット区切りの階層構造を持ち、ファイルシステムのパスに対応する。
3.2.2 ルールの定義
Rego のルールは以下の形式で記述する。
# 定数ルール (Complete Rule)
default allow := false
# 条件付きルール
allow if {
input.method == "GET"
input.path == "/public"
}
# 複数の条件を持つルール (AND 条件: すべての行が真である必要がある)
allow if {
input.user.role == "admin"
input.method == "DELETE"
startswith(input.path, "/api/")
}
# 複数のルール定義による OR 条件
allow if {
input.user.role == "editor"
input.method == "PUT"
}
重要なポイント:
- 同一ルール内の複数の式は AND 条件として評価される
- 同一名の複数ルールは OR 条件として評価される
defaultキーワードは、すべてのルールがundefinedの場合のデフォルト値を定義する
3.2.3 変数とイテレーション
# some キーワードによる変数宣言とイテレーション
allow if {
some i
input.user.roles[i] == "admin"
}
# より簡潔な形式 (v0.34+)
allow if {
some role in input.user.roles
role == "admin"
}
# every キーワード (v0.44+): すべての要素が条件を満たすことを要求
all_users_active if {
every user in data.users {
user.status == "active"
}
}
3.2.4 内包表記 (Comprehension)
# Set 内包表記
admin_users := {user |
some user in data.users
user.role == "admin"
}
# Array 内包表記
sorted_names := [name |
some user in data.users
name := user.name
]
# Object 内包表記
user_roles := {name: role |
some name, user in data.users
role := user.role
}
3.3 データ型
Rego は以下のデータ型をサポートする。
| 型 | 例 | 説明 |
|---|---|---|
| Boolean | true, false | 真偽値 |
| Number | 42, 3.14 | 整数・浮動小数点数 |
| String | "hello" | 文字列 (UTF-8) |
| Null | null | null 値 |
| Array | [1, 2, 3] | 順序付きリスト |
| Object | {"key": "value"} | キーバリューマップ |
| Set | {1, 2, 3} | 一意な値の集合 |
3.4 組み込み関数
Rego は豊富な組み込み関数を提供する。以下は主要なカテゴリと代表的な関数である。
文字列操作
# 文字列の結合
result := concat(", ", ["a", "b", "c"]) # "a, b, c"
# 文字列の検索
contains("hello world", "world") # true
startswith("/api/v1/users", "/api/") # true
# 正規表現
regex.match("^[a-z]+$", "hello") # true
# フォーマット
result := sprintf("user: %s, role: %s", [input.user, input.role])
数値操作
max_value := max({1, 2, 3}) # 3
abs_value := abs(-5) # 5
round_value := round(3.7) # 4
集合操作
# 和集合
union_set := {1, 2, 3} | {3, 4, 5} # {1, 2, 3, 4, 5}
# 積集合
intersect_set := {1, 2, 3} & {2, 3, 4} # {2, 3}
# 差集合
diff_set := {1, 2, 3} - {2} # {1, 3}
オブジェクト操作
# キーの取得
keys := object.keys({"a": 1, "b": 2}) # {"a", "b"}
# オブジェクトのマージ
merged := object.union({"a": 1}, {"b": 2}) # {"a": 1, "b": 2}
# パスによるアクセス
value := object.get(input, ["user", "name"], "unknown")
JWT / 暗号操作
# JWT の検証とデコード
[header, payload, sig] := io.jwt.decode(input.token)
# 署名付き JWT の検証
io.jwt.verify_rs256(input.token, data.keys.public_key)
# JWT のデコードと検証を同時に行う
[valid, header, payload] := io.jwt.decode_verify(input.token, {
"cert": data.keys.public_key,
"iss": "https://auth.example.com",
"aud": "my-service"
})
HTTP / ネットワーク
# 外部 API の呼び出し (注意: パフォーマンスに影響する可能性あり)
response := http.send({
"method": "GET",
"url": "https://api.example.com/users",
"headers": {"Authorization": concat(" ", ["Bearer", input.token])},
"raise_error": false
})
# CIDR マッチング
net.cidr_contains("10.0.0.0/8", "10.1.2.3") # true
3.5 実践的なポリシー例
3.5.1 RBAC (ロールベースアクセス制御)
package rbac
import data.roles
import data.user_roles
default allow := false
# ユーザーのロールに紐づく権限を取得
user_permissions[permission] {
some role in user_roles[input.user]
some permission in roles[role].permissions
}
# 要求されたアクションがユーザーの権限に含まれるか確認
allow if {
required_permission := concat(":", [input.action, input.resource])
required_permission in user_permissions
}
# 管理者は常にアクセス可能
allow if {
some role in user_roles[input.user]
role == "super_admin"
}
対応するデータ:
{
"roles": {
"admin": {
"permissions": ["read:users", "write:users", "delete:users", "read:reports"]
},
"editor": {
"permissions": ["read:users", "write:users", "read:reports"]
},
"viewer": {
"permissions": ["read:users", "read:reports"]
}
},
"user_roles": {
"alice": ["admin"],
"bob": ["editor", "viewer"],
"charlie": ["viewer"]
}
}
3.5.2 ABAC (属性ベースアクセス制御)
package abac
default allow := false
# 勤務時間内のアクセスのみ許可
allow if {
input.user.department == input.resource.department
input.action == "read"
is_business_hours
}
# 機密リソースはマネージャー以上のみ
allow if {
input.resource.classification == "confidential"
input.user.level >= 5
input.user.department == input.resource.department
}
is_business_hours if {
now := time.now_ns()
[hour, _, _] := time.clock([now, "Asia/Tokyo"])
hour >= 9
hour < 18
}
3.5.3 API リクエストのバリデーション
package api.validation
# バリデーションエラーの収集
violations[msg] {
input.request.method == "POST"
input.request.path == "/api/v1/users"
not input.request.body.email
msg := "email フィールドは必須です"
}
violations[msg] {
input.request.method == "POST"
input.request.path == "/api/v1/users"
input.request.body.email
not regex.match(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`, input.request.body.email)
msg := "無効なメールアドレス形式です"
}
violations[msg] {
input.request.method == "POST"
input.request.path == "/api/v1/users"
input.request.body.age
input.request.body.age < 0
msg := "年齢は 0 以上である必要があります"
}
# バリデーション結果
result := {
"allow": count(violations) == 0,
"violations": violations
}
4. アーキテクチャと動作原理
4.1 OPA の内部アーキテクチャ
OPA は以下の主要コンポーネントから構成される。
┌─────────────────────────────────────────────────────┐
│ OPA Server │
│ │
│ ┌─────────┐ ┌─────────────┐ ┌────────────────┐ │
│ │ REST │ │ Bundle │ │ Decision │ │
│ │ API │ │ Loader │ │ Logger │ │
│ └────┬────┘ └──────┬──────┘ └───────┬────────┘ │
│ │ │ │ │
│ ┌────▼──────────────▼─────────────────▼────────┐ │
│ │ Policy Store │ │
│ │ ┌──────────────┐ ┌─────────────────────┐ │ │
│ │ │ Rego Modules │ │ JSON Data Store │ │ │
│ │ └──────────────┘ └─────────────────────┘ │ │
│ └──────────────────────┬───────────────────────┘ │
│ │ │
│ ┌──────────────────────▼───────────────────────┐ │
│ │ Evaluation Engine │ │
│ │ ┌──────────┐ ┌────────────┐ ┌───────────┐ │ │
│ │ │ Parser │ │ Compiler │ │ Topdown │ │ │
│ │ │ │ │ │ │ Evaluator│ │ │
│ │ └──────────┘ └────────────┘ └───────────┘ │ │
│ │ ┌──────────┐ ┌────────────┐ │ │
│ │ │ Planner │ │ WASM │ │ │
│ │ │ │ │ Compiler │ │ │
│ │ └──────────┘ └────────────┘ │ │
│ └──────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────┐ │
│ │ Status / Health / Metrics │ │
│ └──────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────┘
4.1.1 REST API
OPA はデフォルトで HTTP サーバーとして動作し、以下のエンドポイントを公開する。
| エンドポイント | メソッド | 説明 |
|---|---|---|
/v1/data/{path} | GET/POST | ポリシーの評価 (判断の取得) |
/v1/policies/{id} | GET/PUT/DELETE | ポリシーの管理 (CRUD) |
/v1/data/{path} | PUT/PATCH | データの管理 |
/v1/query | POST | アドホッククエリの実行 |
/v1/compile | POST | 部分評価 (Partial Evaluation) |
/health | GET | ヘルスチェック |
/metrics | GET | Prometheus メトリクス |
4.1.2 評価エンジン
OPA の評価エンジンは以下のステップでポリシーを処理する。
- パース (Parse): Rego ソースコードを AST (Abstract Syntax Tree) に変換
- コンパイル (Compile): AST を最適化し、型チェックを実行
- 評価 (Evaluate): トップダウン方式でルールを評価
トップダウン評価は再帰的に動作し、以下の特性を持つ。
- メモ化 (Memoization): 一度評価されたルールの結果はキャッシュされ、同一クエリ内で再利用される
- バックトラッキング: 条件が失敗した場合、前の選択肢に戻って代替パスを試行する
- コンプリート評価: 結果が
undefinedになるルールはデフォルト値にフォールバックする
4.1.3 Bundle Loader
Bundle Loader はリモートの HTTP サーバーから定期的にポリシーバンドルをダウンロードする。バンドルは .tar.gz 形式でパッケージングされ、以下を含む。
- Rego ファイル (
.rego) - JSON データファイル (
data.json) - マニフェストファイル (
.manifest) - オプションの署名ファイル (
.signatures.json)
4.1.4 Decision Logger
Decision Logger は OPA の判断結果をログとして外部システムに送信する。これにより、すべてのポリシー判断の監査証跡が記録される。
4.2 データフロー
OPA を中心としたデータフローを時系列で示す。
1. アプリケーションが OPA にクエリを送信
POST /v1/data/authz/allow
{"input": {"user": "alice", "action": "read", "resource": "/api/users"}}
2. OPA が入力データを受け取り、ポリシーを評価
- input.user → "alice"
- data.user_roles["alice"] → ["admin"]
- data.roles["admin"].permissions → ["read", "write", "delete"]
- "read" in permissions → true
- allow → true
3. OPA が判断結果を返す
{"result": true}
4. Decision Logger が判断をログに記録
{
"decision_id": "abc-123",
"input": {...},
"result": true,
"timestamp": "2025-01-15T10:30:00Z",
"metrics": {"timer_rego_query_eval_ns": 125000}
}
5. アプリケーションが判断に基づいてリクエストを許可/拒否
4.3 OPA の設定
OPA サーバーの設定は YAML ファイルで行う。以下は典型的な設定例である。
# opa-config.yaml
# サービス定義: バンドルやステータスの送信先
services:
- name: bundle-server
url: https://bundle.example.com
credentials:
bearer:
token: "${BUNDLE_TOKEN}"
response_header_timeout_seconds: 5
- name: logging-server
url: https://logs.example.com
credentials:
bearer:
token: "${LOG_TOKEN}"
# バンドル設定
bundles:
authz:
service: bundle-server
resource: /bundles/authz/bundle.tar.gz
persist: true
polling:
min_delay_seconds: 30
max_delay_seconds: 120
signing:
keyid: global_key
scope: write
# Decision Log 設定
decision_logs:
service: logging-server
reporting:
min_delay_seconds: 5
max_delay_seconds: 30
partition_name: /logs
# 機密情報のマスキング
mask_decision: /system/log/mask
# ステータス報告
status:
service: bundle-server
partition_name: /status
# キャッシュ設定
caching:
inter_query_builtin_cache:
max_size_bytes: 268435456 # 256MB
# サーバー設定
labels:
app: my-service
region: ap-northeast-1
environment: production
# デフォルトの判断ルール
default_decision: /authz/allow
default_authorization_policy: /system/authz/allow
4.4 OPA の起動
# 基本的な起動 (サーバーモード)
opa run --server
# 設定ファイルを指定して起動
opa run --server --config-file opa-config.yaml
# ポリシーとデータファイルを指定して起動
opa run --server \
--addr :8181 \
--log-level info \
--log-format json \
policy.rego \
data.json
# REPL モード (対話型)
opa run
> data.example.allow
true
# ワンショット評価
opa eval --data policy.rego --input input.json "data.authz.allow"
5. デプロイメントパターン
OPA はその軽量な設計により、さまざまなデプロイメントパターンに対応する。ユースケースや非機能要件に応じて最適なパターンを選択できる。
5.1 サイドカーパターン
最も一般的なパターン。各サービスの Pod 内にサイドカーとして OPA をデプロイする。
┌──────────────────────────────────────┐
│ Pod │
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Application │ │ OPA │ │
│ │ Container │──▶│ Container │ │
│ │ │◀──│ │ │
│ │ Port: 8080 │ │ Port: 8181 │ │
│ └──────────────┘ └──────────────┘ │
│ │
└──────────────────────────────────────┘
メリット:
- レイテンシが最小 (localhost 通信)
- 障害の影響範囲が限定的 (1 つの OPA がダウンしても他のサービスに影響しない)
- サービスごとに異なるポリシーをロード可能
デメリット:
- リソース消費が増加 (各 Pod に OPA のメモリ・CPU が必要)
- ポリシーの配布先が多くなる
- 運用管理の対象が増える
Kubernetes マニフェスト例:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-service
spec:
replicas: 3
selector:
matchLabels:
app: my-service
template:
metadata:
labels:
app: my-service
spec:
containers:
- name: app
image: my-service:latest
ports:
- containerPort: 8080
env:
- name: OPA_URL
value: "http://localhost:8181"
- name: opa
image: openpolicyagent/opa:latest
args:
- "run"
- "--server"
- "--config-file=/config/opa-config.yaml"
- "--log-level=info"
- "--log-format=json"
ports:
- containerPort: 8181
volumeMounts:
- name: opa-config
mountPath: /config
resources:
requests:
memory: "64Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /health
port: 8181
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /health?bundles
port: 8181
initialDelaySeconds: 5
periodSeconds: 10
volumes:
- name: opa-config
configMap:
name: opa-config
5.2 デーモンパターン
各ノードに DaemonSet として OPA をデプロイし、同一ノード上の複数サービスで共有する。
┌─────────────────────────────────────────────────────┐
│ Node │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Pod A │ │ Pod B │ │ Pod C │ │
│ │ │ │ │ │ │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │
│ └──────────────┼──────────────┘ │
│ │ │
│ ┌───────▼───────┐ │
│ │ OPA Daemon │ │
│ │ (DaemonSet) │ │
│ │ Port: 8181 │ │
│ └───────────────┘ │
│ │
└─────────────────────────────────────────────────────┘
メリット:
- ノードあたり 1 つの OPA で済むため、リソース効率が良い
- ポリシーの配布先が少ない
デメリット:
- OPA のダウンがノード上の全サービスに影響
- ネットワークレイテンシがサイドカーより若干大きい
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: opa
spec:
selector:
matchLabels:
app: opa
template:
metadata:
labels:
app: opa
spec:
containers:
- name: opa
image: openpolicyagent/opa:latest
args:
- "run"
- "--server"
- "--config-file=/config/opa-config.yaml"
- "--addr=0.0.0.0:8181"
ports:
- containerPort: 8181
hostPort: 8181
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "1Gi"
cpu: "1000m"
5.3 Go ライブラリパターン
Go 言語で記述されたアプリケーションの場合、OPA をライブラリとして組み込むことができる。ネットワーク通信が不要になるため、最高のパフォーマンスを実現する。
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"github.com/open-policy-agent/opa/rego"
)
func main() {
ctx := context.Background()
// Rego ポリシーを定義
module := `
package authz
default allow := false
allow if {
input.user.role == "admin"
}
allow if {
input.user.role == "editor"
input.action == "read"
}
`
// Rego インスタンスを作成
query, err := rego.New(
rego.Query("data.authz.allow"),
rego.Module("policy.rego", module),
).PrepareForEval(ctx)
if err != nil {
log.Fatal(err)
}
// 入力データを作成
input := map[string]interface{}{
"user": map[string]interface{}{
"name": "alice",
"role": "admin",
},
"action": "delete",
"resource": "/api/users/123",
}
// ポリシーを評価
results, err := query.Eval(ctx, rego.EvalInput(input))
if err != nil {
log.Fatal(err)
}
if len(results) > 0 && results[0].Expressions[0].Value == true {
fmt.Println("Access allowed")
} else {
fmt.Println("Access denied")
}
}
5.4 WebAssembly (WASM) パターン
OPA ポリシーを WebAssembly にコンパイルし、ブラウザやエッジ環境で実行する。
# ポリシーを WASM にコンパイル
opa build -t wasm -e authz/allow policy.rego
# 出力される bundle.tar.gz に WASM バイナリが含まれる
tar -xzf bundle.tar.gz /policy.wasm
JavaScript からの利用例:
const { loadPolicy } = require("@open-policy-agent/opa-wasm");
const fs = require("fs");
async function evaluate() {
const policyWasm = fs.readFileSync("policy.wasm");
const policy = await loadPolicy(policyWasm);
// データのセット
policy.setData({
roles: {
admin: { permissions: ["read", "write", "delete"] }
}
});
// 評価
const input = {
user: { role: "admin" },
action: "write"
};
const result = policy.evaluate(input);
console.log("Decision:", result);
}
evaluate();
5.5 デプロイメントパターンの比較
| パターン | レイテンシ | リソース効率 | 障害分離 | 適用先 |
|---|---|---|---|---|
| サイドカー | 最小 (μs) | 低 | 高 | Kubernetes マイクロサービス |
| デーモン | 小 (ms) | 中 | 中 | ノード共有型環境 |
| Go ライブラリ | 最小 (μs) | 最高 | 最高 | Go アプリケーション |
| WASM | 小 (ms) | 高 | 高 | ブラウザ、エッジ、非 Go 言語 |
6. Kubernetes との統合 (OPA Gatekeeper)
6.1 OPA Gatekeeper とは
OPA Gatekeeper は、Kubernetes の Admission Controller として動作する OPA の実装である。Kubernetes API サーバーへのリクエストをインターセプトし、ポリシーに基づいてリソースの作成・変更を許可または拒否する。
Gatekeeper は OPA の Kubernetes ネイティブな拡張であり、以下の特徴を持つ。
- CRD ベースのポリシー管理: ConstraintTemplate と Constraint を Kubernetes のカスタムリソースとして定義
- Audit 機能: 既存リソースに対するポリシー違反の定期的な監査
- Mutation Webhook: リソースの自動修正 (v3.10+)
- External Data Provider: 外部データソースとの連携
6.2 アーキテクチャ
┌───────────────────────────────────────────────────────────┐
│ Kubernetes Cluster │
│ │
│ ┌─────────┐ ┌──────────────┐ ┌────────────────┐ │
│ │ kubectl │───▶│ API Server │───▶│ etcd │ │
│ └─────────┘ └──────┬───────┘ └────────────────┘ │
│ │ │
│ Admission │
│ Webhook │
│ │ │
│ ┌──────▼───────┐ │
│ │ Gatekeeper │ │
│ │ Controller │ │
│ │ │ │
│ │ ┌──────────┐ │ │
│ │ │ OPA │ │ │
│ │ │ Engine │ │ │
│ │ └──────────┘ │ │
│ │ ┌──────────┐ │ │
│ │ │ Constraint│ │ │
│ │ │ Templates│ │ │
│ │ └──────────┘ │ │
│ │ ┌──────────┐ │ │
│ │ │ Audit │ │ │
│ │ │ Engine │ │ │
│ │ └──────────┘ │ │
│ └──────────────┘ │
│ │
└───────────────────────────────────────────────────────────┘
6.3 Gatekeeper のインストール
# Helm を使用したインストール
helm repo add gatekeeper https://open-policy-agent.github.io/gatekeeper/charts
helm install gatekeeper/gatekeeper \
--name-template=gatekeeper \
--namespace gatekeeper-system \
--create-namespace \
--set replicas=3 \
--set audit.interval=60 \
--set constraintViolationsLimit=100
# マニフェストを使用したインストール
kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/v3.15.0/deploy/gatekeeper.yaml
6.4 ConstraintTemplate と Constraint
Gatekeeper では、ポリシーを ConstraintTemplate (テンプレート) と Constraint (インスタンス) の 2 層構造で定義する。
6.4.1 ConstraintTemplate の定義
ConstraintTemplate は Rego ポリシーのテンプレートを定義する。
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8srequiredlabels
annotations:
description: "必須ラベルの存在を検証するテンプレート"
spec:
crd:
spec:
names:
kind: K8sRequiredLabels
validation:
openAPIV3Schema:
type: object
properties:
labels:
type: array
items:
type: string
description: "必須ラベルのリスト"
message:
type: string
description: "カスタムエラーメッセージ"
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequiredlabels
violation[{"msg": msg, "details": {"missing_labels": missing}}] {
provided := {label | input.review.object.metadata.labels[label]}
required := {label | label := input.parameters.labels[_]}
missing := required - provided
count(missing) > 0
msg := sprintf("リソース '%v' に必須ラベルが不足しています: %v", [
input.review.object.metadata.name,
missing
])
}
6.4.2 Constraint の定義
Constraint は ConstraintTemplate のインスタンスであり、具体的なパラメータと適用範囲を定義する。
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
name: require-team-label
spec:
enforcementAction: deny # deny, dryrun, warn
match:
kinds:
- apiGroups: [""]
kinds: ["Namespace"]
- apiGroups: ["apps"]
kinds: ["Deployment", "StatefulSet"]
excludedNamespaces:
- kube-system
- gatekeeper-system
parameters:
labels:
- "team"
- "environment"
message: "チーム名と環境ラベルは必須です"
6.5 よく使用される Gatekeeper ポリシー
6.5.1 コンテナイメージの制限
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8sallowedrepos
spec:
crd:
spec:
names:
kind: K8sAllowedRepos
validation:
openAPIV3Schema:
type: object
properties:
repos:
type: array
items:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8sallowedrepos
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
not startswith_any(container.image, input.parameters.repos)
msg := sprintf("コンテナ '%v' のイメージ '%v' は許可されたレジストリのものではありません。許可されたレジストリ: %v", [
container.name, container.image, input.parameters.repos
])
}
violation[{"msg": msg}] {
container := input.review.object.spec.initContainers[_]
not startswith_any(container.image, input.parameters.repos)
msg := sprintf("initContainer '%v' のイメージ '%v' は許可されたレジストリのものではありません。", [
container.name, container.image
])
}
startswith_any(str, prefixes) {
prefix := prefixes[_]
startswith(str, prefix)
}
---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sAllowedRepos
metadata:
name: allowed-repos
spec:
match:
kinds:
- apiGroups: ["apps"]
kinds: ["Deployment", "StatefulSet", "DaemonSet"]
namespaces:
- "production"
- "staging"
parameters:
repos:
- "gcr.io/my-project/"
- "docker.io/library/"
- "quay.io/my-org/"
6.5.2 リソース制限の必須化
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8sresourcelimits
spec:
crd:
spec:
names:
kind: K8sResourceLimits
validation:
openAPIV3Schema:
type: object
properties:
maxCPU:
type: string
maxMemory:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8sresourcelimits
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
not container.resources.limits
msg := sprintf("コンテナ '%v' にリソース制限 (limits) が設定されていません", [container.name])
}
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
not container.resources.requests
msg := sprintf("コンテナ '%v' にリソース要求 (requests) が設定されていません", [container.name])
}
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
container.resources.limits.cpu
not resource_within_limit(container.resources.limits.cpu, input.parameters.maxCPU)
msg := sprintf("コンテナ '%v' の CPU 制限 '%v' が最大値 '%v' を超えています", [
container.name, container.resources.limits.cpu, input.parameters.maxCPU
])
}
6.5.3 特権コンテナの禁止
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8spsprivilegedcontainer
spec:
crd:
spec:
names:
kind: K8sPSPPrivilegedContainer
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8spsprivilegedcontainer
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
container.securityContext.privileged == true
msg := sprintf("特権コンテナは許可されていません: '%v'", [container.name])
}
violation[{"msg": msg}] {
container := input.review.object.spec.initContainers[_]
container.securityContext.privileged == true
msg := sprintf("特権 initContainer は許可されていません: '%v'", [container.name])
}
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
container.securityContext.runAsUser == 0
msg := sprintf("root ユーザーでの実行は許可されていません: '%v'", [container.name])
}
---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sPSPPrivilegedContainer
metadata:
name: no-privileged-containers
spec:
match:
kinds:
- apiGroups: ["apps"]
kinds: ["Deployment", "StatefulSet", "DaemonSet"]
excludedNamespaces:
- kube-system
6.6 Audit (監査) 機能
Gatekeeper は既存リソースに対してポリシー違反を定期的にチェックする Audit 機能を提供する。
# 監査結果の確認
kubectl get k8srequiredlabels require-team-label -o yaml
# 出力例
status:
auditTimestamp: "2025-01-15T10:30:00Z"
totalViolations: 3
violations:
- enforcementAction: deny
kind: Deployment
name: legacy-app
namespace: production
message: "リソース 'legacy-app' に必須ラベルが不足しています: {\"team\"}"
- enforcementAction: deny
kind: Namespace
name: test-namespace
message: "リソース 'test-namespace' に必須ラベルが不足しています: {\"team\", \"environment\"}"
# Gatekeeper の全体的な監査ステータスを確認
kubectl describe constrainttemplate k8srequiredlabels
# すべての Constraint の違反をまとめて確認
kubectl get constraints -o json | jq '.items[] | {name: .metadata.name, violations: .status.totalViolations}'
7. マイクロサービス API 認可
7.1 HTTP API 認可パターン
OPA をマイクロサービスの API 認可に使用する基本的なパターンを示す。
┌──────────┐ ┌───────────────┐ ┌──────────────┐
│ Client │───▶│ API Gateway │───▶│ Backend │
│ │ │ / Service │ │ Service │
└──────────┘ └───────┬───────┘ └──────────────┘
│
┌────▼────┐
│ OPA │
│ Sidecar │
└─────────┘
7.2 Go サービスでの統合例
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
)
// OPAClient は OPA との通信を行うクライアント
type OPAClient struct {
baseURL string
client *http.Client
}
// OPAInput は OPA に送信する入力データ
type OPAInput struct {
Input map[string]interface{} `json:"input"`
}
// OPAResult は OPA からの応答
type OPAResult struct {
Result bool `json:"result"`
}
func NewOPAClient(baseURL string) *OPAClient {
return &OPAClient{
baseURL: baseURL,
client: &http.Client{},
}
}
func (c *OPAClient) IsAllowed(input map[string]interface{}) (bool, error) {
body, _ := json.Marshal(OPAInput{Input: input})
resp, err := c.client.Post(
c.baseURL+"/v1/data/httpapi/authz/allow",
"application/json",
bytes.NewReader(body),
)
if err != nil {
return false, fmt.Errorf("OPA request failed: %w", err)
}
defer resp.Body.Close()
var result OPAResult
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return false, fmt.Errorf("OPA response decode failed: %w", err)
}
return result.Result, nil
}
// AuthMiddleware は認可ミドルウェア
func AuthMiddleware(opa *OPAClient) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// JWT からユーザー情報を抽出 (簡略化)
token := r.Header.Get("Authorization")
userInfo := extractUserInfo(token)
input := map[string]interface{}{
"method": r.Method,
"path": strings.Split(r.URL.Path, "/"),
"user": userInfo,
"headers": flattenHeaders(r.Header),
}
allowed, err := opa.IsAllowed(input)
if err != nil {
http.Error(w, "Authorization check failed", http.StatusInternalServerError)
return
}
if !allowed {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
next.ServeHTTP(w, r)
})
}
}
func main() {
opa := NewOPAClient("http://localhost:8181")
mux := http.NewServeMux()
mux.HandleFunc("/api/v1/users", handleUsers)
mux.HandleFunc("/api/v1/admin", handleAdmin)
handler := AuthMiddleware(opa)(mux)
http.ListenAndServe(":8080", handler)
}
7.3 対応する Rego ポリシー
package httpapi.authz
import input.method
import input.path
import input.user
default allow := false
# --- パブリックエンドポイント ---
allow if {
method == "GET"
path == ["", "api", "v1", "health"]
}
# --- 認証済みユーザー ---
allow if {
method == "GET"
path == ["", "api", "v1", "users"]
user.authenticated == true
}
# --- リソースオーナー ---
allow if {
method == "PUT"
path == ["", "api", "v1", "users", user_id]
user.id == user_id
}
# --- 管理者 ---
allow if {
path[2] == "admin"
"admin" in user.roles
}
# --- レート制限チェック ---
allow if {
method == "POST"
path == ["", "api", "v1", "users"]
user.authenticated == true
not rate_limited
}
rate_limited if {
user.request_count_last_minute > 100
}
# --- 詳細な応答を返すバージョン ---
authz := {
"allow": allow,
"reason": reason,
"headers": response_headers,
}
reason := "public endpoint" if {
method == "GET"
path == ["", "api", "v1", "health"]
}
reason := "insufficient permissions" if {
not allow
}
response_headers := {"X-RateLimit-Remaining": remaining} if {
remaining := sprintf("%d", [100 - user.request_count_last_minute])
}
7.4 Python (Flask) での統合例
import functools
import requests
from flask import Flask, request, jsonify, g
app = Flask(__name__)
OPA_URL = "http://localhost:8181/v1/data/httpapi/authz/allow"
def authorize(f):
@functools.wraps(f)
def decorated(*args, **kwargs):
# リクエスト情報を OPA に送信
opa_input = {
"input": {
"method": request.method,
"path": request.path.split("/"),
"user": {
"id": g.get("user_id"),
"roles": g.get("user_roles", []),
"authenticated": g.get("authenticated", False),
},
"query_params": dict(request.args),
"content_type": request.content_type,
}
}
try:
resp = requests.post(OPA_URL, json=opa_input, timeout=1.0)
result = resp.json()
if not result.get("result", False):
return jsonify({"error": "Forbidden"}), 403
except requests.RequestException as e:
app.logger.error(f"OPA authorization failed: {e}")
# Fail-closed: OPA に到達できない場合は拒否
return jsonify({"error": "Authorization service unavailable"}), 503
return f(*args, **kwargs)
return decorated
@app.route("/api/v1/users", methods=["GET"])
@authorize
def get_users():
return jsonify({"users": [...]})
@app.route("/api/v1/admin/settings", methods=["GET", "PUT"])
@authorize
def admin_settings():
return jsonify({"settings": {... }})
7.5 データフィルタリング
OPA はアクセス制御だけでなく、ユーザーの権限に基づいたデータフィルタリングにも使用できる。
package httpapi.filter
# ユーザーがアクセス可能なレコードのみを返す
visible_records[record] {
some record in data.records
record.department == input.user.department
}
visible_records[record] {
some record in data.records
record.classification == "public"
}
# 管理者はすべてのレコードを参照可能
visible_records[record] {
some record in data.records
"admin" in input.user.roles
}
# フィールドレベルのフィルタリング
sanitized_records[sanitized] {
some record in visible_records
sanitized := object.remove(record, masked_fields)
}
masked_fields := {"salary", "ssn"} if {
not "hr" in input.user.roles
}
masked_fields := set() if {
"hr" in input.user.roles
}
8. Envoy / Istio との統合
8.1 OPA-Envoy プラグイン
OPA は Envoy プロキシの External Authorization (ext_authz) フィルタと統合できる。これにより、サービスメッシュ内のすべてのトラフィックに対して統一的なポリシー適用が可能になる。
OPA には専用の Envoy プラグイン (opa-envoy-plugin) が用意されており、gRPC ベースの External Authorization API を実装している。
┌────────┐ ┌──────────────┐ ┌──────────────┐
│ Client │────▶│ Envoy │────▶│ Upstream │
│ │ │ Proxy │ │ Service │
└────────┘ └──────┬───────┘ └──────────────┘
│
ext_authz
│
┌──────▼───────┐
│ OPA-Envoy │
│ Plugin │
└──────────────┘
8.2 OPA-Envoy の設定
OPA 設定 (opa-envoy-config.yaml)
services:
- name: bundle-server
url: https://bundle.example.com
bundles:
envoy/authz:
service: bundle-server
resource: /bundles/envoy/authz/bundle.tar.gz
plugins:
envoy_ext_authz_grpc:
addr: :9191
path: envoy/authz/allow
dry-run: false
enable-reflection: true
# 判断結果のプロトバージョン
proto-descriptor: /etc/opa/proto/auth.proto
decision_logs:
console: true
Envoy 設定
static_resources:
listeners:
- name: listener_0
address:
socket_address:
address: 0.0.0.0
port_value: 10000
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: ingress_http
route_config:
name: local_route
virtual_hosts:
- name: backend
domains: ["*"]
routes:
- match:
prefix: "/"
route:
cluster: upstream_service
http_filters:
# External Authorization フィルタ
- name: envoy.filters.http.ext_authz
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz
transport_api_version: V3
grpc_service:
envoy_grpc:
cluster_name: opa
timeout: 0.5s
failure_mode_allow: false # OPA 障害時はリクエストを拒否
with_request_body:
max_request_bytes: 8192
allow_partial_message: true
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
clusters:
- name: opa
type: STRICT_DNS
connect_timeout: 1s
load_assignment:
cluster_name: opa
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: 127.0.0.1
port_value: 9191
typed_extension_protocol_options:
envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
"@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
explicit_http_config:
http2_protocol_options: {}
- name: upstream_service
type: STRICT_DNS
connect_timeout: 1s
load_assignment:
cluster_name: upstream_service
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: 127.0.0.1
port_value: 8080
8.3 Envoy 用 Rego ポリシー
package envoy.authz
import input.attributes.request.http as http_request
import input.attributes.source.address as source_address
import input.attributes.destination.address as dest_address
default allow := false
# ヘルスチェックは常に許可
allow if {
http_request.method == "GET"
http_request.path == "/healthz"
}
# JWT トークンの検証
allow if {
is_valid_token
action_allowed
}
# トークン検証
is_valid_token if {
token := bearer_token
[valid, _, payload] := io.jwt.decode_verify(token, {
"cert": data.keys.public_key,
"iss": "https://auth.example.com",
"aud": "my-service"
})
valid == true
}
# ベアラートークンの抽出
bearer_token := t if {
v := http_request.headers.authorization
startswith(v, "Bearer ")
t := substring(v, count("Bearer "), -1)
}
# トークンペイロードの取得
token_payload := payload if {
[_, payload, _] := io.jwt.decode(bearer_token)
}
# アクションの認可
action_allowed if {
http_request.method == "GET"
glob.match("/api/v1/users/*", ["/"], http_request.path)
}
action_allowed if {
http_request.method == "POST"
http_request.path == "/api/v1/users"
"admin" in token_payload.roles
}
action_allowed if {
http_request.method == "DELETE"
glob.match("/api/v1/users/*", ["/"], http_request.path)
"super_admin" in token_payload.roles
}
# サービス間通信の制御
action_allowed if {
source_service := token_payload.sub
dest_service := http_request.headers["x-destination-service"]
allowed_communication[source_service][dest_service]
}
# サービス間通信マトリクス
allowed_communication := {
"frontend": {"user-service", "product-service"},
"user-service": {"auth-service", "notification-service"},
"order-service": {"user-service", "product-service", "payment-service"},
}
8.4 Istio との統合
Istio サービスメッシュでは、AuthorizationPolicy を拡張して OPA を External Authorizer として使用できる。
# Istio の meshConfig に OPA を External Authorizer として登録
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
meshConfig:
extensionProviders:
- name: opa-ext-authz
envoyExtAuthzGrpc:
service: opa.opa-system.svc.cluster.local
port: 9191
timeout: 1s
---
# AuthorizationPolicy で OPA を使用
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: opa-authz
namespace: production
spec:
action: CUSTOM
provider:
name: opa-ext-authz
rules:
- to:
- operation:
paths: ["/api/*"]
notPaths: ["/api/v1/health"]
9. Terraform との統合
9.1 概要
OPA は Terraform のプラン (plan) データに対してポリシーを適用し、インフラストラクチャの変更が組織のポリシーに準拠しているかを検証できる。これは Conftest ツールまたは直接 OPA を使用して実現する。
┌──────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────┐
│ terraform│───▶│ terraform │───▶│ OPA / │───▶│ terraform│
│ init │ │ plan -out │ │ Conftest │ │ apply │
│ │ │ tfplan │ │ (validate) │ │ │
└──────────┘ └──────────────┘ └──────────────┘ └──────────┘
│
Pass / Fail
9.2 Terraform プランの変換
# Terraform プランを作成
terraform plan -out=tfplan
# プランを JSON に変換
terraform show -json tfplan > tfplan.json
# OPA で評価
opa eval --data policy/ --input tfplan.json "data.terraform.deny"
# Conftest で評価
conftest test tfplan.json --policy policy/
9.3 Terraform ポリシーの例
9.3.1 許可されたインスタンスタイプの制限
package terraform
import input.resource_changes
# 許可されたインスタンスタイプ
allowed_instance_types := {
"t3.micro", "t3.small", "t3.medium",
"m5.large", "m5.xlarge",
"c5.large", "c5.xlarge"
}
deny[msg] {
some rc in resource_changes
rc.type == "aws_instance"
rc.change.after.instance_type
not rc.change.after.instance_type in allowed_instance_types
msg := sprintf(
"EC2 インスタンス '%v' のインスタンスタイプ '%v' は許可されていません。許可タイプ: %v",
[rc.name, rc.change.after.instance_type, allowed_instance_types]
)
}
9.3.2 セキュリティグループの検証
package terraform
deny[msg] {
some rc in resource_changes
rc.type == "aws_security_group_rule"
rc.change.after.type == "ingress"
rc.change.after.cidr_blocks[_] == "0.0.0.0/0"
rc.change.after.from_port <= 22
rc.change.after.to_port >= 22
msg := sprintf(
"セキュリティグループルール '%v': SSH (ポート 22) を全世界に公開することは禁止されています",
[rc.name]
)
}
deny[msg] {
some rc in resource_changes
rc.type == "aws_security_group_rule"
rc.change.after.type == "ingress"
rc.change.after.cidr_blocks[_] == "0.0.0.0/0"
rc.change.after.from_port <= 3389
rc.change.after.to_port >= 3389
msg := sprintf(
"セキュリティグループルール '%v': RDP (ポート 3389) を全世界に公開することは禁止されています",
[rc.name]
)
}
9.3.3 S3 バケットの暗号化必須化
package terraform
deny[msg] {
some rc in resource_changes
rc.type == "aws_s3_bucket"
rc.change.actions[_] == "create"
not has_encryption(rc)
msg := sprintf(
"S3 バケット '%v': サーバーサイド暗号化が設定されていません。AES256 または aws:kms を使用してください",
[rc.name]
)
}
has_encryption(rc) if {
rc.change.after.server_side_encryption_configuration[_].rule[_].apply_server_side_encryption_by_default[_].sse_algorithm
}
deny[msg] {
some rc in resource_changes
rc.type == "aws_s3_bucket"
rc.change.actions[_] == "create"
not has_versioning(rc)
msg := sprintf(
"S3 バケット '%v': バージョニングが有効になっていません",
[rc.name]
)
}
has_versioning(rc) if {
rc.change.after.versioning[_].enabled == true
}
deny[msg] {
some rc in resource_changes
rc.type == "aws_s3_bucket"
rc.change.actions[_] == "create"
not has_public_access_block(rc)
msg := sprintf(
"S3 バケット '%v': パブリックアクセスブロックを設定してください",
[rc.name]
)
}
9.3.4 タグの必須化
package terraform
required_tags := {"Environment", "Team", "CostCenter", "ManagedBy"}
deny[msg] {
some rc in resource_changes
rc.change.actions[_] == "create"
taggable_resource(rc.type)
provided_tags := {tag | rc.change.after.tags[tag]}
missing := required_tags - provided_tags
count(missing) > 0
msg := sprintf(
"リソース '%v' (%v) に必須タグが不足しています: %v",
[rc.name, rc.type, missing]
)
}
taggable_resource(type) if {
taggable := {
"aws_instance", "aws_s3_bucket", "aws_rds_instance",
"aws_lambda_function", "aws_ecs_service", "aws_eks_cluster",
"aws_vpc", "aws_subnet", "aws_security_group"
}
type in taggable
}
9.4 Conftest の活用
Conftest は OPA をベースとした構成ファイルのテストツールであり、Terraform 以外にも Kubernetes マニフェスト、Dockerfile、各種設定ファイルに対してポリシーテストを実行できる。
# インストール
brew install conftest
# Terraform プランのテスト
conftest test tfplan.json --policy policy/ --output table
# Kubernetes マニフェストのテスト
conftest test k8s/ --policy policy/k8s/ --all-namespaces
# Dockerfile のテスト
conftest test Dockerfile --policy policy/docker/
# 複数フォーマットのサポート
conftest test --policy policy/ \
--input json tfplan.json \
--input yaml k8s-manifests/ \
--input dockerfile Dockerfile
Dockerfile ポリシーの例
package docker
deny[msg] {
input.Cmd == "from"
val := input.Value[0]
contains(val, ":latest")
msg := "latest タグの使用は禁止されています。具体的なバージョンタグを指定してください"
}
deny[msg] {
input.Cmd == "user"
input.Value[0] == "root"
msg := "root ユーザーでの実行は禁止されています"
}
deny[msg] {
input.Cmd == "run"
some val in input.Value
contains(val, "curl")
contains(val, "|")
contains(val, "sh")
msg := "curl | sh パターンは禁止されています。セキュリティリスクがあります"
}
10. ポリシーテスト
10.1 テストの重要性
ポリシーはコードと同様にバグが入り込む可能性がある。特にセキュリティポリシーのバグは致命的な結果をもたらす。OPA は組み込みのテストフレームワークを提供し、ポリシーの品質を保証する。
10.2 テストの基本
テストファイルは _test.rego のサフィックスを持ち、test_ プレフィックスのルールとして定義する。
# policy.rego
package authz
default allow := false
allow if {
input.user.role == "admin"
}
allow if {
input.user.role == "editor"
input.method == "GET"
}
allow if {
input.user.role == "editor"
input.method == "PUT"
input.user.id == input.resource.owner_id
}
# policy_test.rego
package authz
# --- 管理者のテスト ---
test_admin_allow_read if {
allow with input as {
"user": {"role": "admin", "id": "user1"},
"method": "GET",
"resource": {"owner_id": "user2"}
}
}
test_admin_allow_delete if {
allow with input as {
"user": {"role": "admin", "id": "user1"},
"method": "DELETE",
"resource": {"owner_id": "user2"}
}
}
# --- 編集者のテスト ---
test_editor_allow_read if {
allow with input as {
"user": {"role": "editor", "id": "user1"},
"method": "GET",
"resource": {"owner_id": "user2"}
}
}
test_editor_deny_delete if {
not allow with input as {
"user": {"role": "editor", "id": "user1"},
"method": "DELETE",
"resource": {"owner_id": "user2"}
}
}
test_editor_allow_put_own_resource if {
allow with input as {
"user": {"role": "editor", "id": "user1"},
"method": "PUT",
"resource": {"owner_id": "user1"}
}
}
test_editor_deny_put_other_resource if {
not allow with input as {
"user": {"role": "editor", "id": "user1"},
"method": "PUT",
"resource": {"owner_id": "user2"}
}
}
# --- 閲覧者のテスト ---
test_viewer_deny_all if {
not allow with input as {
"user": {"role": "viewer", "id": "user1"},
"method": "GET",
"resource": {"owner_id": "user1"}
}
}
# --- 未認証ユーザーのテスト ---
test_unauthenticated_deny if {
not allow with input as {
"method": "GET",
"resource": {"owner_id": "user1"}
}
}
10.3 テストの実行
# テストの実行
opa test . -v
# 出力例:
# policy_test.rego:
# data.authz.test_admin_allow_read: PASS (1.234ms)
# data.authz.test_admin_allow_delete: PASS (0.987ms)
# data.authz.test_editor_allow_read: PASS (0.876ms)
# data.authz.test_editor_deny_delete: PASS (0.654ms)
# data.authz.test_editor_allow_put_own_resource: PASS (0.543ms)
# data.authz.test_editor_deny_put_other_resource: PASS (0.432ms)
# data.authz.test_viewer_deny_all: PASS (0.321ms)
# data.authz.test_unauthenticated_deny: PASS (0.210ms)
# -----------------------------------------------
# PASS: 8/8
# カバレッジの計測
opa test . --coverage --format=json
# 出力例:
# {
# "files": {
# "policy.rego": {
# "covered": [3, 5, 6, 9, 10, 13, 14, 15],
# "not_covered": [],
# "coverage": 100
# }
# }
# }
# 特定のテストのみ実行
opa test . -v --run "test_admin"
# ベンチマーク
opa test . --bench --count 100
10.4 with キーワードによるモック
with キーワードを使用して、テスト時にデータや組み込み関数をモックできる。
# テスト対象のポリシー
package authz
allow if {
is_business_hours
user_has_permission
}
is_business_hours if {
now := time.now_ns()
[hour, _, _] := time.clock([now, "Asia/Tokyo"])
hour >= 9
hour < 18
}
user_has_permission if {
some role in data.user_roles[input.user]
some perm in data.roles[role].permissions
perm == input.action
}
# テスト
package authz
# 営業時間内のテスト (time.now_ns をモック)
test_allow_during_business_hours if {
# 2025-01-15 12:00:00 JST のナノ秒
allow with input as {
"user": "alice",
"action": "read"
}
with data.user_roles as {"alice": ["admin"]}
with data.roles as {"admin": {"permissions": ["read", "write"]}}
with time.now_ns as 1736910000000000000 # JST 12:00
}
# 営業時間外のテスト
test_deny_outside_business_hours if {
not allow with input as {
"user": "alice",
"action": "read"
}
with data.user_roles as {"alice": ["admin"]}
with data.roles as {"admin": {"permissions": ["read", "write"]}}
with time.now_ns as 1736946000000000000 # JST 22:00
}
# HTTP 呼び出しのモック
test_external_api_check if {
allow with input as {"user": "alice"}
with http.send as {"status_code": 200, "body": {"valid": true}}
}
10.5 CI/CD パイプラインでのテスト
# GitHub Actions の例
name: OPA Policy Test
on:
pull_request:
paths:
- 'policies/**'
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup OPA
uses: open-policy-agent/setup-opa@v2
with:
version: latest
- name: Run OPA Tests
run: |
opa test policies/ -v --coverage --format=json > coverage.json
echo "Coverage Report:"
cat coverage.json | jq '.files | to_entries[] | "\(.key): \(.value.coverage)%"'
- name: Check Coverage Threshold
run: |
COVERAGE=$(cat coverage.json | jq '[.files[].coverage] | add / length')
echo "Average coverage: ${COVERAGE}%"
if (( $(echo "$COVERAGE < 80" | bc -l) )); then
echo "Coverage ${COVERAGE}% is below threshold of 80%"
exit 1
fi
- name: OPA Check (Format & Type Check)
run: |
opa fmt --diff --fail policies/
opa check policies/ --strict
11. バンドルとポリシー配布
11.1 バンドルの概要
OPA のバンドル機能は、ポリシーとデータを一つのパッケージとしてまとめ、リモートサーバーから OPA エージェントに配布する仕組みである。大規模な環境でのポリシー管理に不可欠な機能であり、以下の利点がある。
- ポリシーのバージョン管理と配布の自動化
- ポリシーの署名による改ざん検知
- 差分更新による効率的な配布
11.2 バンドルの構造
bundle.tar.gz
├── .manifest # バンドルのメタデータ
├── .signatures.json # ポリシーの署名 (オプション)
├── authz/
│ ├── policy.rego # Rego ポリシーファイル
│ ├── policy_test.rego # テストファイル (通常は除外)
│ └── data.json # パッケージスコープのデータ
├── rbac/
│ ├── roles.rego
│ └── data.json
└── data.json # ルートスコープのデータ
11.3 バンドルのビルド
# 基本的なバンドルのビルド
opa build -b policies/ -o bundle.tar.gz
# エントリポイントを指定してバンドルをビルド
opa build -b policies/ \
-e authz/allow \
-e rbac/user_permissions \
-o bundle.tar.gz
# WASM を含むバンドルのビルド
opa build -b policies/ \
-t wasm \
-e authz/allow \
-o bundle.tar.gz
# テストファイルを除外
opa build -b policies/ \
--ignore "*_test.rego" \
-o bundle.tar.gz
# リビジョンを指定
opa build -b policies/ \
--revision "v1.2.3-abc123" \
-o bundle.tar.gz
11.4 バンドルの署名
バンドルの署名機能により、配布されるポリシーの改ざんを検知できる。
# 署名鍵の生成
openssl genrsa -out private_key.pem 2048
openssl rsa -in private_key.pem -pubout -out public_key.pem
# 署名付きバンドルのビルド
opa build -b policies/ \
--signing-key private_key.pem \
--signing-alg RS256 \
--claims-file claims.json \
-o bundle.tar.gz
// claims.json
{
"iss": "policy-team@example.com",
"scope": "write",
"keyid": "production-key-1",
"exp": 1735689600
}
OPA 側の検証設定:
# opa-config.yaml
bundles:
authz:
service: bundle-server
resource: /bundles/authz/bundle.tar.gz
signing:
keyid: production-key-1
scope: write
keys:
production-key-1:
algorithm: RS256
key: |
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhk...
-----END PUBLIC KEY-----
11.5 バンドル配布サーバー
バンドル配布サーバーは HTTP GET リクエストに対してバンドルを返すシンプルな HTTP サーバーである。以下のオプションが利用可能。
11.5.1 Nginx ベースの静的配布
server {
listen 8888;
server_name bundle.example.com;
location /bundles/ {
root /var/www/bundles;
autoindex off;
# ETag サポート (変更検知)
etag on;
# キャッシュ制御
add_header Cache-Control "no-cache, must-revalidate";
# アクセス制御
auth_request /auth;
}
location = /auth {
internal;
proxy_pass http://auth-service:8080/verify;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
}
}
11.5.2 OCI レジストリを使用した配布
OPA はバンドルを OCI (Open Container Initiative) アーティファクトとしてコンテナレジストリに保存・取得できる。
# バンドルを OCI レジストリにプッシュ
opa build -b policies/ -o bundle.tar.gz
oras push ghcr.io/myorg/opa-bundles/authz:v1.0.0 \
--artifact-type application/vnd.oci.image.layer.v1.tar+gzip \
bundle.tar.gz
# OPA の設定
# opa-config.yaml
services:
- name: ghcr
url: https://ghcr.io
type: oci
credentials:
bearer:
token: "${GITHUB_TOKEN}"
bundles:
authz:
service: ghcr
resource: myorg/opa-bundles/authz:v1.0.0
persist: true
polling:
min_delay_seconds: 60
max_delay_seconds: 300
11.5.3 S3 ベースの配布
services:
- name: s3
url: https://my-bucket.s3.ap-northeast-1.amazonaws.com
credentials:
s3_signing:
environment_credentials: {}
# または明示的な認証情報
# access_key: "${AWS_ACCESS_KEY_ID}"
# secret_key: "${AWS_SECRET_ACCESS_KEY}"
# token: "${AWS_SESSION_TOKEN}"
bundles:
authz:
service: s3
resource: /bundles/authz/bundle.tar.gz
persist: true
polling:
min_delay_seconds: 30
max_delay_seconds: 120
11.6 ステータス報告
OPA は自身の状態 (バンドルのロード状況、エラーなど) を外部に報告できる。
status:
service: management-server
partition_name: /status
# ステータス API のレスポンス例
# GET /status
# {
# "labels": {
# "app": "my-service",
# "region": "ap-northeast-1"
# },
# "bundles": {
# "authz": {
# "name": "authz",
# "active_revision": "v1.2.3-abc123",
# "last_successful_download": "2025-01-15T10:30:00Z",
# "last_successful_activation": "2025-01-15T10:30:01Z"
# }
# },
# "plugins": {
# "bundle": {"state": "OK"},
# "decision_logs": {"state": "OK"},
# "status": {"state": "OK"}
# }
# }
12. パフォーマンスチューニング
12.1 パフォーマンス特性
OPA はインメモリで動作するため、基本的に非常に高速である。しかし、ポリシーの複雑さやデータ量によってはパフォーマンスが問題になることがある。
典型的なパフォーマンス指標:
| シナリオ | 平均レイテンシ | 99 パーセンタイル |
|---|---|---|
| シンプルな RBAC チェック | 0.05ms | 0.1ms |
| JWT 検証 + RBAC | 0.5ms | 1.0ms |
| 複雑な ABAC (50+ ルール) | 1.0ms | 3.0ms |
| 大規模データセット (100K レコード) | 5.0ms | 15.0ms |
| 外部 HTTP 呼び出し含む | 50ms+ | 200ms+ |
12.2 プロファイリング
# プロファイリングの有効化
opa eval --data policy.rego --input input.json \
--profile --format=pretty \
"data.authz.allow"
# 出力例:
# +----------+----------+----------+---------+
# | TIMER | EVAL | REDO | TOTAL |
# +----------+----------+----------+---------+
# | query | 125.3µs | 45.2µs | 170.5µs |
# | authz | 98.1µs | 32.1µs | 130.2µs |
# | rbac | 45.6µs | 12.3µs | 57.9µs |
# +----------+----------+----------+---------+
# ベンチマーク
opa bench --data policy.rego --input input.json \
"data.authz.allow"
# 出力例:
# +-----------------------------+--------+
# | BENCHMARK | TIME |
# +-----------------------------+--------+
# | data.authz.allow | 125µs |
# | iterations: 10000 | |
# | allocs: 45 | |
# | heap: 12.3KB | |
# +-----------------------------+--------+
12.3 最適化テクニック
12.3.1 部分評価 (Partial Evaluation) の活用
部分評価は、入力の一部が既知の場合に、残りの入力に依存する最小限のポリシーを事前に計算する手法である。
# 部分評価 API
curl -X POST http://localhost:8181/v1/compile \
-H "Content-Type: application/json" \
-d '{
"query": "data.authz.allow == true",
"input": {
"user": {"role": "admin"}
},
"unknowns": ["input.action", "input.resource"]
}'
# 結果: role=admin に対して最適化されたポリシーが返される
12.3.2 インデックスの活用
OPA は特定のパターンでルールを記述すると、自動的にインデックスを構築してパフォーマンスを向上させる。
# ✅ インデックスが効くパターン
# 完全一致 (Equality)
allow if {
data.roles[input.user.role] # role でインデックス参照
}
# ✅ セットメンバーシップ
allow if {
input.action in data.allowed_actions # セットのルックアップ
}
# ❌ インデックスが効かないパターン (避けるべき)
# 線形走査
allow if {
some role in data.all_roles
role.name == input.user.role # 全件走査が発生
}
12.3.3 http.send のキャッシュ
# キャッシュを有効にした http.send
response := http.send({
"method": "GET",
"url": "https://api.example.com/users",
"cache": true,
"force_cache": true,
"force_cache_duration_seconds": 300 # 5 分間キャッシュ
})
OPA の設定でキャッシュサイズを調整:
caching:
inter_query_builtin_cache:
max_size_bytes: 536870912 # 512MB
forced_eviction_threshold_percentage: 80
stale_entry_eviction_period_seconds: 60
12.3.4 データ構造の最適化
# ❌ 非効率: 線形走査
deny if {
some user in data.blocked_users # 配列の走査
user == input.user.id
}
# ✅ 効率的: セットのルックアップ
deny if {
data.blocked_users_set[input.user.id] # O(1) ルックアップ
}
# データの変換
# blocked_users: ["user1", "user2", "user3"] → 非効率
# blocked_users_set: {"user1": true, "user2": true, "user3": true} → 効率的
12.4 リソース設定のガイドライン
| 環境 | CPU | メモリ | バンドルサイズ |
|---|---|---|---|
| 小規模 (< 100 ルール) | 100m | 64Mi | < 1MB |
| 中規模 (100-500 ルール) | 250m | 256Mi | 1-10MB |
| 大規模 (500+ ルール) | 500m | 512Mi-1Gi | 10-50MB |
| データヘビー (100K+ レコード) | 1000m | 2Gi+ | 50MB+ |
12.5 メトリクスとモニタリング
OPA は Prometheus 形式のメトリクスを公開する。
# Prometheus メトリクスの取得
curl http://localhost:8181/metrics
# 主要なメトリクス
# opa_decision_logs_total - 判断ログの総数
# opa_bundle_loaded_total - バンドルロードの総数
# http_request_duration_seconds - HTTP リクエストのレイテンシ
# opa_rego_query_eval_ns - Rego クエリ評価のナノ秒
Grafana ダッシュボードの設定例:
{
"panels": [
{
"title": "OPA Query Latency (p99)",
"type": "graph",
"targets": [
{
"expr": "histogram_quantile(0.99, rate(http_request_duration_seconds_bucket{handler=\"/v1/data\"}[5m]))",
"legendFormat": "p99"
}
]
},
{
"title": "OPA Decision Rate",
"type": "stat",
"targets": [
{
"expr": "rate(http_request_duration_seconds_count{handler=\"/v1/data\"}[5m])"
}
]
},
{
"title": "Bundle Load Status",
"type": "table",
"targets": [
{
"expr": "opa_bundle_loaded_total"
}
]
}
]
}
13. セキュリティ考慮事項
13.1 OPA 自体のセキュリティ
OPA をプロダクション環境で運用する際には、OPA 自体のセキュリティを確保する必要がある。
13.1.1 認証と認可
OPA の管理 API には認証・認可を設定すべきである。
# opa-config.yaml
# TLS の設定
addr: ":8181"
tls:
cert_file: /certs/server.crt
private_key_file: /certs/server.key
ca_cert_file: /certs/ca.crt
# クライアント証明書の要求
client_auth_type: require_and_verify_client_cert
# 認可ポリシー (OPA 自身の API に対するアクセス制御)
default_authorization_policy: /system/authz/allow
# system/authz.rego
package system.authz
default allow := false
# ヘルスチェックは誰でもアクセス可能
allow if {
input.path == ["health"]
}
# メトリクスは監視システムからのみ
allow if {
input.path == ["metrics"]
net.cidr_contains("10.0.0.0/8", input.identity)
}
# ポリシー評価は認証済みクライアントのみ
allow if {
input.path[0] == "v1"
input.path[1] == "data"
input.identity != ""
}
# ポリシー管理は管理者のみ
allow if {
input.path[0] == "v1"
input.path[1] == "policies"
input.identity == "admin-client"
}
13.1.2 Decision Log のマスキング
判断ログに機密情報が含まれる場合、マスキングポリシーを設定する。
# system/log.rego
package system.log
# 機密フィールドのマスキング
mask["/input/request/headers/authorization"] {
input.input.request.headers.authorization
}
mask["/input/user/password"] {
input.input.user.password
}
mask["/input/token"] {
input.input.token
}
# 特定のパスの判断ログを完全に除外
mask["/input"] {
input.path == "internal/sensitive"
}
13.1.3 ネットワークセキュリティ
# Kubernetes NetworkPolicy
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: opa-network-policy
namespace: opa-system
spec:
podSelector:
matchLabels:
app: opa
policyTypes:
- Ingress
- Egress
ingress:
# 同一 Pod のアプリケーションコンテナからのみアクセス許可
- from:
- podSelector:
matchLabels:
opa-sidecar: "true"
ports:
- port: 8181
protocol: TCP
egress:
# バンドルサーバーへのアクセス
- to:
- namespaceSelector:
matchLabels:
name: bundle-system
ports:
- port: 443
protocol: TCP
# DNS
- to:
- namespaceSelector: {}
ports:
- port: 53
protocol: UDP
- port: 53
protocol: TCP
13.2 ポリシー記述のセキュリティ
13.2.1 Fail-Closed 設計
# ✅ 正しい: デフォルトは拒否
package authz
default allow := false
allow if {
# 明示的に許可する条件
}
# ❌ 危険: デフォルトが許可
# default allow := true
#
# deny if {
# # 明示的に拒否する条件
# }
# (deny リストに漏れがあるとすべて許可されてしまう)
13.2.2 入力の検証
package authz
default allow := false
# 入力の構造を検証
allow if {
# 必須フィールドの存在を確認
input.user
input.user.id
input.user.roles
is_array(input.user.roles)
# 実際の認可ロジック
"admin" in input.user.roles
}
13.2.3 外部呼び出しの制限
# ❌ 危険: 無制限の外部 HTTP 呼び出し
response := http.send({
"method": "GET",
"url": input.callback_url # ユーザーが任意の URL を指定できてしまう (SSRF)
})
# ✅ 安全: ホワイトリストによる制限
response := http.send({
"method": "GET",
"url": url
}) if {
url := concat("", ["https://api.internal.example.com/users/", input.user_id])
regex.match(`^[a-zA-Z0-9-]+$`, input.user_id) # 入力のバリデーション
}
14. エコシステムとツール
14.1 主要なツールとプロジェクト
14.1.1 OPA Gatekeeper
前述のとおり、Kubernetes Admission Controller として OPA を運用するためのプロジェクト。CRD ベースのポリシー管理と Audit 機能を提供する。
14.1.2 Conftest
構造化された設定ファイル (Kubernetes YAML, Terraform HCL, Dockerfile, JSON, XML 等) に対してポリシーテストを実行するツール。CI/CD パイプラインでの利用に最適化されている。
# インストール
brew install conftest
# Kubernetes マニフェストのテスト
conftest test deployment.yaml -p policy/ --output table
# 複数のファイルフォーマットに対応
conftest test --policy policy/ \
deployment.yaml \ # Kubernetes YAML
terraform.tfplan.json \ # Terraform Plan
Dockerfile # Dockerfile
14.1.3 Styra DAS (Declarative Authorization Service)
Styra 社が提供する OPA の商用管理プラットフォーム。以下の機能を提供する。
- Web UI によるポリシーの作成・編集
- バンドルの自動配布
- Decision Log の可視化と分析
- ポリシーの影響分析 (Impact Analysis)
- コンプライアンスレポーティング
14.1.4 OPA Playground
ブラウザ上で Rego ポリシーを実行・テストできるオンラインツール。学習やプロトタイピングに便利。
14.1.5 Regal — Rego Linter
Rego コードの品質を保つための Linter ツール。
# インストール
brew install styrainc/packages/regal
# Lint の実行
regal lint policies/
# 出力例:
# Rule: opa-fmt
# Description: File should be formatted with `opa fmt`
# Category: style
# Location: policy.rego:15
#
# Rule: use-assignment-operator
# Description: Use := for assignment, not =
# Category: style
# Location: policy.rego:22
# 設定ファイル (.regal/config.yaml)
# rules:
# style:
# opa-fmt:
# level: error
# line-length:
# level: warning
# max-line-length: 120
14.2 ライブラリとフレームワーク
| 言語 | ライブラリ | 用途 |
|---|---|---|
| Go | github.com/open-policy-agent/opa/rego | OPA を Go ライブラリとして組み込み |
| Python | opa-python-client | OPA REST API クライアント |
| Java | opa-java | OPA REST API クライアント |
| Node.js | @open-policy-agent/opa-wasm | WASM ベースのポリシー評価 |
| Rust | opa-wasm | Rust からの WASM ポリシー評価 |
| .NET | OPA.NET | .NET 用 OPA クライアント |
14.3 統合パターン一覧
| 統合先 | ツール/方法 | 主な用途 |
|---|---|---|
| Kubernetes | OPA Gatekeeper | Admission Control |
| Envoy / Istio | OPA-Envoy Plugin | サービスメッシュ認可 |
| Terraform | Conftest / OPA | インフラポリシー |
| Kafka | OPA REST API | トピックアクセス制御 |
| PostgreSQL | OPA REST API | 行レベルセキュリティ |
| CI/CD | Conftest | コードレビュー、ゲート |
| API Gateway (Kong) | Kong OPA Plugin | API 認可 |
| NGINX | nginx-opa-plugin | リバースプロキシ認可 |
| SSH | PAM OPA module | サーバーアクセス制御 |
| Docker | Conftest | イメージポリシー |
15. まとめとベストプラクティス
15.1 ベストプラクティス一覧
ポリシー設計
| # | ベストプラクティス | 説明 |
|---|---|---|
| 1 | Fail-Closed 設計 | default allow := false を必ず設定し、明示的に許可されたケースのみアクセスを許可する |
| 2 | パッケージの適切な分割 | 機能ごとにパッケージを分割し、再利用性と可読性を高める |
| 3 | ポリシーとデータの分離 | ハードコーディングされた値を避け、データとして外部化する |
| 4 | テストの徹底 | すべてのルールに対してユニットテストを記述し、カバレッジ 80% 以上を目指す |
| 5 | Rego のフォーマット | opa fmt を CI/CD で強制し、一貫したコードスタイルを維持する |
| 6 | Regal Linter の使用 | コード品質を自動的にチェックし、一般的な間違いを防止する |
運用
| # | ベストプラクティス | 説明 |
|---|---|---|
| 7 | バンドルの署名 | 本番環境ではバンドルの署名を有効にし、改ざんを防止する |
| 8 | Decision Log の有効化 | すべてのポリシー判断をログに記録し、監査証跡を確保する |
| 9 | ヘルスチェックの設定 | /health?bundles を使用し、バンドルがロードされた状態でのみトラフィックを受け入れる |
| 10 | 段階的なロールアウト | dryrun / warn モードで新しいポリシーをテストしてから deny に切り替える |
| 11 | リソース制限の設定 | CPU・メモリの requests/limits を適切に設定する |
| 12 | メトリクスの監視 | Prometheus メトリクスを収集し、レイテンシとエラー率を監視する |
パフォーマンス
| # | ベストプラクティス | 説明 |
|---|---|---|
| 13 | インデックスの活用 | セットのメンバーシップチェックやオブジェクトのキーアクセスを活用し、線形走査を避ける |
| 14 | http.send の最小化 | 外部 HTTP 呼び出しを最小限にし、必要な場合はキャッシュを有効にする |
| 15 | データ構造の最適化 | 頻繁に検索されるデータはオブジェクト (マップ) として構造化する |
| 16 | 部分評価の活用 | 入力の一部が既知の場合、部分評価を使用してパフォーマンスを向上させる |
15.2 アンチパターン
| # | アンチパターン | 問題 | 推奨 |
|---|---|---|---|
| 1 | default allow := true | すべてのリクエストがデフォルトで許可される | default allow := false を使用 |
| 2 | ポリシー内でのハードコーディング | ポリシーの変更にコード変更が必要 | データとして外部化する |
| 3 | テストなしのデプロイ | ポリシーのバグが本番に到達する | CI/CD でテストを強制する |
| 4 | 過度な http.send の使用 | レイテンシの増加、外部依存の増加 | データをバンドルに含めるか、キャッシュを使用する |
| 5 | 巨大な単一ポリシーファイル | 可読性・保守性の低下 | パッケージに分割する |
| 6 | Decision Log のマスキング忘れ | 機密情報がログに記録される | マスキングポリシーを設定する |
15.3 導入ロードマップ
フェーズ 1: 評価と PoC (1-2 週間)
- OPA のインストールとローカル環境での試用
- 既存の認可ロジックの洗い出し
- 代表的なユースケースの Rego ポリシー化
- パフォーマンスのベンチマーク
フェーズ 2: 開発環境での導入 (2-4 週間)
- サイドカーパターンでの OPA デプロイ
- バンドル配布インフラの構築
- CI/CD パイプラインでのポリシーテストの導入
- 開発チームへのトレーニング
フェーズ 3: ステージング環境での検証 (2-4 週間)
dryrunモードでのポリシー適用- Decision Log の分析と偽陽性/偽陰性の確認
- パフォーマンスの検証とチューニング
- 監視ダッシュボードの整備
フェーズ 4: 本番環境での展開 (2-4 週間)
- 段階的なトラフィック切り替え (カナリアリリース)
denyモードへの切り替え- アラートの設定
- 運用ドキュメントの整備
フェーズ 5: 拡張と最適化 (継続的)
- 新しいユースケースへの適用拡大
- ポリシーの継続的な改善
- Gatekeeper による Kubernetes ポリシーの追加
- Conftest による CI/CD ポリシーゲートの追加
15.4 OPA の将来展望
OPA は活発に開発が続けられており、以下の領域で進化が期待される。
- Rego v1: Rego 言語の改良 (将来の破壊的変更なし)
- WASM の成熟: ブラウザ・エッジでのポリシー評価の普及
- IR (Intermediate Representation): より効率的なポリシー評価エンジン
- External Data Provider: Gatekeeper の外部データ連携の強化
- Policy Composition: 複数のポリシーの組み合わせ機能の改善
- Cloud Native 統合の深化: 新しいクラウドサービスやプラットフォームとの統合
15.5 結論
Open Policy Agent は、クラウドネイティブ時代における「Policy as Code」の標準的なソリューションとして確固たる地位を築いている。汎用ポリシーエンジンとしての柔軟性、Rego 言語の表現力、そして CNCF 卒業プロジェクトとしてのエコシステムの広がりは、OPA を組織のポリシー管理基盤として採用するための強力な根拠となる。
重要なのは、OPA はあくまで「判断エンジン」であり、施行は各システムが行うという設計哲学である。この分離により、OPA は特定のドメインに縛られることなく、Kubernetes、マイクロサービス、CI/CD、インフラストラクチャなど、あらゆるレイヤーで一貫したポリシー管理を実現する。
本記事で紹介した設定例やパターンを参考に、段階的に OPA を導入し、組織全体のセキュリティとコンプライアンスを向上させていただきたい。
参考リソース: