casbin-許可權管理

wang_yb發表於2018-11-20

概要

許可權管理幾乎是每個系統或者服務都會直接或者間接涉及的部分. 許可權管理保障了資源(大部分時候就是資料)的安全, 許可權管理一般都是和業務強關聯, 每當有新的業務或者業務變化時, 不能將精力完全放在業務實現上, 許可權的調整往往耗費大量的精力.

其實, 許可權的本質沒有那麼複雜, 只是對訪問的控制而已, 有一套完善的訪問控制介面, 再加上簡單的許可權模型. 許可權模型之所以能夠簡單, 就是因為許可權管理本身並不複雜, 只是在和具體業務結合時, 出現了各種各樣的訪問控制場景, 才顯得複雜.

PERM 模型

PERM(Policy, Effect, Request, Matchers)模型很簡單, 但是反映了許可權的本質 – 訪問控制

  • Policy: 定義許可權的規則
  • Effect: 定義組合了多個 Policy 之後的結果, allow/deny
  • Request: 訪問請求, 也就是誰想操作什麼
  • Matcher: 判斷 Request 是否滿足 Policy

PERM 模型

casbin 許可權庫

casbin 使用了 PERM 模型來表達許可權, 並且提供了簡單直接的 API.

核心概念

model file

用來定義具體的許可權模型, 目前支援的模型基本覆蓋了常見的所有場景:

  1. ACL
  2. ACL with superuser
  3. ACL without users
  4. ACL without resources
  5. RBAC
  6. RBAC with resource roles
  7. RBAC with domains/tenants
  8. ABAC
  9. ……

model file 定義語法

casbin 是基於 PERM 的, 所有 model file 中主要就是定義 PERM 4 個部分.

  1. Request definition

    [request_definition]
    r = sub, obj, act

    分別表示 request 中的

    • accessing entity (Subject)
    • accessed resource (Object)
    • the access method (Action)
  2. Policy definition

    [policy_definition]
    p = sub, obj, act
    p2 = sub, act

    定義的每一行稱為 policy rule, p, p2 是 policy rule 的名字. p2 定義的是 sub 所有的資源都能執行 act

  3. Policy effect

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

    上面表示有任意一條 policy rule 滿足, 則最終結果為 allow

  4. Matchers

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

    定義了 request 和 policy 匹配的方式, p.eft 是 allow 還是 deny, 就是基於此來決定的

  5. Role

    [role_definition]
    g = _, _
    g2 = _, _
    g3 = _, _, _

    g, g2, g3 表示不同的 RBAC 體系, _, _ 表示使用者和角色 _, _, _ 表示使用者, 角色, 域(也就是租戶)

policy file

定義具體的策略, 許可權的檢查就是基於定義的 model file 和 policy file 來完成的.

相對於 model file 定義規則, policy file 中定義的就是具體的內容.

RBAC 示例

定義 model file

