golang中的权限模型casbin


原文链接: golang中的权限模型casbin
  1. 理解 PERM 模型
    PERM(Policy, Effect, Request, Matchers)模型很简单, 但是反映了权限的本质 – 访问控制

Policy: 定义权限的规则集(动态变化,可存数据库)
Request: 访问请求, 也就是谁(sub)想操作(act)什么资源(obj)
Matcher: 对一个 Request 依次匹配定义的N个 Policys ,匹配的规则都是同一个Matcher
Effect: Compose/Reduce 定义组合了多个 Policy 之后的结果, allow/deny

**Request definition**

[request_definition]
r = sub, obj, act
分别表示 request 中的
    accessing entity (Subject)
    accessed resource (Object)
    the access method (Action)

**Policy definition**

[policy_definition]
p = sub, obj, act
p2 = sub, act
定义的每一行称为 policy rule, p, p2 是 policy rule 的名字. p2 定义的是 sub 所有的资源都能执行 act

**Policy effect**

[policy_effect]
e = some(where (p.eft == allow))
上面表示有任意一条 policy rule 满足, 则最终结果为 allow

**Matchers**

[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act
定义了 request 和 policy 匹配的方式, p.eft 是 allow 还是 deny, 就是基于此来决定的

**Role**

[role_definition]
g = _, _
g2 = _, _
g3 = _, _, _
g, g2, g3 表示不同的 RBAC 体系,  _, _ 表示用户和角色  _, _, _ 表示用户, 角色, 域(也就是租户)

Model语法

Model CONF 至少应包含四个部分: [request_definition], [policy_definition], [policy_effect], [matchers]。

如果 model 使用 RBAC, 还需要添加[role_definition]部分。

Model CONF 可以包含注释。注释以 # 开头,# 将注释整行。

Request定义
[request_definition] 部分用于request的定义,它明确了 e.Enforce(...) 函数中参数的含义。

if e.Enforce(sub, dom, obj, act) {
    
}
func (e *Enforcer) Enforce(rvals ...interface{}) bool {
	for j, token := range e.model["r"]["r"].Tokens {
				parameters[token] = rvals[j]
            }
}

[request_definition]
r = sub, obj, act

sub, obj, act 表示经典三元组: 访问实体 (Subject),访问资源 (Object) 和访问方法 (Action)。 但是, 你可以自定义你自己的请求表单, 如果不需要指定特定资源,则可以这样定义 sub、act ,或者如果有两个访问实体, 则为 sub、sub2、obj、act。

Policy定义
[policy_definition] 部分是对policy的定义,以下文的 model 配置为例:

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

这些是我们对policy规则的具体描述

p, alice, data1, read
p2, bob, write-all-objects

policy部分的每一行称之为一个策略规则, 每条策略规则通常以形如p, p2的policy type开头。 如果存在多个policy定义,那么我们会根据前文提到的policy type与具体的某条定义匹配。 上面的policy的绑定关系将会在matcher中使用, 罗列如下:

(alice, data1, read) -> (p.sub, p.obj, p.act)
(bob, write-all-objects) -> (p2.sub, p2.act)

注1: 当前只支持形如 p的单个policy定义, 形如p2 类型的尚未支持。 通常情况下, 用户无需使用多个 policy 定义, 如果您有其他情形的policy定义诉求,请在https://github.com/casbin/casbin/issues/new提出issue告知我们。

注2: policy定义中的元素始终被视为字符串(string)对待, 如果您对此有疑问,请移步https://github.com/casbin/casbin/issues/113

Policy effect定义
[policy_effect] 部分是对policy生效范围的定义, 原语定义了当多个policy rule同时匹配访问请求request时,该如何对多个决策结果进行集成以实现统一决策。 以下示例展示了一个只有一条规则生效,其余都被拒绝的情况:

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

该Effect原语表示如果存在任意一个决策结果为allow的匹配规则,则最终决策结果为allow,即allow-override。 其中p.eft 表示策略规则的决策结果,可以为allow 或者deny,当不指定规则的决策结果时,取默认值allow 。 通常情况下,policy的p.eft默认为allow, 因此前面例子中都使用了这个默认值。

这是另一个policy effect的例子:

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

该Effect原语表示不存在任何决策结果为deny的匹配规则,则最终决策结果为allow ,即deny-override。 some 量词判断是否存在一条策略规则满足匹配器。 any 量词则判断是否所有的策略规则都满足匹配器 (此处未使用)。 policy effect还可以利用逻辑运算符进行连接:

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

该Effect原语表示当至少存在一个决策结果为allow的匹配规则,且不存在决策结果为deny的匹配规则时,则最终决策结果为allow。 这时allow授权和deny授权同时存在,但是deny优先。

Matchers
[matchers] 原语定义了策略规则如何与访问请求进行匹配的匹配器,其本质上是布尔表达式,可以理解为Request、Policy等原语定义了关于策略和请求的变量,然后将这些变量代入Matcher原语中求值,从而进行策略决策。

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

这是一个简单的例子,该Matcher原语表示,访问请求request中的subject、object、action三元组应与策略规则policy rule中的subject、object、action三元组分别对应相同。

Matcher原语支持+、 -、 *、 /等算数运算符,==,、!=、 >、 <等关系运算符以及&& (与)、|| (或)、 ! (非)等逻辑运算符。

注: 虽然可以像其他原语一样的编写多个类似于 m1, m2 的matcher, 但是当前我们只支持一个有效的 matcher m。 通常情况下,您可以在一个matcher中使用上文提到的逻辑运算符来实现复杂的逻辑判断, 因而我们认为目前不需要支持多个matcher。 如果您对此有疑问,请告知我们(https://github.com/casbin/casbin/issues)。

matcher中的函数
matcher的强大与灵活之处在于您甚至可以在matcher中定义函数,这些函数可以是内置函数或自定义的函数。当前支持的内置函数如下:

fm.AddFunction("keyMatch", util.KeyMatchFunc)
fm.AddFunction("keyMatch2", util.KeyMatch2Func)
fm.AddFunction("regexMatch", util.RegexMatchFunc)
fm.AddFunction("ipMatch", util.IPMatchFunc)

函数 释义 示例
keyMatch(arg1, arg2) 参数 arg1 是一个 URL 路径,例如 /alice_data/resource1,参数 arg2 可以是URL路径或者是一个 * 模式,例如 /alice_data/*。此函数返回 arg1是否与 arg2 匹配。 keymatch_model.conf/keymatch_policy.csv
keyMatch2(arg1, arg2) 参数 arg1 是一个 URL 路径,例如 /alice_data/resource1,参数 arg2 可以是 URL 路径或者是一个 : 模式,例如/alice_data/:resource。此函数返回 arg1 是否与 arg2 匹配。 keymatch2_model.conf/keymatch2_policy.csv
regexMatch(arg1, arg2) arg1 可以是任何字符串。arg2 是一个正则表达式。它返回 arg1 是否匹配 arg2。 keymatch_model.conf/keymatch_policy.csv
ipMatch(arg1, arg2) arg1 是一个 IP 地址, 如 192.168.2.123。arg2 可以是 IP 地址或 CIDR, 如 192.168.2. 0/24。它返回 arg1 是否匹配 arg2。 ipmatch_model.conf/ipmatch_policy.csv
如何添加自定义函数
首先准备好一个有几个参数和一个布尔值返回值的函数:

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]

}

然后用 interface{} 类型包装此函数:

func KeyMatchFunc(args ...interface{}) (interface{}, error) {

name1 := args[0].(string)
name2 := args[1].(string)

return (bool)(KeyMatch(name1, name2)), nil

}

最后, 将该函数注册到 Casbin enforcer:

e.AddFunction("my_func", KeyMatchFunc)

现在, 您可以像下面这样使用 model 中的函数:

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

Role定义
[role_definition]原语定义了RBAC中的角色继承关系。 Casbin支持RBAC系统的多个实例,例如,用户可以有角色和继承关系,而资源也可以有角色和继承关系。 这两个RBAC系统不会干扰。

此部分是可选的。如果在模型中不使用 RBAC 角色, 则省略此部分。

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

上述Role原语表示g i是一个RBAC体系,g2是另一个RBAC体系。 _, _表示角色继承关系的前项和后项,即前项继承后项角色的权限。 一般来讲,如果您需要进行角色和用户的绑定,直接使用g 即可。 当您需要表示角色(或者组)与用户和资源的绑定关系时,可以使用g 和 g2 这样的表现形式。 请参见 rbac_model 和 rbac_model_with_resource_roles 的示例。

在Casbin里,我们以policy表示中实际的用户角色映射关系 (或是资源-角色映射关系),例如:

p, data2_admin, data2, read
g, alice, data2_admin

上述策略规则表示alice继承或具有角色data2_admin,这里的alice可以为具体的某个用户、某种资源抑或某个角色,在Casbin中它将会被当作字符串(string)来对待。

在 matcher 中,您应该以如下方式来校验角色信息:

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

它表示请求request中的sub必须具有policy中定义的sub角色。

注:

Casbin 只存储用户角色的映射关系。
Casbin 不验证用户是否为有效用户, 或者角色是否为有效角色。这应该由身份验证来处理。
RBAC 系统中的用户名称和角色名称不应相同。因为Casbin将用户名和角色识别为字符串, 所以当前语境下Casbin无法得出这个字面量到底指代用户 alice 还是角色 alice。 这时,使用明确的 role_alice ,问题便可迎刃而解。
假设A具有角色 B,B 具有角色 C,并且 A 有角色 C。这种传递性在当前版本会造成死循环。
域租户的角色定义
在Casbin中的RBAC角色可以是全局或是基于特定于域的。 特定域的角色意味着当用户处于不同的域/租户群体时,用户所表现的角色也不尽相同。 这对于像云服务这样的大型系统非常有用,因为用户通常分属于不同的租户群体。

域/租户的角色定义应该类似于:

[role_definition]
g = _, _, _

原语中第三个_表示域的概念,相对应的策略规则的实例如下:

p, admin, tenant1, data1, read
p, admin, tenant2, data2, read

g, alice, admin, tenant1
g, alice, user, tenant2

该实例表示tenant1的域内角色admin 可以读取data1, alice在tenant1域中具有admin角色,但在tenant2域中具有user角色, 所以alice可以有读取data1的权限。 同理,因为alice不是tenant2的admin,所以她访问不了data2。

接下来在matcher中,应该像下面的例子一样检查角色信息:

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

更多示例参见: rbac_model_with_domains.conf 。

`