运维权限系统

2018/7/17 posted in  Security

http://blog.niean.name/2016/01/31/rbac1

年关将近,终于有了一点时间。研究了下小米的运维权限体系,结合自己过往的经验,整理了下运维权限系统相关的设计思路。欢迎各位行家拍砖。

概述

访问控制,是针对越权使用资源的防御措施。企业环境中的访问控制策略一般有三种:自主型访问控制方法、强制型访问控制方法和基于角色的访问控制方法(Role-Based Access Control, RBAC)。其中,自主式太弱,强制式太强,RBAC 是目前公认的、解决企业统一资源访问控制的有效方法。

RBAC 以角色为中心,进行访问策略的控制。例如,在一个医院系统中,医生角色可以诊断、开据处方、指示实验室化验等;研究员角色只能收集用于研究的匿名临床信息。NIST(The National Institute of Standards and Technology,美国国家标准与技术研究院) 标准 RBAC 有 4 个模型,分别是基本模型 RBAC0(Core RBAC)、角色分级模型 RBAC1(Hierarchal RBAC)、角色限制模型 RBAC2(Constraint RBAC) 和统一模型 RBAC3(Combines RBAC)[1]

RBAC0

RBAC0 定义了 RBAC 控制系统的最小元素集合,包括用户 (user)、角色 (role)、权限 (permission)、资源 (resouce)、操作 (operation),模型如下,

在 RBAC0 模型中,主要关系有两种,为角色分配权限 PA(Permission Assignment)、分配角色给用户 UA(Users Assignment)。PA 实现权限和角色之间的关联关系,UA 实现用户和角色之间的关联关系。

在 RBAC0 模型中,资源的定义,需要注意以下两个问题 [2]

  1. RBAC0 中的资源,指的是资源类别 (resouce class)、而不是某个特定资源的实例 (resouce instance),权限管理系统负责 资源类别之权限 的管理,而应用系统负责 特定资源实例之权限 的管理。因为,对资源实例的访问控制,通常带有较多个性化的业务逻辑、不适合放在高度抽象的权限管理系统中。
  2. RBAC0 中的资源,是一级扁平化的。在生产环境下,资源可能具有层次关系和包含关系,需要将这种层级关系 降为 一级扁平结构。

RBAC 的其他模型,都是在 RBAC0 的基础上,演化而来的。

RBAC1

RBAC1 引入了,角色间的继承关系,被继承的是 一个个的权限点。角色间的继承关系,可分为一般继承关系和受限继承关系。一般继承关系仅要求角色继承关系是一个绝对偏序关系、允许角色间的多继承 (类似有向无环图)。而受限继承关系则进一步要求角色继承关系是一个树结构。

在 RBAC1 模型中,角色继承常常借助外部已有系统来实现。

RBAC2

RBAC2 模型中添加了责任分离关系。RBAC2 的约束规定了权限被赋予角色时, 或角色被赋予用户时, 以及当用户在某一时刻激活一个角色时所应遵循的强制性规则。约束与用户-角色-权限关系一起,决定了 RBAC2 模型中用户的访问权限。

RBAC3

RBAC3 包含了 RBAC1 和 RBAC2,既提供了角色继承,又提供了责任分离关系。

业务树为核心的运维体系,其权限系统一般会采用 RBAC3 模型,且角色继承借助业务树来完成、约束也借助服务树来实现。

运维权限系统

本文适合,以业务树为核心的,运维体系。对于不以业务树为核心的运维体系,本文不一定可用。

业务树

业务树,以树状的上下层级模式,展示公司内部的各个业务。业务树是公司业务的高度抽象,是各运维子系统与公司业务发生耦合的纽带,是各运维子系统彼此交互的统一协议平台。一个典型的业务树,结构如下图所示,

模型设计

权限系统的设计,取决于其滋生的业务环境。根据运维系统的业务特性,运维权限系统特点如下:

  1. 资源类型少,操作少,因此,可以穷举所有的权限点。甚至,增加角色时,可以从所有权限点中进行勾选
  2. 权限点较少,运维业务场景相对固定且较少,因此,可以预定义绝大部分角色。甚至可以,不允许增删角色、只允许通过继承和覆盖来定制角色权限
  3. 借助业务树的层级关系,实现角色的向上继承、向上覆盖
  4. 权限系统的约束关系,大部分与业务规范、安全控制有关, 更适合在代码中实现

结合上述特点,采用 RBAC3 模型,设计出运维权限系统的核心表结构,如下图,

上述表结构设计,有如下关键点,

  • 用户 user、角色 role、权限点 permission 是权限模型中的元素,用户角色关系表r_user_role记录了 UA 的结果,角色权限表r_role_permission记录了 PA 的结果
  • 资源 resource 和操作 operation,合并到权限点表 permission 中。因为,资源和操作组成的权限点相对较少、相对固定,没有必要将资源和操作拆出来、独立成表
  • 角色表 role 中有一个 tag 字段。tag 是业务树节点的有序字符串描述,易于存储、同时又保留了业务树的层级信息,可以辅助实现角色的继承、覆盖等逻辑,可以适配不同公司的业务树结构

角色

运维系统,主要面向不同层次的开发和运维人员。因此,常用角色包括,

- 开发人员 运维人员
普通成员 dev.member sre.member
管理员 dev.admin sre.admin

其中,dev.member、dev.admin、sre.member、sre.admin 可以是 权限系统预定义的 角色。

权限点

