Casbin是一个权限控制的开发库,它的特性包括:
Casbin不负责:
Casbin支持的访问控制模型列表:
模型 | 说明 |
ACL |
为对象添加一个访问许可(Permissions)列表,每个条目指定主体(Subject,例如用户/进程)可以对对象执行何种操作(Action) 模型示例: [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 策略示例: p, alice, data1, read p, bob, data2, write |
ACL with superuser |
ACL模型,外加指定特权用户 模型示例: [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 || r.sub == "root" |
ACL without users |
用于没有身份验证机制/用户登陆的系统 模型示例: [request_definition] r = obj, act [policy_definition] p = obj, act [policy_effect] e = some(where (p.eft == allow)) [matchers] m = r.obj == p.obj && r.act == p.act 策略示例: p, data1, read p, data2, write |
ACL without resources |
针对资源类别,而非资源实例进行访问控制。例如write-article, read-log,不去控制某个article、log的访问权限 模型示例: [request_definition] r = sub, act [policy_definition] p = sub, act [policy_effect] e = some(where (p.eft == allow)) [matchers] m = r.sub == p.sub && r.act == p.act |
RBAC |
基于角色的访问控制 模型示例: [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 策略示例: p, alice, data1, read p, bob, data2, write p, data2_admin, data2, read p, data2_admin, data2, write g, alice, data2_admin |
RBAC with resource roles |
用户、资源都可以具有角色(roles,或groups) 模型示例: [request_definition] r = sub, obj, act [policy_definition] p = sub, obj, act [role_definition] g = _, _ g2 = _, _ [policy_effect] e = some(where (p.eft == allow)) [matchers] m = g(r.sub, p.sub) && g2(r.obj, p.obj) && r.act == p.act 策略示例: p, alice, data1, read p, bob, data2, write p, data_group_admin, data_group, write g, alice, data_group_admin g2, data1, data_group g2, data2, data_group |
RBAC with domains/tenants |
对于不同的域(domain)/租户(tenant),用户可以具有不同的角色 模型示例: [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 策略示例: p, admin, domain1, data1, read p, admin, domain1, data1, write p, admin, domain2, data2, read p, admin, domain2, data2, write g, alice, admin, domain1 g, bob, admin, domain2 |
ABAC |
基于属性的角色控制,也叫(Policy-Based Access Control)或CBAC(Claims-Based Access Control) 不同于常见的将用户通过某种方式关联到权限的方式,ABAC通过动态计算一个或一组属性是否满足某种条件来进行授权判断(可以编写简单的逻辑)。属性通常来说分为四类:
理论上能够实现非常灵活的权限控制,几乎能满足所有类型的需求 举例来说,规则:允许所有班主任在上课时间自由进出校门,班主任是用户的角色熟悉,上课时间是环境属性,进出是操作属性,校门则是对象属性 ABAC的缺点是过于复杂,因此K8S在1.8版本引入RBAC 模型示例: [request_definition] r = sub, obj, act [policy_definition] p = sub, obj, act [policy_effect] e = some(where (p.eft == allow)) [matchers] m = r.sub == r.obj.Owner |
RESTful |
支持以HTTP动词,以及/res/*, /res/:id这样的路径来描述操作和资源 模型示例: [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 && keyMatch(r.obj, p.obj) && regexMatch(r.act, p.act) 策略示例: p, alice, /alice_data/*, GET p, alice, /alice_data/resource1, POST p, bob, /alice_data/resource2, GET p, bob, /bob_data/*, POST p, cathy, /cathy_data, (GET)|(POST) |
Deny-override |
支持allow、deny,deny可以覆盖allow 模型示例: [request_definition] r = sub, obj, act [policy_definition] p = sub, obj, act, eft [role_definition] g = _, _ [policy_effect] e = some(where (p.eft == allow)) && !some(where (p.eft == deny)) [matchers] m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act 策略示例: p, alice, data1, read, allow p, bob, data2, write, allow p, data2_admin, data2, read, allow p, data2_admin, data2, write, allow p, alice, data2, write, deny g, alice, data2_admin |
Priority |
策略规则可以支持优先级 模型示例: [request_definition] r = sub, obj, act [policy_definition] p = sub, obj, act, eft [role_definition] g = _, _ [policy_effect] e = priority(p.eft) || deny [matchers] m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act 策略示例: p, alice, data1, read, allow p, data1_deny_group, data1, read, deny p, data1_deny_group, data1, write, deny p, alice, data1, write, allow g, alice, data1_deny_group p, data2_allow_group, data2, read, allow p, bob, data2, read, deny p, bob, data2, write, deny g, bob, data2_allow_group |
在线编辑器:
在Casbin中,访问控制模型被抽象到基于PERM元模型的CONF文件中。
PERM元模型包含四大要素: https://casbin.org/editor/ 可以用于编写模型、策略。
下图展示了基于PERM的模型对一个请求进行授权的过程:
[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
对于该模型定义,定义授权规则的策略文件(模型是定义了公式,策略则是将具体的主体、操作、资源带入公式计算)可以如下:
p, alice, data1, read p, bob, data2, write
如果我们想允许admin执行任何操作,需要修改matcher:
[matchers] m = r.sub == admin || (r.sub == p.sub && r.obj == p.obj && r.act == p.act) # 如果太长,可以 \ 结尾换行
要将上述模型从ACL改为ABAC风格,我们可以在matcher中访问资源的属性:
[matchers] m = r.obj.owner == r.sub && (r.sub == p.sub && r.obj == p.obj && r.act == p.act) # 如果对象所有者是请求主体 m = r.obj == p.obj && r.act == p.act || r.obj in ('data2', 'data3') # Golang版本的Casbin还支持 in 操作符
可以看到,PERM元模型足够灵活,可以让我们以简单模型开始,并按需切换到复杂的授权模型。
首先,从模型文件、策略文件创建Enforcer:
e, _ := casbin.NewEnforcer("path/to/model.conf", "path/to/policy.csv")
判断一个Request是否可以被允许:
// 请求信息 sub := "alice" obj := "data1" act := "read" if res := e.Enforce(sub, obj, act); res { // 允许访问 } else { // 拒绝访问 }
Casbin提供了两套管理策略(Permission)的API:
// 获取所有主体 allSubjects := e.GetAllSubjects() // 获取指定的命名的策略中的所有主体 allNamedSubjects := e.GetAllNamedSubjects("p")
// 获取所有资源 allObjects := e.GetAllObjects() // 获取指定的命名的策略中所有的资源 allNamedObjects := e.GetAllNamedObjects("p")
// 获取所有操作 allActions := e.GetAllActions() allNamedActions := e.GetAllNamedActions("p")
// 获取所有角色 allRoles = e.GetAllRoles() allNamedRoles := e.GetAllNamedRoles("g")
// 获取所有策略(授权规则) policy = e.GetPolicy() // 获取过滤后的策略,可以根据字段进行过滤 字段索引 字段值 filteredPolicy := e.GetFilteredPolicy(0, "alice") // 获取命名的策略中的所有授权规则 namedPolicy := e.GetNamedPolicy("p") filteredNamedPolicy = e.GetFilteredNamedPolicy("p", 0, "bob") // 获取所有角色继承规则 groupingPolicy := e.GetGroupingPolicy() filteredGroupingPolicy := e.GetFilteredGroupingPolicy(0, "alice") namedGroupingPolicy := e.GetNamedGroupingPolicy("g") namedGroupingPolicy := e.GetFilteredNamedGroupingPolicy("g", 0, "alice") // 判断指定的授权规则是否存在 hasPolicy := e.HasPolicy("data2_admin", "data2", "read") hasNamedPolicy := e.HasNamedPolicy("p", "data2_admin", "data2", "read") // 添加一个授权规则 added := e.AddPolicy("eve", "data3", "read") // 添加若干 rules := [][] string { []string {"jack", "data4", "read"}, []string {"katy", "data4", "write"}, []string {"leyo", "data4", "read"}, []string {"ham", "data4", "write"}, } areRulesAdded := e.AddPolicies(rules) // 添加到命名策略中 added := e.AddNamedPolicy("p", "eve", "data3", "read") areRulesAdded := e.AddNamedPolicies("p", rules) // 删除一个授权规则 removed := e.RemovePolicy("alice", "data1", "read") // 删除多个授权规则 areRulesRemoved := e.RemovePolicies(rules) // 字段索引 字段值... removed := e.RemoveFilteredPolicy(0, "alice", "data1", "read") removed := e.RemoveNamedPolicy("p", "alice", "data1", "read") areRulesRemoved := e.RemoveNamedPolicies("p", rules) removed := e.RemoveFilteredNamedPolicy("p", 0, "alice", "data1", "read") // 添加一个角色继承规则,如果规则已经存在,返回false added := e.AddGroupingPolicy("group1", "data2_admin") areRulesAdded := e.AddGroupingPolicies(rules) added := e.AddNamedGroupingPolicy("g", "group1", "data2_admin") areRulesAdded := e.AddNamedGroupingPolicies("g", rules) // 删除角色继承规则 removed := e.RemoveGroupingPolicy("alice", "data2_admin")
你添加一个Go函数,并在编写规则时,使用该函数进行Match:
func CustomFunction(key1 string, key2 string) bool { if key1 == "/alice_data2/myid/using/res_id" && key2 == "/alice_data/:resource" { return true } else if key1 == "/alice_data2/myid/using/res_id" && key2 == "/alice_data2/:id/using/:resId" { return true } else { return false } } func CustomFunctionWrapper(args ...interface{}) (interface{}, error) { key1 := args[0].(string) key2 := args[1].(string) return bool(CustomFunction(key1, key2)), nil } e.AddFunction("keyMatchCustom", CustomFunctionWrapper)
// 获取具有角色的用户 res := e.GetRolesForUser("alice") // 判断用户是否具有角色 res := e.HasRoleForUser("alice", "data1_admin") // 为用户添加角色 e.AddRoleForUser("alice", "data2_admin") // 为用户删除角色 e.DeleteRoleForUser("alice", "data1_admin") // 删除用户的所有角色 e.DeleteRolesForUser("alice") // 删除一个角色 e.DeleteRole("data2_admin") // 获取用户具有的隐式特权,不同于GetRolesForUser,该函数会同时 // 返回所有间接得到的角色(因为角色之间可以有继承关系) // // 例如: // g, alice, role:admin alice属于admin // g, role:admin, role:user admin属于user // 该方法会返回 ["role:admin", "role:user"] e.GetImplicitRolesForUser("alice") // 获取用户的特权,不同于GetPermissionsForUser,该函数会同时 // 返回来自用户所属角色的特权 e.GetImplicitPermissionsForUser("alice")
// 删除一个特权(操作) e.DeletePermission("read") // 为用户或角色添加一个特权 e.AddPermissionForUser("bob", "read") // 为用户删除一个特权 e.DeletePermissionForUser("bob", "read") // 删除用户的全部特权 e.DeletePermissionsForUser("bob") // 查询用户的特权 e.GetPermissionsForUser("bob") // 查看用户是否具有指定的特权 e.HasPermissionForUser("alice", []string{"read"})
在Casbin中,策略存储以适配器的形式实现。你可以使用适配器的LoadPolicy()来加载策略规则,使用SavePolicy()来保存规则。
所有可用的适配器:https://casbin.org/docs/en/adapters#supported-adapters
type Adapter interface { // 加载所有策略到model中 LoadPolicy(model model.Model) error // 保存所有策略 SavePolicy(model model.Model) error // 添加一个策略到后端存储,作为自动保存特性的一部分 AddPolicy(sec string, ptype string, rule []string) error // 从后端存储删除一个策略,作为自动保存特性的一部分 RemovePolicy(sec string, ptype string, rule []string) error // 从后端存储删除匹配的策略规则,作为自动保存特性的一部分 RemovFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) error } // 建模访问控制模型 type Model map[string]AssertionMap type AssertionMap map[string]*Assertion type Assertion struct { Key string Value string Tokens []string Policy [][]string RM rbac.RoleManager } var sectionNameMap = map[string]string{ "r": "request_definition", "p": "policy_definition", "g": "role_definition", "e": "policy_effect", "m": "matchers", }
import ( "github.com/casbin/casbin" "github.com/casbin/casbin/file-adapter" ) a := fileadapter.NewAdapter("examples/basic_policy.csv") // 从适配器,而非文件加载策略 e := casbin.NewEnforcer("examples/basic_model.conf", a)
Github地址:https://github.com/sebastianliu/etcd-adapter
import ( "github.com/sebastianliu/etcd-adapter" "github.com/casbin/casbin" ) a := etcdadapter.NewAdapter([]string{"http://127.0.0.1:2379"}, "casbin_policy_test") e := casbin.NewEnforcer("rbac_model.conf", a) e.LoadPolicy()
角色管理器用于管理RBAC角色层次(用户-角色、角色-角色映射)。角色管理器可以从Casbin策略文件,或者第三方数据源提取角色数据。除了默认角色管理器,都是out-of-tree的。
package rbac type RoleManager interface { // 清空所有数据,重置到初始状态 Clear() error // 在两个角色之间添加继承关系。domain是角色的前缀 AddLink(name1 string, name2 string, domain ...string) error // 解除两个角色之间的继承关系 DeleteLink(name1 string, name2 string, domain ...string) error // 判断两个角色之间是否有继承关系 HasLink(name1 string, name2 string, domain ...string) (bool, error) // 获取用户继承的角色列表 GetRoles(name string, domain ...string) ([]string, error) // 获取继承某角色的用户列表 GetUsers(name string, domain ...string) ([]string, error) PrintRoles() error }
腾讯的TKE项目使用Casbin实现了权限控制。相关代码位于auth-api和auth-controller中。
可以先将字符串解析为Model对象,然后再创建Enforcer:
m, err := model.NewModelFromString(authapi.DefaultRuleModel) // SyncedEnforcer保持和文件或数据库同步 enforcer, err = casbin.NewSyncedEnforcer(m)
import casbinlog "github.com/casbin/casbin/v2/log" casbinlog.SetLogger(&casbinlogger.WrapLogger{}) enforcer.EnableLog(true)
在创建auth-controller的ControllerContext时,会初始化适配器:
adpt := util2.NewAdapter(client.AuthV1().Rules(), sharedInformers.Auth().V1().Rules().Lister()) func NewAdapter(ruleClient authv1client.RuleInterface, ruleLister authv1lister.RuleLister) *RestAdapter { adapter := &RestAdapter{ ruleClient: ruleClient, lister: ruleLister, } return adapter }
可以猜测到,此适配器使用API Server的Lister接口,从Etcd中读取策略信息。
该适配器的实现:
type RestAdapter struct { ruleClient authv1client.RuleInterface lister authv1lister.RuleLister } func (a *RestAdapter) LoadPolicy(model model.Model) error { // 加载所有Rule资源 rules, err := a.lister.List(labels.Everything()) if err != nil { return fmt.Errorf("list all rules failed: %v", err) } for _, rule := range rules { a.loadPolicy(rule, model) } return nil } // 将自定义资源转换为文本,然后从文本解析出策略 // V0 - V6是策略规则可能的参数,一般没有这么多参数 func (a *RestAdapter) loadPolicy(rule *authv1.Rule, model model.Model) { casRule := rule.Spec lineText := casRule.PType if casRule.V0 != "" { lineText += ", " + casRule.V0 } if casRule.V1 != "" { lineText += ", " + casRule.V1 } if casRule.V2 != "" { lineText += ", " + casRule.V2 } if casRule.V3 != "" { lineText += ", " + casRule.V3 } if casRule.V4 != "" { lineText += ", " + casRule.V4 } if casRule.V5 != "" { lineText += ", " + casRule.V5 } if casRule.V6 != "" { lineText += ", " + casRule.V6 } persist.LoadPolicyLine(lineText, model) } // 将内存中的策略保存到Etcd,保存为若干Rule资源 func (a *RestAdapter) SavePolicy(model model.Model) error { // 删除老数据 err := a.destroy(context.Background()) if err != nil { return err } var rules []authv1.Rule for ptype, ast := range model["p"] { for _, line := range ast.Policy { rules = append(rules, ConvertRule(ptype, line)) } } for ptype, ast := range model["g"] { for _, line := range ast.Policy { rules = append(rules, ConvertRule(ptype, line)) } } return a.savePolicy(context.Background(), rules) } func (a *RestAdapter) savePolicy(ctx context.Context, rules []authv1.Rule) error { for _, rule := range rules { if _, err := a.ruleClient.Create(ctx, &rule, metav1.CreateOptions{}); err != nil && !apierrors.IsAlreadyExists(err) { return err } } return nil }
TKE使用的默认模型是:
[request_definition] r = sub, dom, obj, act [policy_definition] p = sub, dom, obj, act, eft [role_definition] # 请求主体, 策略主体, 租户 g = _, _, _ [policy_effect] e = some(where (p.eft == allow)) && !some(where (p.eft == deny)) [matchers] m = g(r.sub, p.sub, r.dom) && keyMatchCustom(r.obj, p.obj) && keyMatchCustom(r.act, p.act)
其中keyMatchCustom是自定义的函数:
enforcer.AddFunction("keyMatchCustom", CustomFunctionWrapper) func CustomFunctionWrapper(args ...interface{}) (interface{}, error) { key1 := args[0].(string) key2 := args[1].(string) return keyMatchCustomFunction(key1, key2), nil } // 目标匹配规则 // /project:123/cluster:456 匹配 /project:*/cluster:456 // registry:123/* 匹配 registry:123/456 func keyMatchCustomFunction(key1 string, key2 string) bool { key1 = strings.ToLower(key1) key2 = strings.ToLower(key2) key2 = strings.Replace(key2, "*", ".*", -1) re := regexp.MustCompile(`(.*):[^/]+(.*)`) i := 2 for { if !strings.Contains(key2, "/:") { break } key2 = re.ReplaceAllString(key2, "$1[^/]+$2") i = i + 1 } return casbinutil.RegexMatch(key1, "^"+key2+"$") }
TKE使用了自定义的角色管理器:
rm := domainrolemanager.NewRoleManager(10) enforcer.SetRoleManager(rm) enforcer.StartAutoLoadPolicy(cfg.CasbinReloadInterval)
该角色管理器来自github.com/dovics/domain-role-manager
Leave a Reply