AuthZEN Interopをcasbinとogenで実装する
はじめに
AuthZEN WGはOpenID Foundationにてアクセス制御のためのプロトコル標準化を目指すために2023年10月に発足されたワーキンググループです。2024年9月にAuthorization API v1.0がimplementer's draftとして発行されており、AuthZEN Interopにて相互運用性を確認するためのテストベッドが提供されています。今回は、AuthZEN Interopで例示されているToDoアプリケーションのアクセス制御を実現するためにcasbinとogenを使ってAuthorization APIを実装しました。
こちらでOPA実装した例が紹介されていますが、私はcasbinを好んで使うので実装の素振りをしたかったのと、ogenを使ったAPI実装を試したいと思いやってみました。
Authorization API v1.0
モデルとインターフェースの概要
Authorization APIの仕様では、サブジェクト、リソース、アクションの4つのモデルが定義されています。サブジェクトとリソースはそれぞれ、認可判断の対象となるアクセス主体(例えばユーザー)とアクセス対象(例えばユーザー情報やToDo情報など)を表します。ドラフトではこれら2つのモデルをtype
とid
、properties
で表現します。例えば、以下のようなサブジェクトの例がドラフトにて挙げられています。
{
"type": "user",
"id": "alice@acmecorp.com",
"properties": {
"department": "Sales",
"ip_address": "172.217.22.14",
"device_id": "8:65:ee:17:7e:0b"
}
}
また、リソースの例として以下のようなものが挙げられています。
{
"type": "book",
"id": "123",
"properties": {
"library_record":{
"title": "AuthZEN in Action",
"isbn": "978-0593383322"
}
}
}
アクションはname
とproperties
で表現されます。例えば、以下のようなアクションの例がドラフトにて挙げられています。
{
"name": "can_read"
}
また、共通的なアクション表現としてcan_access
やcan_read
、can_update
、can_update
といったアクション名が定義されています。
上記のモデル定義に加えて、アクセス判断のコンテクストを含むことができるようにリクエストパラメータとして定義されています。これらのモデルから、アクセス判断を依頼するリクエストボディは以下のように表すことができます。
{
"subject": {
"type": "user",
"id": "alice@acmecorp.com"
},
"resource": {
"type": "account",
"id": "123"
},
"action": {
"name": "can_read",
"properties": {
"method": "GET"
}
},
"context": {
"time": "1985-10-26T01:22-07:00"
}
}
ogenによるAPIサーバーのコード生成
上記のモデル定義およびレスポンスデータ定義に基づいてOpenAPIスキーマを書き起こしました。
この定義から以下のコマンドの実行によりogenのサーバーインターフェースを生成できます。
ogen -package server -target server -clean ./openapi.yml
ハンドラーインターフェースとしてEvaluateAccessメソッドができているので、これを実装することでAuthorization APIのサーバーができあがります。関数に渡される引数の型EvaluateAccessReq
は上述したモデル定義がGoの構造体として定義されているので任意のアクセス制御ライブラリも実装しやすくなります。
type Handler interface {
// EvaluateAccess implements evaluateAccess operation.
//
// Evaluate a user based on their profile.
//
// POST /access/v1/evaluation
EvaluateAccess(ctx context.Context, req *EvaluateAccessReq) (*EvaluateAccessOK, error)
}
CasbinによるAuthZEN Interopの実装
それではAuthZEN InteropのサンプルユースケースであるToDoアプリケーションのアクセス制御をCasbinで実装してみます。このサンプルアプリケーションでは、以下の4つのロールが定義されています。
-
viewer
: ToDoリストを閲覧することができるロール。また、各ユーザー情報も閲覧可能。 -
editor
:viewer
の権限に加えて、ToDoの作成と、自身がオーナーのToDoの編集権限を持つ。 -
admin
:editor
の権限に加えて、任意のToDoの削除権限を持つ。 -
evil_genius
:editor
の権限に加えて、任意のToDoの編集権限を持つ。
これらのロールは別途定義されている5人のユーザーに割り当てられています。このようなユースケースに対して、以下のようなcasbinモデルとポリシーリストを定義しました。
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act, rule
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub.ID,p.sub) && regexMatch(r.obj.ID, p.obj) && r.act == p.act && eval(p.rule)
p,viewer,user::*,can_read_user,true
p,viewer,todo::*,can_read_todo,(r.obj.OwnerID == r.sub.ID)
p,viewer,todo::*,can_read_todos,true
p,editor,todo::*,can_create_todo,true
p,editor,todo::*,can_update_todo,r.obj.OwnerID == r.sub.ID
p,editor,todo::*,can_delete_todo,r.obj.OwnerID == r.sub.ID
p,admin,todo::*,can_delete_todo,true
p,evil_genius,todo::*,can_update_todo,true
g,editor,viewer,,
g,admin,editor,,
g,evil_genius,editor,,
g,rick@the-citadel.com,admin,,
g,rick@the-citadel.com,evil_genius,,
g,beth@the-smiths.com,viewer,,
g,morty@the-citadel.com,editor,,
g,summer@the-smiths.com,editor,,
g,jerry@the-smiths.com,viewer,,
Casbinではeval
によってポリシーごとに条件を指定することができるので、Authorization APIのproperties
やcontext
に含まれる情報を利用したABACもこれで表現することができます。また、階層ロールの表現も可能なため、「editor
はviewer
の権限に加えて〜」のような状況も簡単に表現できます。
ただし、casbinで構造体を扱うのは内部実装としてリフレクションを利用しているため1.1倍~1.5倍程度のパフォーマンス低下が示唆されており、本番環境で利用する際には注意が必要です。パフォーマンスロスを最小限にするためには、subやobjをstring型に制約したり、contextを絞り込んでrequest_definition
を専用に定義しておくなど、柔軟性とのトレードオフを考慮してチューニングすることが必要です。
AuthZEN Interopテストの実行
AuthZEN InteropのGitHubリポジトリにはToDoアプリケーションのユースケースで定義されたアクセス判断を検証するテストケースも格納されているので、これを使って相互運用性を検証していきます。
まず、上述したogenから実装したAPIサーバーを以下のように起動します。
go run ./cmd/casbin/
2025/01/02 19:24:34 INFO Server started at http://localhost:8080
次に、AuthZEN Interopのリポジトリをクローンしてテストを実行します。今回実行するのはdecisions-1.0-implementers-draft.json
というファイルのAuthorization API v1.0 implementer's draftに対応したテストケースです。
git clone https://github.com/openid/authzen.git
cd interop/authzen-todo-backend
yarn install
yarn test http://localhost:8080
無事に全てテストがパスしました。
PASS REQ: {"subject":{"type":"user","id":"CiRmZDA2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"},"action":{"name":"can_read_user"},"resource":{"type":"user","id":"beth@the-smiths.com"}}
...(中略)...
PASS REQ: {"subject":{"type":"user","id":"CiRmZDQ2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"},"action":{"name":"can_delete_todo"},"resource":{"type":"todo","id":"240d0db-8ff0-41ec-98b2-34a096273b95","properties":{"ownerID":"jerry@the-smiths.com"}}}
おわりに
AuthZEN InteropのToDoアプリケーションのアクセス制御をCasbinで実装し、ogenを使ってAuthorization APIのドラフト仕様に準拠したサーバーを実装しました。CasbinはABACをサポートしているため、Authorization APIのモデル定義に基づいて柔軟なアクセス制御を実現することができます。今回は簡単なユースケースだったのもあり労せず表現することができました。ogenを使ったAPIサーバーのコード生成も使用感がよく、標準仕様から型定義を作って実装する体験もスムーズで良いので今後も使っていきたいです。
Discussion