运维系统的权限点,以不同的运维子系统为核心,包括但不限于,

资源 操作
监控系统. 策略 C,R,U,D
监控系统. 策略. 报警历史 R,D
监控系统. 绘图 C,R,U,D
部署系统. 任务 C,R,U,D,X
预算系统. 申请 C,R,U,D,X,A

同一运维子系统,可以抽象出多个资源;每个资源,对应了不同的操作。资源和操作结合起来,就是权限点,如 “监控系统. 策略: C” 就是一个权限点,表示 “创建 监控策略” 的权限。

权限点是有限的、基本稳定的,不会发生大面积的更新。

给角色分配权限

PA。一般的,我们会说,把 权限” 监控系统. 策略: C”,授予 普通开发人员 “dev.member”。考虑到角色的继承、覆盖关系,我们会说,把 权限” 监控系统. 策略: C”,授予,业务节点cop.xiaomi_owt.inf上的 普通开发人员”dev.member”,接口定义如下,

@app.route("/tag/<string:tag>/role/<string:role>/permission/<string:permission>/pa", methods=['POST'])
def pa_tag_role_permission(tag, role, permission):
    rid = get_or_create_role(tag, role)
    permissionId = get_or_create_permission(permission)
    assign_permission_to_role(rid, permissionId)

对应的数据库操作,如下:

insert into `role` values (null, 'cop.xiaomi_owt.inf', 'dev.member') on duplicate key update id=last_insert_id(id); 
# rid=`role`.lastInsertId()

insert into `permission` values (null, '监控系统.策略', 'C') on duplicate key update id=last_insert_id(id);
# permissionId=`permission`. lastInsertId()

insert into `r_role_permission` values (null, rid, permissionId);

为用户分配角色

UA。考虑到角色继承,我们会说,将 业务节点cop.xiaomi_owt.inf上的 普通开发人员 “dev.member” 角色,授予,用户 “niean”,接口定义如下,

@app.route("/tag/<string:tag>/role/<string:role>/user/<string:user>/ua", methods=['POST'])
def ua_tag_role_user(tag, role, user):
    rid = get_role(tag, role)
    uid = get_user(user)
    assign_user_to_role(rid, uid)

对应的数据库操作,为,

# rid=`role`.getIdOfRole('cop.xiaomi_owt.inf', 'dev.member')
# uid=`user`.getIdOfUser('niean')
insert into `r_user_role` values (null, rid, uid);

角色继承

角色继承,依赖业务树的层级结构,向上继承。继承来的角色,其权限点维持不变。

例如,用户”niean” 在业务树节点cop.xiaomi_owt.inf上 具有 普通开发人员”dev.member” 的角色。那么用户”niean” 在业务树节点cop.xiaomi_owt.inf_pdl.falcon上 也具有普通开发人员”dev.member” 的角色,且具有相同的权限点。

我们的数据表”role” 中,保存的是扁平的业务树节点信息,那,如何判断业务节点的上下级关系呢?字符串包含! 如,字符串cop.xiaomi_owt.inf_pdl.falcon包含cop.xiaomi_owt.inf,所以前者是后者的子节点。又如,cop.xiaomi_owt.miui不包含cop.xiaomi_owt.inf,所以前者不是后者的子节点。业务树的层级关系,蜕化为字符串的包含操作,是不是很爽,哈哈。

角色覆盖

角色覆盖,依赖业务树的层级结构,向上覆盖。考虑到安全性,角色覆盖时,对应的权限点只减少、不增加。这是运维规范的一部分,有利于提高安全性。

例如,业务树节点cop.xiaomi_owt.inf上 绑定了一个 普通开发人员”dev.member” 的角色。根据业务需要,业务树节点cop.xiaomi_owt.inf_pdl.falcon上的 普通开发人员”dev.member” 角色,不允许发起部署任务 (“部署系统. 任务: X”),于是,我们需要在业务树节点cop.xiaomi_owt.inf_pdl.falcon上 重新定义 普通开发人员”dev.member” 的 权限点、减少权限点” 部署系统. 任务: X”。

角色覆盖 dev.member 角色的权限点
cop.xiaomi_owt.inf 监控系统. 绘图: R, 部署系统. 任务: R, 部署系统. 任务: X, …
cop.xiaomi_owt.inf_pdl.falcon 监控系统. 绘图: R, 部署系统. 任务: R, …

通常,我们在业务树的根节点cop.xiaomi,授予,各角色以最大权限;同时,在业务树子节点,根据业务需要,删除一些角色特定的权限点。这样,既可以 让用户方便地 在各业务树子节点 通过继承获取权限,又可以 让管理者方便地 在各业务树节点 通过覆盖回收特定权限

验权

典型的验权场景,如下。

@app.route("/permission/user/<string:user>/tag/<string:tag>/permission/<string:permission>/check", methods=['GET'])
def check_user_tag_permission(user, tag, permission):
    if user's roles on tag have permisson:
        return True
    if user's roles on tag's parents have permisson:
        return True
    return False

如果 用户 在 业务树节点及其父节点 上的 角色 拥有 权限点,则验权成功;否则,验权失败。

总结

运维权限系统,业务场景相对简单,模型也较固定。更多的工作,需要在使用过程中逐步积累一些运维规范、安全策略、工程优化等等——从这个角度来说,权限系统是一种最佳实践的沉淀。

鸣谢

感谢小米权限系统的小胖保清帮忙讲解 Tyr 的运维规范:-)