はじめに
TIG真野です。認証認可連載の2本目です。
認証認可がテーマの中で、アクセス制御と聞くとちょっと外れているかもと思いましたが、Casbinについて紹介します。
トップページにも An authorization library that supports access control models like ACL...
とあり、アクセス制御を支援する認可ライブラリだよって書いているので、OKと判断しました。
Casbinについて
ACL、RBAC、ABACなどの様々なモデルでアクセス制御を行えるライブラリです。
私が最初に存在を知ったのは、avelino/awesome-go に載っていたことからだったので、てっきりGo言語のみのライブラリかと思っていました。
実際はドキュメントを見ると、複数の言語をサポートしています。Go以外にも、Java, Node.js, PHP, Python, .NET, C#, C++, Rust, Delphi, Lua, Dart, Elixirに対応しているとのこと(言語によっては一部の機能が使えないなどあるようです)。
ドキュメントに書いてることがシンプルだったのでそのまま転載、抜粋します。
Casbinが行うこと:
{subject, object, action}
の形式や独自定義のカスタマイズされた形式のポリシーを適用します- アクセス制御モデルとそのポリシーの保存をハンドリングします
- ロール・ユーザー間のマッピングとロール・ロール間のマッピング(RBACのロール階層管理
- root や administrator のようなスーパーユーザのサポート
- ルールのマッチングをサポートする複数の組み込み演算子もサポートします。 例えば、 keyMatch はリソース キー
/foo/bar
をパターン/foo*
のマッピング
Casbinが行わないこと:
- 認証 (ログイン時の ユーザー名 と パスワード の検証)
- ユーザーまたはロールのリスト管理。 プロジェクトがこれらのエンティティを管理する方が利便性が高いと考えています。 Casbinはパスワードを保管しない
仕組みとしては、 PERM メタモデル (Policy, Effect, Request, Matchers) にもとづいて動作するとのこと。
ACL、RBAC、ABACについて
それぞれ用語だけ簡単に触れます。
- ACL(Access Controll List)
- アクセス制御
- RBAC(Role Based Access Control)
- ロールベースアクセス制御
- ABAC(Attribute Based Access Control)
- 属性ベースアクセス制御
アクセス制御について詳しい解説は、次のようなサイトを見ると良いと思います。
- https://kenfdev.hateblo.jp/entry/2020/01/13/115032
- https://ja.wikipedia.org/wiki/%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%E5%88%B6%E5%BE%A1%E3%83%AA%E3%82%B9%E3%83%88
- https://www.cloudflare.com/ja-jp/learning/access-management/role-based-access-control-rbac/
- https://www.okta.com/jp/identity-101/role-based-access-control-vs-attribute-based-access-control/
PERM メタモデル について
Casbin以外で聞いたことが無いですが(一般用語でしたらすいません)、Policy, Effect, Request, Matchersの略です。
ファイルシステムのACLのイメージ図です。ポリシー定義がそのファイルの権限を誰が持っているかのリストです。モデル定義はそれをもとにどのように動作させるかを示しています。
図だと、以下を示しています。
- aliceはdata1を読み取りOK
- bobはdata2を書き込みOK
sub, obj, actは だれ
が、何
を、どうする
に置き換えるとイメージしやすいと思います。
触ってみる(Go)
GoでどのようにCasbinを動かすのか、触ってみます。
最初にPolicyを定義します。実用的にはPostgreSQL/MySQLといったDBやAmazon S3などに配備すると思います。そういったAdaptorも用意されています。今回は簡易的にCSVファイルを用います。
例としてRBACをイメージしています。
p, admin, file1, read |
見たまんまですが、adminロールを持っている人はfile1~file3に対して権限があり、Aさんのみadminです。
また、Aさんはfile4, Bさんはfile5に権限を個別に持っています。
Goのコードとしては、モデルのロード、ポリシーのロードを行い生成する casbin.NewEnforcer()
がメインどころです。このインスタンスを生成できるとあとは Enforce()
で判定可能です。
package main |
これを動かすと、次のように想定通りの結果を得られます。
[Aさん file1 read]: true |
CasbinのAPIの使い方で言えば、判定するための実装そのものより、モデルのmatcherの書き方や、これらの定義をどのようにロードさせたり、変更があった場合に追随させるかといったところの方が難しいと思います。
matcherの文法:
- https://casbin.io/ja/docs/function にかかれている通り、組み込み関数が使えます。
- ワイルドカード指定などもこれで対応できます
adaptor:
- https://casbin.io/docs/adapters に記載されている通り、複数のデータソースに対応しています。地味に
Ent
やsqlx
といったO/Rマッパーも対応していて細かいですAutoSave
ですが、enforcerはUpdatePolicy()
で動的に定義を変更することが可能なため、それを自動で保存する機能です- 例えば、何か新しいファイルやレコードが追加された時に権限を更新→自動で永続化先まで反映してくれるといった具合です
HTTPサーバの利用できるAPIをアクセス制御する
さきほどの例だとあまりイメージが付きにくいと思うので、Web APIの URL+Method でアクセス制限する例を実装していみます。
ここでは説明のためスクラッチで書いていますが、Echo, Gin、Chiなどすでにミドルウェアで準備されています。
今回は go-chi を用いて実装します。それっぽい例を探すのが大変だっため、_examples/rest を流用しました。オリジナルのコードはそちらを参照ください。
まずはモデル定義です。
[request_definition] |
最後のmachersだけ、条件が増えています。keyMatch
はワイルドカードを許容する関数です。例えば、 /articles/*
で /articles/1234
とか、 /articles/1234/comments/5678
などを許容したいので利用しています。詳細はこちらを参照ください。
今回はワイルドカードで許容できるようにしたいので、 act
側はORで繋いでいます。 keyMatch
だと GET, POST, DELETE などを *
で許容できなかったのでこの書き方をしています。
ポリシーは今回もCSVファイルで定義します。
p, guest, /, * |
全てロールで、guest < member < owner < admin の順番で権限が強くなるイメージです。
次に、Casbinの判定をchiのミドルウェアに設定する実装イメージを書いてみました。
func main() { |
続いてミドルウェア本体です。かなり端折って書いています。コードコメントにも書いていますが、本来はログイン後にセッションか何かにユーザーIDを載せ、紐づくユーザーロールをDBから取得するようなイメージでいます(あるいはJSTトークンにロールを載せてもらうかなど)。取得したロールも、本来は http.Requestの context.Context
に載せて引き回した方が自然かもしれませんが、簡易的にリクエストヘッダーから取っています。
main関数側で設定した casbin.Enforcer
で、リクエストを検証して、OKであれば後続へ。NGであれば403を返します。
func CasbinAuthorizer(e *casbin.Enforcer) func(next http.Handler) http.Handler { |
実際にcurlで試してみます。ロールをリクエストヘッダで切り替えていますが、本来はこれだと意味がないので、ログイン処理などを追加して、サーバ側でロールを判定するように改修し、クライアントがロールを指定できなくする必要があります。
# guest ロール |
ロールに権限があれば、操作が成功していることが分かります。
もっと細かい制御をするためには
上記の実装例だと、例えばある記事の削除は、作成したユーザー自身
も削除できるようにしたい、といった要望には対応できません(ユーザー単位でロールを作ればもちろん可能ですが、もはやそれはロールの意味が無いですよね)。
Casbinでどう実現するかですが、今のURLの構造でがんばるのであれば、articlesが追加されるごとにユーザーIDとマッピングさせた policy.csv のレコードに相当するデータを釣っていくことです。Casbinの機能であれば、Priority Modelで多段の権限を判定できるので、参考になるかもしれません。
また、この実装例のURL階層だと、ワイルドカードが使いにくいので、 /users/<user_id>/articles/<article id>
といった階層を工夫すると policy
のメンテナンスをシンプルに抑えることもできるかと思います。
まとめ
気になっていたCasbinの触りについてまとめました。ドキュメントサイトも新しくなっていますし、採用事例もちょくちょく聞きますし、プロダクション運用にも耐えうる品質だと思っています。
すこし気になっているのは、2022.10.3時点だと v3.0.0-beta.7
がタグ付けされています。もうすぐv3系がリリースされる予感があり、APIの互換性がすこし崩れるのかも? と推測しています(とは言え、ドキュメントページも整理されていますし、すでにv1→v2で移行しているので、根本からそこまで変わらないのでは? と思っていますが)。このあたりは新規に採用する時に留意したほうが良いと思います。
アクセス制限は自前で作り込むと面倒な割に、ユーザーからは当たり前品質の扱いをされがちだと思うので、こういった既存プロダクトにうまくのっかれると良いと思います。