Casbin是一个强大的、高效的开源访问控制框架,其权限管理机制支持多种访问控制模型。
Casbin支持以下编程语言:
- 支持自定义请求的格式,默认的请求格式为
{subject, object, action}
。- 具有访问控制模型model和策略policy两个核心概念。
- 支持RBAC中的多层角色继承,不止主体可以有角色,资源也可以具有角色。
- 支持超级用户,如
root
或Administrator
,超级用户可以不受授权策略的约束访问任意资源。- 支持多种内置的操作符,如
keyMatch
,方便对路径式的资源进行管理,如/foo/bar
可以映射到/foo*
- 身份认证 authentication(即验证用户的用户名、密码),casbin只负责访问控制。应该有其他专门的组件负责身份认证,然后由casbin进行访问控制,二者是相互配合的关系。
- 管理用户列表或角色列表。 Casbin 认为由项目自身来管理用户、角色列表更为合适, 用户通常有他们的密码,但是 Casbin 的设计思想并不是把它作为一个存储密码的容器。 而是存储RBAC方案中用户和角色之间的映射关系
github:https://github.com/casbin/casbin
docs:https://casbin.org/docs/zh-CN/overview
在 Casbin 中, 访问控制模型被抽象为基于 PERM (Policy, Effect, Request, Matcher) 的一个文件。 因此,切换或升级项目的授权机制与修改配置一样简单。
PERM(Policy, Effect, Request, Matchers)模型很简单, 但是反映了权限的本质 – 访问控制
[外链图片转存失败(img-ys2XIpxc-1565944147357)(C:\Users\Todd\AppData\Roaming\Typora\typora-user-images\1565593950617.png)]
casbin 是基于 PERM 的, 所有 model file 中主要就是定义 PERM 4 个部分
1.Request definition
[request_definition]
r = sub, obj, act
分别表示 request 中的
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 体系, _, _ 表示用户和角色 _, _, _ 表示用户, 角色, 域
您可以通过组合可用的模型来定制您自己的访问控制模型。 例如,您可以在一个model中获得RBAC角色和ABAC属性,并共享一组policy规则。
Casbin中最基本、最简单的model是ACL。ACL中的model CONF为:
# Request definition
[request_definition]
r = sub, obj, act
# Policy definition
[policy_definition]
p = sub, obj, act
# Policy effect
[policy_effect]
e = some(where (p.eft == allow))
# Matchers
[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act
ACL model的示例policy如下:
p, alice, data1, read
p, bob, data2, write
这表示:
e := casbin.NewEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")
// Initialize the model from Go code.
m := casbin.NewModel()
m.AddDef("r", "r", "sub, obj, act")
m.AddDef("p", "p", "sub, obj, act")
m.AddDef("g", "g", "_, _")
m.AddDef("e", "e", "some(where (p.eft == allow))")
m.AddDef("m", "m", "g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act")
// Load the policy rules from the .CSV file adapter.
// 使用自己的 adapter 替换。
a := persist.NewFileAdapter("examples/rbac_policy.csv")
// 创建一个 enforcer。
e := casbin.NewEnforcer(m, a)
// Initialize the model from a string.
text :=
`
[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
`
m := NewModel(text)
// Load the policy rules from the .CSV file adapter.
// Replace it with your adapter to avoid files.
a := persist.NewFileAdapter("examples/rbac_policy.csv")
// Create the enforcer.
e := casbin.NewEnforcer(m, a)
这种方法的优点是您不需要维护模型文件
//全局变量 e是执行者实例
e := NewEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")
//获取当前策略中显示的主题列表
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")
//向当前策略添加授权规则。 如果规则已经存在,函数返回false,并且不会添加规则。 否则,函数通过添加新规则并返回true
added := e.AddPolicy("eve", "data3", "read")
// 向当前命名策略添加授权规则。 如果规则已经存在,函数返回false,并且不会添加规则。 否则,函数通过添加新规则并返回true
added := e.AddNamedPolicy("p", "eve", "data3", "read")
// 从当前策略中删除授权规则
removed := e.RemovePolicy("alice", "data1", "read")
// 移除当前策略中的授权规则,可以指定字段筛选器。 RemovePolicy 从当前策略中删除授权规则
removed := e.RemoveFilteredPolicy(0, "alice", "data1", "read")
//从当前命名策略中删除授权规则
removed := e.RemoveNamedPolicy("p", "alice", "data1", "read")
//从当前命名策略中移除授权规则,可以指定字段筛选器
removed := e.RemoveFilteredNamedPolicy("p", 0, "alice", "data1", "read")
//确定是否存在角色继承规则
has := e.HasGroupingPolicy("alice", "data2_admin")
//确定是否存在命名角色继承规则
has := e.HasNamedGroupingPolicy("g", "alice", "data2_admin")
// 向当前策略添加角色继承规则。 如果规则已经存在,函数返回false,并且不会添加规则。 如果规则已经存在,函数返回false,并且不会添加规则
added := e.AddGroupingPolicy("group1", "data2_admin")
//将命名角色继承规则添加到当前策略。 如果规则已经存在,函数返回false,并且不会添加规则。 否则,函数通过添加新规则并返回true
added := e.AddNamedGroupingPolicy("g", "group1", "data2_admin")
// 从当前策略中删除角色继承规则
removed := e.RemoveGroupingPolicy("alice", "data2_admin")
//从当前策略中移除角色继承规则,可以指定字段筛选器
removed := e.RemoveFilteredGroupingPolicy(0, "alice")
//从当前命名策略中移除角色继承规则
removed := e.RemoveNamedGroupingPolicy("g", "alice")
//当前命名策略中移除角色继承规则,可以指定字段筛选器
removed := e.RemoveFilteredNamedGroupingPolicy("g", 0, "alice")
//添加自定义函数
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)
casbin-go语言使用者
名称 | 描述 | 模型 | 策略 |
---|---|---|---|
VMware Harbor | VMware的开源可信云本地注册表项目,用于存储、签名和扫描内容。 | Code | Code |
Intel RMD | 英特尔的资源管理守护进程。 | .conf | .csv |
VMware Dispatch | 用于部署和管理无服务器风格应用程序的框架。 | Code | Code |
Skydive | 一个开源的实时网络拓扑和协议分析器。 | Code | .csv |
Zenpress | 用Golang编写的CMS系统。 | .conf | Gorm |
Argo CD | 为Kubernetes持续提供的GitOps。 | .conf | .csv |
Muxi Cloud | 一种更容易管理Kubernetes集群的方法。 | .conf | Code |
EngineerCMS | CMS管理工程师的知识。 | .conf | SQLite |
Cyber Auth API | 一个Golang身份验证API项目 | .conf | .csv |
IRIS Community | IRIS社区活动网页 | .conf | .csv |
Metadata DB | BB归档元数据数据库。 | .conf | .csv |
Qilin API | 游戏内容下的ProtocolONE许可证管理工具。 | Code | .csv |
常见的设计模式(DAC,MAC,RBAC,ABAC)
系统会识别用户,然后根据被操作对象(Subject)的权限控制列表(ACL: Access Control List)或者权限控制矩阵(ACL: Access Control Matrix)的信息来决定用户的是否能对其进行哪些操作,例如读取或修改。
而拥有对象权限的用户,又可以将该对象的权限分配给其他用户,所以称之为“自主(Discretionary)”控制。
这种设计最常见的应用就是文件系统的权限设计,如微软的NTFS。
DAC最大缺陷就是对权限控制比较分散,不便于管理,比如无法简单地将一组文件设置统一的权限开放给指定的一群用户
MAC是为了弥补DAC权限控制过于分散的问题而诞生的。在MAC的设计中,每一个对象都都有一些权限标识,每个用户同样也会有一些权限标识,而用户能否对该对象进行操作取决于双方的权限标识的关系,这个限制判断通常是由系统硬性限制的。比如在影视作品中我们经常能看到特工在查询机密文件时,屏幕提示需要“无法访问,需要一级安全许可”,这个例子中,文件上就有“一级安全许可”的权限标识,而用户并不具有。
MAC非常适合机密机构或者其他等级观念强烈的行业,但对于类似商业服务系统,则因为不够灵活而不能适用。
因为DAC和MAC的诸多限制,于是诞生了RBAC,并且成为了迄今为止最为普及的权限设计模型。
RBAC在用户和权限之间引入了“角色(Role)”的概念(暂时忽略Session这个概念):
如图所示,每个用户关联一个或多个角色,每个角色关联一个或多个权限,从而可以实现了非常灵活的权限管理。角色可以根据实际业务需求灵活创建,这样就省去了每新增一个用户就要关联一遍所有权限的麻烦。简单来说RBAC就是:用户关联角色,角色关联权限。另外,RBAC是可以模拟出DAC和MAC的效果的。
ABAC被一些人称为是权限系统设计的未来。
不同于常见的将用户通过某种方式关联到权限的方式,ABAC则是通过动态计算一个或一组属性来是否满足某种条件来进行授权判断(可以编写简单的逻辑)。属性通常来说分为四类:用户属性(如用户年龄),环境属性(如当前时间),操作属性(如读取)和对象属性(如一篇文章,又称资源属性),所以理论上能够实现非常灵活的权限控制,几乎能满足所有类型的需求。
例如规则:“允许所有班主任在上课时间自由进出校门”这条规则,其中,“班主任”是用户的角色属性,“上课时间”是环境属性,“进出”是操作属性,而“校门”就是对象属性了。为了实现便捷的规则设置和规则判断执行,ABAC通常有配置文件(XML、YAML等)或DSL配合规则解析引擎使用。
用户在携带自身的属性值包括主题属性,资源属性,环境属性,然后向资源发送请求,授权引擎 会根据subject所携带的属性进行判断,然后会给出拒绝或者同意的结果给用户,然后就可以访问资源。
总结一下,ABAC有如下特点:
既然ABAC这么好,那最流行的为什么还是RBAC呢?
大部分系统对权限控制并没有过多的需求,而且ABAC的管理相对来说太复杂了。
ABAC有时也被称为PBAC(Policy-Based Access Control)或CBAC(Claims-Based Access Control)
wiki地址:https://github.com/xuperchain/xuperunion/wiki/2.-关键概念#26-账户权限控制模型
名词解释
AK(Access Key)
:具体的一个address,由密码学算法生成一组公私钥对,然后将公钥用指定编码方式压缩为一个地址。
账号(Account)
: 在超级链上部署合约需要有账号, 账号可以绑定一组AK(公钥),并且AK可以有不同的权重。 账户的名字具有唯一性。
系统会首先识别用户,然后根据被操作对象的ACL的信息来决定用户能否对其进行哪些操作
ACL(Access Control List)是最早也是最基本的一种访问控制机制,它的原理非常简单:每一项资源,都配有一个列表,这个列表记录的就是哪些用户可以对这项资源执行CRUD中的那些操作。当系统试图访问这项资源时,会首先检查这个列表中是否有关于当前用户的访问权限,从而确定当前用户可否执行相应的操作。总得来说,ACL是一种面向资源的访问控制模型,它的机制是围绕“资源”展开的
在超级链中,账户和合约的关系如下所述:
account := "XC0000000000000001@xuper" ak := "XC0000000000000001@xuper/dpzuVdosQrF2kmzumhVeFQZa1aYcdgFpN"
主要有两个功能:账号权限管理、合约权限管理
系统设计
权限策略:
var PermissionRule_name = map[int32]string{
0: "NULL", // 无权限控制
1: "SIGN_THRESHOLD",// 签名阈值策略
2: "SIGN_AKSET", // AKSet签名策略
3: "SIGN_RATE", // 签名率策略
4: "SIGN_SUM", // 签名个数策略
5: "CA_SERVER", // CA服务器鉴权
6: "COMMUNITY_VOTE",// 社区治理
}
//签名阈值策略:
Sum{Weight(AK_i) , if sign_ok(AK_i)} >= acceptValue
权限测试例:
func Test_IdentifyAccount(t *testing.T) {
account := "XC0000000000000001@xuper"
ak := "XC0000000000000001@xuper/dpzuVdosQrF2kmzumhVeFQZa1aYcdgFpN"
pubkey := "{\"Curvname\":\"P-256\",\"X\":74695617477160058757747208220371236837474210247114418775262229497812962582435,\"Y\":51348715319124770392993866417088542497927816017012182211244120852620959209571}"
prikey := "{\"Curvname\":\"P-256\",\"X\":74695617477160058757747208220371236837474210247114418775262229497812962582435,\"Y\":51348715319124770392993866417088542497927816017012182211244120852620959209571,\"D\":29079635126530934056640915735344231956621504557963207107451663058887647996601}"
msg := "this is the test message from permission"
xcc, err := crypto_client.CreateCryptoClientFromJSONPublicKey([]byte(pubkey))
if err != nil {
t.Error("create crypto client failed, err=", err)
return
}
ecdsaPriKey, err := xcc.GetEcdsaPrivateKeyFromJSON([]byte(prikey))
if err != nil {
t.Error("get private key failed, err=", err)
return
}
sign, err := xcc.SignECDSA(ecdsaPriKey, []byte(msg))
if err != nil {
t.Error("sign failed, err=", err)
return
}
signInfo := &pb.SignatureInfo{
Sign: sign,
PublicKey: pubkey,
}
aks := make([]string, 1)
signs := make([]*pb.SignatureInfo, 1)
aks[0] = ak
signs[0] = signInfo
aclMgr, err := acl_mock.NewFakeACLManager()
if err != nil {
t.Error("NewAclManager failed, err=", err)
return
}
pm := &pb.PermissionModel{
Rule: pb.PermissionRule_SIGN_THRESHOLD,
AcceptValue: 1,
}
aclObj := &pb.Acl{
Pm: pm,
AksWeight: make(map[string]float64),
}
aclObj.AksWeight["dpzuVdosQrF2kmzumhVeFQZa1aYcdgFpN"] = 1
aclMgr.SetAccountACL(account, aclObj)
//主要为了实现IdentifyAccount方法
//SetAccountAC/ IdentifyAccount检查aks和符号是否匹配,这些aks可以通过account的ACL策略表示 给定的帐户。如果签名匹配aks,并且aks可以代表帐户,则返回true。将账户acl保存在内存中
result, err := IdentifyAccount(account, aks, signs, []byte(msg), aclMgr)
if err != nil || !result {
t.Error("IdentifyAccount failed, err=", err)
return
}
fakesign := "this is a fake signature"
signInfo2 := &pb.SignatureInfo{
Sign: []byte(fakesign),
PublicKey: pubkey,
}
signs[0] = signInfo2
result, err = IdentifyAccount(account, aks, signs, []byte(msg), aclMgr)
if result == true {
t.Error("IdentifyAccount fake sign failed, result=", result)
}
}
在IdentifyAccount方法中主要实现:
func IdentifyAccount(account string, aksuri []string, signs []*pb.SignatureInfo,
msg []byte, aclMgr acl.ManagerInterface) (bool, error) {
akslen := len(aksuri)
signslen := len(signs)
// aks and signs could have zero length for permission rule Null
if akslen != signslen || aclMgr == nil {
return false, fmt.Errorf("Invalid Param, akslen=%d,signslen=%d,aclMgr=%v", akslen, signslen, aclMgr)
}
// build perm tree
pnode, err := ptree.BuildAccountPermTree(aclMgr, account, aksuri, signs)
if err != nil {
return false, err
}
return validatePermTree(pnode, msg, true)
}
在这其中主要实现buildAccountPermTree和validatePermTree两个方法,Pnode定义了perm 树的节点
type PermNode struct { Name string // the name(id) of account/ak/method ACL *pb.Acl // the ACL definition of this account/method Status ValidateStatus // the ACL validation status of this node SignInfo *pb.SignatureInfo // the signature info of this node, only AK have this field Children []*PermNode // the children of this node, usually are ACL members of account/method } const ( _ ValidateStatus = iota // NotVerified : not verified by ACLValidator NotVerified // Success : ACLValidator verified successful Success // Failed : ACLValidator verified failed Failed )
它也没有使用PERM模型,至于为什么会用Perm这个词,目前也没想明白。
package main
import (
"fmt"
"github.com/casbin/casbin"
"github.com/casbin/casbin/model"
)
const modelText = `
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj,act
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = r.sub.App == r.obj.App && r.sub.Type == r.obj.Type && r.sub.Method == r.obj.Method && r.sub.Name == r.obj.Name \
&& r.sub.Read == r.act.Read && r.sub.Write == r.act.Write
`
type User struct {
Id int
UserName string
Group []Group
}
type Group struct {
Id int
Name string
Read bool
Write bool
App string // app
Type string // 类型
Method string // 方法
Priority int // 优先级
}
type Obj struct {
App string // app
Name string
Type string // 类型
Method string // 方法
}
type Act struct {
Read bool
Write bool
}
func main() {
m := model.Model{}
m.LoadModelFromText(modelText)
e:= casbin.NewEnforcer(m)
group1 := Group{
Name: "lk",
Read: true,
Write: true,
App: "asset",
Type: "user",
Method: "Get",
Priority: 100,
}
//group2 中Name和Write 不与Obj资源和执行操作权限中相同
group2 := Group{
Name: "lkTest",
Read: true,
Write: false,
App: "asset",
Type: "user",
Method: "Get",
Priority: 100,
}
// 用户 palletone 属于 group1 , group2
user1 := User{
UserName: "palletone",
Group: []Group{group1, group2},
}
//资源属性
obj := Obj{
App: "asset",
Name: "lk",
Type: "user",
Method: "Get",
}
//当执行操作权限 Read Write为True时 策略执行结果为allow
act := Act{
Read:true,
Write:true,
}
// 检查 用户 palletone 所有的组 是否有权限
for _, v := range user1.Group {
//强制决定一个“subject”是否可以通过操作“action”访问一个“object”,输入参数通常是:(sub, obj, act)。
flag:= e.Enforce(v, obj, act)
if flag {
fmt.Println("权限正常")
} else {
fmt.Println("没有权限")
}
}
}
//打印结果
//权限正常
//没有权限