Casbinでアクセスコントロール

2021.02.18

What is Casbin?

Casbinとは、ACL/RBAC/ABAC などのアクセス制御モデルをサポートするAuthorizationライブラリです。
Go/Java/Javascript/RustなどからDelphiやElixirまで幅広く対応しています。

Features of Casbin

公式ある説明より主要な機能の特徴。

  • ハイブリッドアクセス制御モデル

Casbin では、アクセス制御モデルは PERM メタモデル (Policy, Effect, Request, Matchers) に基づいて
configファイルで設定できる。
なので、認可の仕組みを変更するのも簡単。

  • 柔軟なポリシーストレージ

メモリやファイルだけでなく、ポリシー設定はMySQL、Postgres、OracleからMongoDB、Redis、
Cassandra、AWS S3などなどいろいろなデータストアがサポートされてる。
詳しくはここを確認。

  • ロールマネージャ

ロールマネージャは、CasbinのRBACロール階層(ユーザ-ロールマッピング)を管理するために使用される。
ロールマネージャは、CasbinのポリシールールやLDAP、Okta、Auth0、Azure ADなどの外部ソースから
ロールデータを取得可能。
Casbinロールマネージャの詳細はここ

Casbin's Model definition

CasbinではPERM-Metaモデル(Policy、Effect、Request、Matcher)でのアクセス制御モデルを採用しており、
モデル定義はconfファイルに記述する。

まずはACL(アクセスコントロールリスト)を例に説明。

  • Request

アクセスする際のリクエストに関する情報。
ACLモデルでは一般的に、
sub(実行者)、obj(対象)、act(アクセス方法)
を使う。

[request_definition]
r = {sub, obj, act}
  • Policy

ポリシーの記述形式を定義するモデル。
例えばこのモデル定義で「data1 (obj) を alice (sub) が read (act) できる」
というPolicyを記述した場合、

p, alice, data1, read

となる。

ポリシーの記述形式は下記。

[policy_definition]
p = {sub, obj, action}
  • Matchers

RequestとPolicyがどうやってマッチするか、それぞれで定義したモデルを参照し、
条件式を使って定義する。
↓のACLモデル定義では、アクセス判定要求の
sub、obj、actの全てが一致するポリシーを合致するものと判定される。

[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act
  • Effect

Requestが複数のポリシーにマッチした場合、どうやって判定させるか定義する。
※Matcherの評価値は、eftフィールドで利用可能

↓のACLモデル定義では、Matcherの条件式で合致するポリシーのうちeftが
allowに設定されたものが1つでもある場合に許可判定となる。

[policy_effect]
e = some(where (p.eft == allow))

また、RBAC(ロールベールのアクセスコントロール)も使用可能。
ロールベースのアクセスコントロールとは、ユーザーなどに対してにロール(管理者などの役割)が
割当てられ、このロールに対して任意の機能に対する実行許可が与えられる。

これら以外にも、属性ベース(ABAC)やRESTfulの
アクセスコントロールについてもサポートしている。

Environment

今回はTypescriptでcasbinを動かしてみる。

  • OS : MacOS 10.15.7
  • node : v14.4.0

nodeはHomebrewでインストール済み。

Setup

まずはTypeScriptとcasbinをモジュールをインストール。

<br />% npm install --save casbin
% npm install --save-dev @types/node 
% npm install --save-dev typescript
# tsc --init しておく

Make Example

まずはcasbinのモデル定義用confファイルをbasic_mode.confという名前で作成する。 Request,Policy,Matchers,Effectの定義をそれぞれ記述。

[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

次にポリシーファイルを用意。
aliceはdata1をreadでき、bobはdata2をwriteできます。

p, alice, data1, read
p, bob, data2, write

サンプルコードを記述。

//example.ts
const casbin = require('casbin');

//リソースへアクセスするユーザー
const sub = 'alice';
//アクセス対象のリソース
const obj = 'data1';
//リソースに対する操作
const act = 'read';


async function execute(){
  const enforcer = await casbin.newEnforcer('basic_model.conf', 'basic_policy.csv');
  const res = await enforcer.enforce(sub, obj, act);
  if (res) {
    // aliceのdata1に対するread操作は許可されている
    console.log("ok");
  } else {
    // リクエストは拒否されている
    console.log("ng");
  }

}

execute();

モデル定義ファイルとポリシーファイルを読み込み、操作が許可されているかどうかチェックする。
プログラムをコンパイルして実行すると、アクセスが許可されていることがわかる。

% tsc example.ts
% node example.js
ok

ロールモデルも試してみる。
まずはrole_model.confを定義。

[request_definition]
r = sub, obj, act

[policy_definition]
p = sub, obj, act

[role_definition]
g = _, _

[policy_effect]
e = some(where (p.eft == allow))

[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act

role_definitionはRBAC形式に対応するための記述で、ロールの記述形式を定義している。

サンプルを記述。
taroに対してロール割当をおこなったあと、権限のチェックをする。

async function execute2() {
    const enforcer = await casbin.newEnforcer('role_model.conf', 'basic_policy.csv');

    //ロール割り当て
    await enforcer.addNamedGroupingPolicy("g", "taro", "role1");

    //ポリシー割当
    await enforcer.addNamedPolicy("p", "role1", "data1", "read");


    const res = await enforcer.enforce("taro", "data1", "read");
    if (res) {
      console.log("role : ok");
    } else {
      console.log("role : ng");
    }

    //ユーザーのロール取得
    const roles = await enforcer.getRolesForUser("taro");
    console.log(roles);

}

execute2();

実行結果は以下。

% node example.js
role : ok
[ 'role1' ]

Summary

手軽にACLやRBAC形式のアクセス制御ができた。
ちなみに、fastify用のプラグインもあり、
簡単に組み込むこともできる。

Refarences