[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

定義 policy file

p, superAdmin, project, read
p, superAdmin, project, write
p, admin, project, read
p, admin, project, write
p, admin, asse, read
p, admin, asse, write
p, zhuangjia, project, write
p, zhuangjia, asse, write
p, shangshang, project, read
p, shangshang, asse, read

g, quyuan, admin
g, wenyin, zhuangjia

測試程式碼

package rbac

import (
  "fmt"
  "log"

  "github.com/casbin/casbin"
)

func TestRBAC() {
  e := casbin.NewEnforcer("rbac/rbac.conf", "rbac/rbac.csv")

  fmt.Printf("RBAC test start\n") // output for debug

  // superAdmin
  if e.Enforce("superAdmin", "project", "read") {
    log.Println("superAdmin can read project")
  } else {
    log.Fatal("ERROR: superAdmin can not read project")
  }

  if e.Enforce("superAdmin", "project", "write") {
    log.Println("superAdmin can write project")
  } else {
    log.Fatal("ERROR: superAdmin can not write project")
  }

  // admin
  if e.Enforce("quyuan", "project", "read") {
    log.Println("quyuan can read project")
  } else {
    log.Fatal("ERROR: quyuan can not read project")
  }

  if e.Enforce("quyuan", "project", "write") {
    log.Println("quyuan can write project")
  } else {
    log.Fatal("ERROR: quyuan can not write project")
  }

  if e.Enforce("quyuan", "asse", "read") {
    log.Println("quyuan can read asse")
  } else {
    log.Fatal("ERROR: quyuan can not read asse")
  }

  if e.Enforce("quyuan", "asse", "write") {
    log.Println("quyuan can write asse")
  } else {
    log.Fatal("ERROR: quyuan can not write asse")
  }

  // zhuangjia
  if e.Enforce("wenyin", "project", "read") {
    log.Fatal("ERROR: wenyin can read project")
  } else {
    log.Println("wenyin can not read project")
  }

  if e.Enforce("wenyin", "project", "write") {
    log.Println("wenyin can write project")
  } else {
    log.Fatal("ERROR: wenyin can not write project")
  }

  if e.Enforce("wenyin", "asse", "read") {
    log.Fatal("ERROR: wenyin can read asse")
  } else {
    log.Println("wenyin can not read asse")
  }

  if e.Enforce("wenyin", "asse", "write") {
    log.Println("wenyin can write asse")
  } else {
    log.Fatal("ERROR: wenyin can not write asse")
  }

  // shangshang
  if e.Enforce("shangshang", "project", "read") {
    log.Println("shangshang can read project")
  } else {
    log.Fatal("ERROR: shangshang can not read project")
  }

  if e.Enforce("shangshang", "project", "write") {
    log.Fatal("ERROR: shangshang can write project")
  } else {
    log.Println("shangshang can not write project")
  }

  if e.Enforce("shangshang", "asse", "read") {
    log.Println("shangshang can read asse")
  } else {
    log.Fatal("ERROR: shangshang can not read asse")
  }

  if e.Enforce("shangshang", "asse", "write") {
    log.Fatal("ERROR: shangshang can write asse")
  } else {
    log.Println("shangshang can not write asse")
  }
}

多租戶示例

定義 model file

[request_definition]
r = sub, dom, obj, act

[policy_definition]
p = sub, dom, obj, act

[role_definition]
g = _, _, _

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

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

定義 policy file

p, superAdmin, gy, project, read
p, superAdmin, gy, project, write
p, superAdmin, jn, project, read
p, superAdmin, jn, project, write
p, admin, gy, project, read
p, admin, gy, project, write
p, admin, jn, asse, read
p, admin, jn, asse, write
p, zhuangjia, jn, project, write
p, zhuangjia, gy, asse, write

g, quyuan, admin, gy
g, quyuan, admin, jn
g, wenyin, zhuangjia, gy
g, shangshang, zhuangjia, jn

測試程式碼

package tenants

import (
  "fmt"
  "log"

  "github.com/casbin/casbin"
)

// TestTenants test tenants
func TestTenants() {
  e := casbin.NewEnforcer("tenants/tenants.conf", "tenants/tenants.csv")

  fmt.Printf("RBAC TENANTS test start\n") // output for debug

  // superAdmin
  if e.Enforce("superAdmin", "gy", "project", "read") {
    log.Println("superAdmin can read project in gy")
  } else {
    log.Fatal("ERROR: superAdmin can not read project in gy")
  }

  if e.Enforce("superAdmin", "gy", "project", "write") {
    log.Println("superAdmin can write project in gy")
  } else {
    log.Fatal("ERROR: superAdmin can not write project in gy")
  }

  if e.Enforce("superAdmin", "jn", "project", "read") {
    log.Println("superAdmin can read project in jn")
  } else {
    log.Fatal("ERROR: superAdmin can not read project in jn")
  }

  if e.Enforce("superAdmin", "jn", "project", "write") {
    log.Println("superAdmin can write project in jn")
  } else {
    log.Fatal("ERROR: superAdmin can not write project in jn")
  }

  // admin
  if e.Enforce("quyuan", "gy", "project", "read") {
    log.Println("quyuan can read project in gy")
  } else {
    log.Fatal("ERROR: quyuan can not read project in gy")
  }

  if e.Enforce("quyuan", "gy", "project", "write") {
    log.Println("quyuan can write project in gy")
  } else {
    log.Fatal("ERROR: quyuan can not write project in gy")
  }

  if e.Enforce("quyuan", "jn", "project", "read") {
    log.Fatal("ERROR: quyuan can read project in jn")
  } else {
    log.Println("quyuan can not read project in jn")
  }

  if e.Enforce("quyuan", "jn", "project", "write") {
    log.Fatal("ERROR: quyuan can write project in jn")
  } else {
    log.Println("quyuan can not write project in jn")
  }

  if e.Enforce("quyuan", "gy", "asse", "read") {
    log.Fatal("ERROR: quyuan can read asse in gy")
  } else {
    log.Println("quyuan can not read asse in gy")
  }

  if e.Enforce("quyuan", "gy", "asse", "write") {
    log.Fatal("ERROR: quyuan can write asse in gy")
  } else {
    log.Println("quyuan can not write asse in gy")
  }

  if e.Enforce("quyuan", "jn", "asse", "read") {
    log.Println("quyuan can read asse in jn")
  } else {
    log.Fatal("ERROR: quyuan can not read asse in jn")
  }

  if e.Enforce("quyuan", "jn", "asse", "write") {
    log.Println("quyuan can write asse in jn")
  } else {
    log.Fatal("ERROR: quyuan can not write asse in jn")
  }

  // wenyin
  if e.Enforce("wenyin", "gy", "asse", "write") {
    log.Println("wenyin can write asse in gy")
  } else {
    log.Fatal("ERROR: wenyin can not write asse in gy")
  }

  if e.Enforce("wenyin", "jn", "asse", "write") {
    log.Fatal("ERROR: wenyin can write asse in jn")
  } else {
    log.Println("wenyin can not write asse in jn")
  }

  // shangshang
  if e.Enforce("shangshang", "jn", "project", "write") {
    log.Println("shangshang can write project in jn")
  } else {
    log.Fatal("ERROR: shangshang can not write project in jn")
  }

  if e.Enforce("shangshang", "gy", "project", "write") {
    log.Fatal("ERROR: shangshang can write project in gy")
  } else {
    log.Println("shangshang can not write project in gy")
  }
}

總結

casbin 許可權管理庫比較簡單, 易上手, 但是它的功能卻不簡單, 支援了目前主流的所有許可權管理場景. 在使用上, model file 和 poclicy file 的定義也簡單明瞭, 抽象出了許可權管理最本質的東西.

將具體業務中的許可權要求對映到 casbin 中 model file, 就可以藉助 casbin 的 API, 快速的實現許可權管理.

相關文章