Casbin で認可ポリシーを保存する1番簡単な選択肢は CSV ファイルだけど,アダプターを使うと認可ポリシーをデータベースで管理できる👌今回は PyCasbin で,ドキュメントに載っている DynamoDB Adapter (python-dycasbin) を使って,Casbin 認可ポリシーを Amazon DynamoDB に保存してみた.試したログをまとめておく❗️
また LoadPolicy()
や SavePolicy()
など,アダプターインタフェースを実装すればカスタムアダプターも実装できる👀
👾 requirements.txt
今回は PyCasbin と python-dycasbin の最新バージョンを使う👌
casbin==1.36.2 python-dycasbin==0.4.1
👾 model.conf
そして,Casbin Model はドキュメントにも載っている ACL (Access Control List) をそのまま使う👌
[request_definition] r = sub, obj, act [policy_definition] p = sub, obj, act [policy_effect] e = some(where (p.eft == allow)) [matchers] m = r.sub == p.sub && r.obj == p.obj && r.act == p.act
👾 add-policy.py
まず,python-dycasbin を使って Amazon DynamoDB テーブルに Casbin 認可ポリシーを登録する.今回はサンプルとして Amazon S3 を参考に s3:PutObject
アクションを制御することにした.
- sub (
kakakakakku
) は obj1 (arn:aws:s3:::sample-bucket-1/*
) に対して act (s3:PutObject
) できる - sub (
kakakakakku
) は obj2 (arn:aws:s3:::sample-bucket-2/*
) に対して act (s3:PutObject
) できる
また Amazon DynamoDB は LocalStack を使ってローカル環境でアクセスできるようにしておく✅
import casbin from python_dycasbin import adapter adapter = adapter.Adapter(endpoint_url='http://localhost:4566') e = casbin.Enforcer('model.conf', adapter, True) sub = 'kakakakakku' obj1 = 'arn:aws:s3:::sample-bucket-1/*' obj2 = 'arn:aws:s3:::sample-bucket-2/*' act = 's3:PutObject' e.add_policy(sub, obj1, act) e.add_policy(sub, obj2, act)
add-policy.py
を実行すると,Adapter 経由で自動的に Amazon DynamoDB テーブル(デフォルトだと casbin_rule
)が構築される.Adapter クラスの初期化 (__init__
) で毎回テーブルを構築しようと試みるところは微妙に感じるし,テーブル設定として WCU / RCU が 10
で固定されているのも厳しい気がした😇オンデマンドキャパシティを選択するオプションがあっても良さそう.
登録した Casbin 認可ポリシーは Amazon DynamoDB テーブルでは以下のように登録されていた.
$ awslocal dynamodb scan --table-name casbin_rule { "Items": [ { "ptype": { "S": "p" }, "v0": { "S": "kakakakakku" }, "v1": { "S": "arn:aws:s3:::sample-bucket-1/*" }, "id": { "S": "bf571b8e16cef131aeac79c15a800421" }, "v2": { "S": "s3:PutObject" } }, { "ptype": { "S": "p" }, "v0": { "S": "kakakakakku" }, "v1": { "S": "arn:aws:s3:::sample-bucket-2/*" }, "id": { "S": "46808bf495552ff34293bdb9f8f0a9b2" }, "v2": { "S": "s3:PutObject" } } ], "Count": 2, "ScannedCount": 2, "ConsumedCapacity": null }
登録されたデータ(アイテム)は LocalStack Resource Browser でも確認できた👌
👾 enforce.py
今度は arn:aws:s3:::sample-bucket-1/*
と arn:aws:s3:::sample-bucket-2/*
と arn:aws:s3:::sample-bucket-3/*
に対する s3:PutObject
アクションの許可を確認する.
import casbin from python_dycasbin import adapter adapter = adapter.Adapter(endpoint_url='http://localhost:4566') e = casbin.Enforcer('model.conf', adapter, True) sub = 'kakakakakku' obj1 = 'arn:aws:s3:::sample-bucket-1/*' obj2 = 'arn:aws:s3:::sample-bucket-2/*' obj3 = 'arn:aws:s3:::sample-bucket-3/*' act = 's3:PutObject' e.enforce(sub, obj1, act) e.enforce(sub, obj2, act) e.enforce(sub, obj3, act)
期待通りに True, True, False
と認可判断ができていた❗️
2024-06-21 19:00:00,000 Request: kakakakakku, arn:aws:s3:::sample-bucket-1/*, s3:PutObject ---> True 2024-06-21 19:00:00,000 Request: kakakakakku, arn:aws:s3:::sample-bucket-2/*, s3:PutObject ---> True 2024-06-21 19:00:00,000 Request: kakakakakku, arn:aws:s3:::sample-bucket-3/*, s3:PutObject ---> False
ちなみに python-dycasbin の実装を読むと,Amazon DynamoDB テーブルに対して Scan を実行していて気になる💨Amazon DynamoDB テーブルに GSI (Global Secondary Index) を追加して,Scan ではなく Query を使うように変更したプルリクエストも出ているようだった.
👾 remove-policy.py
最後は arn:aws:s3:::sample-bucket-2/*
に対する Casbin 認可ポリシーを削除してみる.
import casbin from python_dycasbin import adapter adapter = adapter.Adapter(endpoint_url='http://localhost:4566') e = casbin.Enforcer('model.conf', adapter, True) sub = 'kakakakakku' obj2 = 'arn:aws:s3:::sample-bucket-2/*' act = 's3:PutObject' e.remove_policy(sub, obj2, act)
もう一度 s3:PutObject
アクションの許可を確認したところ,期待通りに True, False, False
と認可判断ができていた❗️
2024-06-21 19:00:00,000 Request: kakakakakku, arn:aws:s3:::sample-bucket-1/*, s3:PutObject ---> True 2024-06-21 19:00:00,000 Request: kakakakakku, arn:aws:s3:::sample-bucket-2/*, s3:PutObject ---> False 2024-06-21 19:00:00,000 Request: kakakakakku, arn:aws:s3:::sample-bucket-3/*, s3:PutObject ---> False
Amazon DynamoDB テーブルのデータ(アイテム)も削除されていた👌
$ awslocal dynamodb scan --table-name casbin_rule { "Items": [ { "ptype": { "S": "p" }, "v0": { "S": "kakakakakku" }, "v1": { "S": "arn:aws:s3:::sample-bucket-1/*" }, "id": { "S": "bf571b8e16cef131aeac79c15a800421" }, "v2": { "S": "s3:PutObject" } } ], "Count": 1, "ScannedCount": 1, "ConsumedCapacity": null }