ポリシーエンジン比較 — AVP / OpenFGA / OPA / SpiceDB / Cerbos をマルチプロダクトで使うとどうなるか

主要なポリシーエンジン5つを同じマルチプロダクトのユースケースで実装し、記述方法・モデル設計・運用特性を具体的に比較する。

はじめに

ポリシーエンジンは種類が多く、ドキュメントを読んだだけでは違いが実感しにくいです。この記事では、同じマルチプロダクトのユースケースを 5 つのエンジンで実装し、具体的なコードで比較します。

共通のユースケース

以下の要件を各エンジンで実装します。

プロダクト

ユーザー

Alice: admin グループ、org-abc 所属
Bob:   lawyer グループ、org-abc 所属
Carol: viewer グループ、org-xyz 所属

リソース

case-001: org-abc の案件、担当者は Bob
doc-001:  org-abc のドキュメント、作成者は Alice、ステータスは draft
doc-002:  org-abc のドキュメント、作成者は Bob、ステータスは published

認可ルール

#ルール
R1admin は全リソースを閲覧・編集できる
R2lawyer は自分が担当する案件を閲覧・編集できる
R3viewer は published のドキュメントだけ閲覧できる
R4ドキュメントの作成者は自分のドキュメントを編集できる
R5他の org のリソースにはアクセスできない

期待結果

ユーザーリソースviewedit
Alice (admin)case-001
Alice (admin)doc-001 (draft)
Bob (lawyer)case-001 (担当)
Bob (lawyer)doc-001 (draft)
Bob (lawyer)doc-002 (published, 作成者)
Carol (viewer, 別org)case-001
Carol (viewer)doc-002 (published)

1. Amazon Verified Permissions (AVP) / Cedar

特徴

スキーマ定義

Cedar はスキーマ(型定義)を先に書く必要があります。

{
  "Common": {
    "entityTypes": {
      "User": {
        "shape": {
          "type": "Record",
          "attributes": {
            "org": { "type": "String" },
            "groups": { "type": "Set", "element": { "type": "String" } }
          }
        }
      }
    },
    "actions": {}
  },
  "Cases": {
    "entityTypes": {
      "Case": {
        "shape": {
          "type": "Record",
          "attributes": {
            "org": { "type": "String" },
            "assignedTo": { "type": "String" }
          }
        }
      }
    },
    "actions": {
      "view": { "appliesTo": { "principalTypes": ["Common::User"], "resourceTypes": ["Cases::Case"] } },
      "edit": { "appliesTo": { "principalTypes": ["Common::User"], "resourceTypes": ["Cases::Case"] } }
    }
  },
  "Docs": {
    "entityTypes": {
      "Document": {
        "shape": {
          "type": "Record",
          "attributes": {
            "org": { "type": "String" },
            "author": { "type": "String" },
            "status": { "type": "String" }
          }
        }
      }
    },
    "actions": {
      "view": { "appliesTo": { "principalTypes": ["Common::User"], "resourceTypes": ["Docs::Document"] } },
      "edit": { "appliesTo": { "principalTypes": ["Common::User"], "resourceTypes": ["Docs::Document"] } }
    }
  }
}

ポリシー

// R1: admin は全リソースを閲覧・編集できる
permit(
  principal,
  action,
  resource
) when {
  principal.groups.contains("admin") &&
  principal.org == resource.org
};

// R2: lawyer は担当案件を閲覧・編集できる
permit(
  principal,
  action,
  resource is Cases::Case
) when {
  principal.groups.contains("lawyer") &&
  principal.org == resource.org &&
  resource.assignedTo == principal
};

// R3: viewer は published ドキュメントを閲覧できる
permit(
  principal,
  action == Docs::Action::"view",
  resource is Docs::Document
) when {
  principal.groups.contains("viewer") &&
  resource.status == "published"
};

// R4: ドキュメントの作成者は編集できる
permit(
  principal,
  action,
  resource is Docs::Document
) when {
  principal.org == resource.org &&
  resource.author == principal
};

// R5: org の不一致はデフォルトで forbid(Cedar は deny-by-default なので明示不要)

API 呼び出し

