运维权限系统

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 的运维规范:-)