Casbin是一個強大的、高效的開源訪問控制框架,其權限管理機制支持多種訪問控制模型。目前這個框架的生態已經發展的愈來愈好了。提供了各類語言的類庫,自定義的權限模型語言,以及模型編輯器。在各類語言中,golang的支持仍是最全的,因此咱們就研究casbin的golang實現。linux
控制訪問模型有哪幾種?咱們須要先來了解下這個。git
這個是Linux中對於資源進行權限管理的訪問模型。Linux中一切資源都是文件,每一個文件均可以設置三種角色的訪問權限(文件建立者,文件建立者所在組,其餘人)。這種訪問模型的缺點很明顯,只能爲一類用戶設置權限,若是這類用戶中有特殊的人,那麼它無能爲力了。github
它的原理是,每一個資源都配置有一個列表,這個列表記錄哪些用戶能夠對這項資源進行CRUD操做。當系統試圖訪問這項資源的時候,會首先檢查這個列表中是否有關於當前用戶的訪問權限,從而肯定這個用戶是否有權限訪問當前資源。linux在UGO以外,也增長了這個功能。golang
setfacl -m user:yejianfeng:rw- ./test
[yejianfeng@ipd-itstool ~]$ getfacl test # file: test # owner: yejianfeng # group: yejianfeng user::rw- user:yejianfeng:rw- group::rw- mask::rw- other::r--
當咱們使用getfacl和setfacl命令的時候咱們就能對某個資源設置增長某我的,某個組的權限列表。操做系統會根據這個權限列表進行判斷,當前用戶是否有權限操做這個資源。數據庫
這個是不少業務系統最通用的權限訪問控制系統。它的特色是在用戶和具體權限之間增長了一個角色。就是先設置一個角色,好比管理員,而後將用戶關聯某個角色中,再將角色設置某個權限。用戶和角色是多對多關係,角色和權限是多對多關係。因此一個用戶是否有某個權限,根據用戶屬於哪些角色,再根據角色是否擁有某個權限來判斷這個用戶是否有某個權限。windows
RBAC的邏輯有更多的變種。api
變種一:角色引入繼承緩存
角色引入了繼承概念,那麼繼承的角色有了上下級或者等級關係。安全
變種二:角色引入了約束restful
角色引入了約束概念。約束概念有兩種,
一種是靜態職責分離:
a、互斥角色:同一個用戶在兩個互斥角色中只能選擇一個
b、基數約束:一個用戶擁有的角色是有限的,一個角色擁有的許可也是有限的
c、先決條件約束:用戶想要得到高級角色,首先必須擁有低級角色
一種是動態職責分離:
能夠動態的約束用戶擁有的角色,如一個用戶能夠擁有兩個角色,可是運行時只能激活一個角色。
變種三:既有角色約束,又有角色繼承
就是前面兩種角色變種的集合。
Attribute-based access control,這種權限驗證模式是用屬性來標記資源權限的。好比k8s中就用到這個權限驗證方法。好比某個資源有pod屬性,有命名空間屬性,那麼我設置的時候能夠這樣設置:
Bob 能夠在命名空間 projectCaribou 中讀取 pod: {"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"user": "bob", "namespace": "projectCaribou", "resource": "pods", "readonly": true}}
這個權限驗證模型的好處就是擴展性好,一旦要增長某種權限,就能夠直接增長某種屬性。
在ACL的訪問控制模式下,有個問題,能給資源增長訪問控制的是誰,這裏就有幾種辦法,好比增長一個super user,這個超級管理員來作統一的操做。還有一種辦法,有某個權限的用戶來負責給其餘用戶分配權限。這個就叫作自主訪問控制。
好比咱們經常使用的windows就是用這麼一種方法。
不少的wiki權限也是這樣的權限管理方式。
強制訪問控制和DAC相反,它不將某些權限下放給用戶,而是在更高維度(好比操做系統)上將全部的用戶設置某些策略,這些策略是須要全部用戶強制執行的。這種訪問控制也是基於某些安全因素考慮。
casbin使用配置文件來設置訪問控制模型。咱們能夠經過casbin的模型編輯器來查看。
它有兩個配置文件,model.conf 和 policy.csv。其中 model.conf 存儲的是咱們的訪問控制模型,policy.csv 存儲的是咱們具體的用戶權限配置。
權限本質上就是最終詢問這麼一個問題「某個用戶,對某個資源,是否能夠進行某種操做」。casbin的使用很是精煉。基本上就生成一個結構,Enforcer,構造這個結構的時候加載 model.conf 和 policy.csv。使用示例以下:
import "github.com/casbin/casbin/v2" e, err := casbin.NewEnforcer("path/to/model.conf", "path/to/policy.csv") sub := "alice" // the user that wants to access a resource. obj := "data1" // the resource that is going to be accessed. act := "read" // the operation that the user performs on the resource. ok, err := e.Enforce(sub, obj, act) // 查看alice是否對data1z這個資源有read權限 if err != nil { // handle err } if ok == true { // permit alice to read data1 } else { // deny the request, show an error }
固然,casbin 能夠讀取具體 policy 的時候不只僅能夠經過 csv 文件進行讀取,也能夠經過數據庫進行讀取。這樣咱們甚至能夠寫一個用戶管理後臺來配置不一樣的用戶權限。model.conf 也是能夠從配置文件中獲取,也能夠從代碼中獲取,從代碼中獲取就能夠擴展爲先讀取數據庫,再代碼加載。可是 model.conf 一旦修改,對應的 policy 就須要進行同步修改,因此 model 在一個系統中不要進行頻繁修改。
casbin 是一種典型的「配置即一切」的軟件思路,那麼它的配置語法就顯得格外重要。咱們能夠經過 casbin 的在線配置編輯器 https://casbin.org/en/editor 來進行學習。
casbin 的理論基礎是這麼一篇論文:PML:一種基於Interpreter的Web服務訪問控制策略語言 。這篇論文是北大的三個學生一塊兒發表的。要理解 casbin 的配置文件,就須要先看這篇論文。
論文的做者以爲如今雲計算時代,權限管理系統是各類雲很是重要的組成部分,可是各類權限管理模型在各個雲廠商,或者各類雲時代的產品又都不同。那麼是否有一種權限模型來統一描述各類權限訪問方式呢?若是有的化,這種權限模型又須要獨立於各類語言而存在,才能被各類語言的雲產品所通用。
因而論文就創造除了這麼一種語言:PML(PERM modeling language)。其中的 PERM 指的是 Policy-Effect-Request-Matcher 。 下面咱們須要一一瞭解每個概念。
Request 表明的是請求,它的寫法是
request ::= r : attributes attributes ::= {attr1, attr2, attr3, ..}
好比咱們寫一行:
r = sub, obj, act
表明一個請求有三個標準的元素,請求主體,請求對象,請求操做。其中的sub, obj, act 能夠是本身定義的,只要你在一個配置文件中定義的元素標識符一致就行。
Policy 表明策略,它表示具體的權限定義的規則是什麼。
它一樣是形如 p = sub, obj, act 的表示方法,好比咱們定義了 policy 的規則如此,那麼咱們在 policy.csv 中每一行定義的 policy_rule 就必須和這個屬性一一對應。
在 policy.csv 文件中定義的策略就是 policy_rule。它和 Policy 是一一對應的。
好比 policy 爲
p = sub, obj, act
我設置的一條 policy_rule 爲
p, bob, data2, write
表示bob(p.sub = bob)能夠對data2 (p.obj = data2)進行 write (p.act = write) 操做這個規則。
policy 默認的最後一個屬性爲決策結果,字段名eft,默認值爲allow,即經過狀況下,p.eft就設置爲allow。
有請求,有規則,那麼請求是否匹配某個規則,則是matcher進行判斷的。
matcher ::=< boolean expr > (variables, constants, stub functions) variables ::= {r.attr1, r.attr2, .., p.attr1, p.attr2, ..} constants ::= {const1, const2, const3, ..}
好比下面這個matcher :
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act
表示當(r.sub == p.sub && r.obj == p.obj && r.act == p.act )的時候,返回true,不然返回false。
Effect 用來判斷若是一個請求知足了規則,是否須要贊成請求。它的規則比較複雜一些。
effect ::=< boolean expr > (effect term1, effect term2, ..) effect term ::= quantifier, condition quantif ier ::= some|any|max|min condition ::=< expr > (variables, constants, stub functions) variables ::= {r.attr1, r.attr2, .., p.attr1, p.attr2, ..} constants ::= {const1, const2, const3, ..}
這裏的 quantifier通常是some(論文中支持max和min),some表示括號中的表達式個數大於等於1就行。max/min表示括號中表達式的結果取最大/小的。(這裏我不是很理解,不過好像casbin也沒有實現min和max)
下面這個例子:
e = some(where (p.eft == allow))
這句話的意思就是將 request 和全部 policy 比對完以後,全部 policy 的策略結果(p.eft)爲allow的個數 >=1,整個請求的策略就是爲 true。
自定義函數是在 matcher 中使用的。咱們能夠本身定義一個函數,而後註冊進enforcer,在matcher中咱們就可使用了。
好比
func KeyMatch(key1 string, key2 string) bool { i := strings.Index(key2, "*") if i == -1 { return key1 == key2 } if len(key1) > i { return key1[:i] == key2[:i] } return key1 == key2[:i] } func KeyMatchFunc(args ...interface{}) (interface{}, error) { name1 := args[0].(string) name2 := args[1].(string) return (bool)(KeyMatch(name1, name2)), nil } e.AddFunction("my_func", KeyMatchFunc) // 配置文件中就能夠這樣寫了 [matchers] m = r.sub == p.sub && my_func(r.obj, p.obj) && r.act == p.act
casbin中有一些自定義的函數:
上面幾個概念關係以下:
大概解釋一下:
1 咱們先定義屬性,通用的一些屬性如 subject, object, action。
2 定義的屬性能夠做爲 Request 的屬性,也能夠做爲 Policy的屬性。
3 Policy_Rule 是 Policy 的具體規則。
4 使用定義的 Matcher 將 Request 和 Policy 進行匹配,這個匹配的過程可能使用到自定義函數。
5 全部的 Policy 匹配完成的結果,經過 Effect 規則得出最終是否能夠訪問的結果。
理解上面的知識,咱們應該能理解這個ACL的例子:
這個例子中定義了兩個 Policy_Rule: (alice 對 data1 有 read 權限) 和 (bob 對 data2 有 write 權限)
當request (alice, data1, read)進來的時候,它匹配了其中一條規則,因此some 以後的最終結果爲true。
RESTFUL接口使用URL和HTTP請求方法表示資源的增刪改查,那麼咱們可使用自定義函數來判斷是否能夠進行某個請求
這個論文還有一些其餘的定義:
其實這個就是一個自定義函數的概念,只是它的參數是請求的主體和角色。這裏引入了一個角色的概念。這個也是RBAC 權限模型所定義的。Has_Role 本質就是定義了一個 g 函數,這個 g 用於判斷哪一個用戶是否屬於哪一個角色。這個 g 的函數也能夠用配置寫規則:
g = _, _
而後在 Policy 寫規則:
g, alice, data2_admin
表示 alice 屬於角色 data2_admin。
matcher 就能夠寫成這樣:
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
咱們來看下下面這個RBAC的規則:
咱們能夠看這裏的 Policy 中,其實用戶和角色是分不出來的,(好比咱們單看policy裏面的p,是不瞭解data2_admin是用戶,仍是角色的)。可是咱們有一個 g (has_role)的規則,說明了alice 是有 data2_admin的角色的。
那麼最終判斷請求, alice, data2, read, 因爲alice 有data2_admin的角色,它知足了(p, data2_admin, data2, read) 這條規則,因此最終斷定結果爲 true。
其實有了這個has_role,咱們也能夠把一個用戶屬於另外一個用戶的關係作出來。這個也就是 RBAC1 的。
g 函數同時也能夠有三個參數,兩個參數的時候表示「誰 是 什麼角色」,三個參數的時候表示「誰 在 什麼域 是 什麼角色」。
這個仍是直接看例子:
在這個例子裏面,有個域的概念,它就至關於能夠表示「某個用戶在某個域(租戶)中是什麼角色」。
這個是實現了一種基於RBAC的分權分域用戶權限系統。
Casbin 支持的權限模型有:
咱們能夠經過這個頁面上的連接看每一個權限模型的配置:https://casbin.org/docs/zh-CN/supported-models
咱們閱讀的是 v2.1.2版本
源碼地址:https://github.com/casbin/casbin 。
註釋版地址:https://github.com/jianfengye/inside-go/tree/master/casbin-2.1.2。
按照大象裝進冰箱的邏輯,咱們也很容易想象獲得 casbin 應該分爲幾個步驟:
1 加載 model 的配置
2 加載 policy 的配置
3 具體請求進來以後,和 model 和 policy 進行匹配判斷。
確實源碼也就是這麼寫的。
整個 casbin 最核心的結構是
// Enforcer是權限驗證的主體 type Enforcer struct { modelPath string // model文件地址 model model.Model // model結構 fm model.FunctionMap // 自定義函數 eft effect.Effector // effecter的邏輯 adapter persist.Adapter // 持久化的Adapter,就是police的Adapter watcher persist.Watcher rm rbac.RoleManager // 這個要是這個模型是rbac(根據是否有g判斷),就增長這個角色管理器 enabled bool // 這個Enforcer的開關 autoSave bool // 若是調用了api修改的Policy,是否自動保存到Adapter中 autoBuildRoleLinks bool }
全部加載的 model 和 policy 都是豐富這個結構體。
好比:
func (e *Enforcer) LoadModel() error
或者
func (e *Enforcer) LoadPolicy() error
裏面的邏輯就不細說了,能夠去看 https://github.com/jianfengye/inside-go/tree/master/casbin-2.1.2 看這個的源碼註釋。我就說幾個我以爲這個項目代碼值得學習或者比較特別的點。
這個項目的 config, log 都是本身從標準庫從頭開始寫的。我認爲,做者一開始對項目就定位很清晰,這個是一個基礎類庫,能依賴儘量少的項目就依賴儘量少的項目。
casbin 的文件夾結構並非扁平的,而是樹形結構,基本上是兩層,甚至用到了三層。我我的以爲一個小的類庫卻是沒有必要分割這麼多文件夾,很容易讓人感受很複雜。不過在 casbin 這個項目中,文件夾分割的仍是比較清晰的。基本上是順着 enforcer 這個結構體的定義,在涉及到須要擴展的字段的時候就起一個文件夾。每一個擴展的文件夾基本都是 interface + implement 的方式。好比 enforcer 中有個 adapter 字段,使用了persist文件夾進行接口的定義和實現。
文件的定義也很清晰。好比 enforcer_interface 定義了80多個接口,在一個文件中所有實現並非很好的寫法,它就分紅了同一個文件夾下的幾個文件來寫(internal_api, rbac_api, management_api,rbac_api_with_domain)。這樣不只減小了代碼的長度,還使用文件名稱把接口進行了分類。
咱們寫 enforcer 可能會想到是否須要緩存,是否配置變化能及時更新,這裏使用了一個父類和兩個子類的方式來實現。把是否使用緩存,是否使用配置的選擇權交給用戶。而不是簡單在一個結構體裏面塞上這些功能。
基本上每一個能夠擴展的結構都考慮到使用接口進行定義。定義接口擴展性提升了,且能夠更加豐富了。
我以爲 persist/adapter 裏面這個寫法挺好的。
首先它定義了 adapter 這個接口,讓實現接口的類來具體實現我從哪一個持久化存儲裏面讀取配置文件,可是但願每一行都有統一的讀取規則,因此它就把 adapter 接口和 LoadPolicyLine 方法放同一個文件裏面。
其次繼承結構:
它實現了接口的繼承,filterdApapter 接口繼承 Adapter,file-filterdAdapter繼承了file-Adapter。
若是咱們寫,有可能就簡單定義了一個 filterLine 的函數,它這裏更近一步,能夠直接設置根據 p 或者 g 的某個字段進行過濾。
if err := e.LoadFilteredPolicy(&fileadapter.Filter{ P: []string{"", "domain1"}, G: []string{"", "", "domain1"}, }); err != nil { t.Errorf("unexpected error in LoadFilteredPolicy: %v", err) }
使用起來就大大方便了。
casbin 項目最核心的必定是那篇 PML 的論文。基於理論論文發展出來的這個項目也是很是牛X的。casbin 應該能知足大多數的權限管理系統的要求了。若是 casbin 不能實現你的權限需求的話,我以爲應該先思考下,產品經理提出的需求是否是靠譜的。。。哈哈哈