{
  "principal": { "entityType": "Common::User", "entityId": "bob" },
  "action": { "actionType": "Cases::Action", "actionId": "view" },
  "resource": { "entityType": "Cases::Case", "entityId": "case-001" },
  "entities": [
    {
      "identifier": { "entityType": "Common::User", "entityId": "bob" },
      "attributes": { "org": "org-abc", "groups": ["lawyer"] }
    },
    {
      "identifier": { "entityType": "Cases::Case", "entityId": "case-001" },
      "attributes": { "org": "org-abc", "assignedTo": "bob" }
    }
  ]
}

所感


2. OpenFGA

特徴

モデル定義

model
  schema 1.1

type user

type org
  relations
    define member: [user]
    define admin: [user]

type case
  relations
    define org: [org]
    define assignee: [user]
    define can_view: assignee or admin from org
    define can_edit: assignee or admin from org

type document
  relations
    define org: [org]
    define author: [user]
    define can_view: author or admin from org or viewer_if_published
    define can_edit: author or admin from org
    define viewer_if_published: [user with published_condition]

condition published_condition(status: string) {
  status == "published"
}

関係性の登録(Tuples)

// 組織メンバー
{ "user": "user:alice", "relation": "admin", "object": "org:org-abc" }
{ "user": "user:bob",   "relation": "member", "object": "org:org-abc" }
{ "user": "user:carol", "relation": "member", "object": "org:org-xyz" }

// 案件
{ "user": "org:org-abc", "relation": "org",      "object": "case:case-001" }
{ "user": "user:bob",    "relation": "assignee",  "object": "case:case-001" }

// ドキュメント
{ "user": "org:org-abc", "relation": "org",    "object": "document:doc-001" }
{ "user": "user:alice",  "relation": "author", "object": "document:doc-001" }
{ "user": "org:org-abc", "relation": "org",    "object": "document:doc-002" }
{ "user": "user:bob",    "relation": "author", "object": "document:doc-002" }

チェック

// Bob は case-001 を view できる?
{ "user": "user:bob", "relation": "can_view", "object": "case:case-001" }
→ allowed (assignee だから)

// Carol は doc-002 を view できる?
{ "user": "user:carol", "relation": "can_view", "object": "document:doc-002",
  "context": { "status": "published" } }
→ allowed (published だから)

所感


3. Open Policy Agent (OPA) / Rego

特徴

ポリシー

package authz

import future.keywords.if
import future.keywords.in

default allow := false

# R1: admin は全リソースを閲覧・編集できる(同じ org 内)
allow if {
  "admin" in input.user.groups
  input.user.org == input.resource.org
}

# R2: lawyer は担当案件を閲覧・編集できる
allow if {
  "lawyer" in input.user.groups
  input.resource.type == "case"
  input.resource.assignedTo == input.user.sub
  input.user.org == input.resource.org
}

# R3: viewer は published ドキュメントを閲覧できる
allow if {
  "viewer" in input.user.groups
  input.resource.type == "document"
  input.resource.status == "published"
  input.action == "view"
}

# R4: ドキュメントの作成者は編集できる
allow if {
  input.resource.type == "document"
  input.resource.author == input.user.sub
  input.user.org == input.resource.org
}

入力

{
  "user": {
    "sub": "bob",
    "org": "org-abc",
    "groups": ["lawyer"]
  },
  "action": "view",
  "resource": {
    "type": "case",
    "id": "case-001",
    "org": "org-abc",
    "assignedTo": "bob"
  }
}

クエリ

curl -X POST http://localhost:8181/v1/data/authz/allow \
  -H "Content-Type: application/json" \
  -d '{ "input": { ... } }'

 { "result": true }

所感


4. SpiceDB

特徴

スキーマ

definition user {}

definition org {
  relation admin: user
  relation member: user
}

definition case {
  relation org: org
  relation assignee: user
  permission view = assignee + org->admin
  permission edit = assignee + org->admin
}

definition document {
  relation org: org
  relation author: user
  relation published: user:*

  permission view = author + org->admin + published
  permission edit = author + org->admin
}

関係性の書き込み

// gRPC or HTTP API
case:case-001#org@org:org-abc
case:case-001#assignee@user:bob
org:org-abc#admin@user:alice
org:org-abc#member@user:bob

document:doc-001#org@org:org-abc
document:doc-001#author@user:alice

document:doc-002#org@org:org-abc
document:doc-002#author@user:bob
document:doc-002#published@user:*    // 全ユーザーに公開

チェック

CheckPermission(user:bob, view, case:case-001) → ALLOWED
CheckPermission(user:carol, view, document:doc-002) → ALLOWED (published)
CheckPermission(user:carol, edit, document:doc-002) → DENIED

所感


5. Cerbos

特徴

ポリシー

# cases_policy.yaml
apiVersion: api.cerbos.dev/v1
resourcePolicy:
  resource: "case"
  version: "default"
  rules:
    # R1: admin は全操作可能(同じ org)
    - actions: ["view", "edit"]
      roles: ["admin"]
      condition:
        match:
          expr: request.resource.attr.org == request.principal.attr.org

    # R2: lawyer は担当案件のみ
    - actions: ["view", "edit"]
      roles: ["lawyer"]
      condition:
        match:
          all:
            of:
              - expr: request.resource.attr.org == request.principal.attr.org
              - expr: request.resource.attr.assignedTo == request.principal.id
# documents_policy.yaml
apiVersion: api.cerbos.dev/v1
resourcePolicy:
  resource: "document"
  version: "default"
  rules:
    # R1: admin は全操作可能
    - actions: ["view", "edit"]
      roles: ["admin"]
      condition:
        match:
          expr: request.resource.attr.org == request.principal.attr.org

    # R3: viewer は published のみ閲覧
    - actions: ["view"]
      roles: ["viewer"]
      condition:
        match:
          expr: request.resource.attr.status == "published"

    # R4: 作成者は編集可能
    - actions: ["view", "edit"]
      roles: ["*"]
      condition:
        match:
          all:
            of:
              - expr: request.resource.attr.org == request.principal.attr.org
              - expr: request.resource.attr.author == request.principal.id

API 呼び出し

{
  "principal": {
    "id": "bob",
    "roles": ["lawyer"],
    "attr": { "org": "org-abc" }
  },
  "resource": {
    "kind": "case",
    "id": "case-001",
    "attr": { "org": "org-abc", "assignedTo": "bob" }
  },
  "actions": ["view", "edit"]
}
// レスポンス
{
  "results": {
    "case-001": {
      "actions": {
        "view": "EFFECT_ALLOW",
        "edit": "EFFECT_ALLOW"
      }
    }
  }
}

所感


総合比較

記述量

同じルールを書くのに必要な行数(概算):

スキーマ/モデルポリシー/関係性合計
Cedar (AVP)40行30行70行
OpenFGA25行15行 (tuples)40行
OPA (Rego)0行30行30行
SpiceDB20行10行 (relations)30行
Cerbos0行40行 (YAML)40行

マルチプロダクトでの使いやすさ

要件CedarOpenFGAOPASpiceDBCerbos
プロダクト間のポリシー分離◎ (namespace)○ (type で分離)△ (package で分離)○ (definition で分離)◎ (resource 単位)
新プロダクト追加スキーマ + ポリシー追加モデル + tuple 追加新 package 追加新 definition 追加新 YAML ファイル追加
ビジネス担当者が読める◎ (YAML)
テスト△ (AVP テスト機能)○ (Playground)◎ (内蔵テスト)○ (zed CLI)◎ (テストスイート)
デバッグ◎ (explain)○ (trace)○ (explain)

向いてるユースケース

エンジン最適なケース
Cedar (AVP)AWS にいる。型安全が欲しい。マネージドでやりたい
OpenFGAGoogle Drive のような共有モデル。フォルダ → ファイルの権限継承
OPA認可以外もやりたい(k8s Admission, Terraform policy)。何でも書きたい
SpiceDB大規模な関係性データ。パフォーマンス重視。Zanzibar を忠実にやりたい
Cerbosチームに開発者以外もいる。YAML で管理したい。API 認可に特化

AVP から移行するなら

移行先移行の難易度理由
CerbosABAC 同士。ポリシーを YAML に書き直すだけ。概念モデルが近い
OPARego への書き換えが必要だが、ABAC の発想は同じ
OpenFGAABAC → ReBAC のモデル変換が必要。関係性の設計からやり直し
SpiceDB同上

ABAC で困ってるなら Cerbos か OPA、関係性ベースが必要なら OpenFGA か SpiceDB という判断です。

まとめ

← Back to all posts