亚马逊AWS官方博客

开启云端零信任架构之旅 – 基于 Cedar 开源语言编写和执行自定义授权策略

本篇博客翻译自 Using Open Source Cedar to Write and Enforce Custom Authorization Policies,作为中文“开启云端零信任架构之旅”系列博客的 Cedar 开源语言学习资料。希望可以帮助中文用户快速熟悉云原生的权限策略描述语言。

Cedar 是一款开源的策略描述语言和软件开发套件(SDK),可为您的应用程序编写和执行授权策略。开发人员能够使用 Cedar 执行细粒度的权限管理,例如图片共享软件中的单个图片,微服务集群中计算节点,以及自动化工作流系统中的单个组件等。您可以将细粒度授权定义为 Cedar 的策略,由应用程序调用 Cedar SDK 的授权引擎来获取授权。Cedar 的语法简单而富有表现力,支持常见的授权范式,包括基于角色的访问控制(RBAC)和基于属性的访问控制(ABAC)。由于 Cedar 策略独立于应用程序代码,因此它们可以独立编写、分析和审计,甚至可以在多个应用程序之间共享。Cedar 支持无缝集成 Amazon Verified Permissions 和  AWS Verified Access 等服务。

在本篇博客中,我们将通过样例应用程序 TinyTodo,来介绍 Cedar 和其 SDK 开发工具包的使用。TinyTodo 是一个可以帮助用户和团队来组织、跟踪和共享待办事项清单的应用程序。我们将展示如何采用 Cedar 策略来表述 TinyTodo 的权限,并展示如何使用 Cedar 授权引擎来为 TinyTodo 的合规用户授予访问权限。更多详细信息可查阅 TinyTodo 代码

TinyTodo 应用程序

TinyTodo 允许个人(Users)和团队(Teams)去组织、跟踪和共享他们的待办事项列表。用户(Users)可以创建列表(Lists),在列表中填充任务。任务完成后,可以勾选对应列表,标记完成。

TinyTodo 权限

我们通常希望区分不同用户查看或更改任务列表的权限,TinyTodo 采用 Cedar 来控制每个人的访问对象范围。列表的创建者(owner)可以将列表分享给其他用户或团队,授权只读(reader)或编辑(editor)权限。只读模式可以获取列表及其中的详细任务信息,编辑模式除具备只读权限外,还可以编辑列表,包括添加新任务和删除已有任务等。

我们接下来用 Cedar 来描述并执行访问权限。下面是 TinyTodo 的 Cedar 策略之一:

策略 1:用户对自己的任务列表具备完全权限

// policy 1: A User can perform any action on a List they own
permit(principal, action, resource)
when {
    resource has owner && resource.owner == principal
};

该策略表示,只要列表资源具有 “owner” 属性,并且访问用户(principal user)是该列表资源的所有者(owner),则该用户可以对该列表执行任何操作。

下面是 TinyTodo 的另一个 Cedar 策略:

策略 2:只读模式或编辑模式的用户可查看授权的任务列表

// policy 2: A User can see a List if they are either a reader or editor
permit (
    principal,
    action == Action::"GetList",
    resource
)
when {
    principal in resource.readers || principal in resource.editors
};

该策略表示,列表的只读用户(readers)或编辑用户(editors)均可读取任务列表的内容(Action:: “getList”)。

Cedar 授权策略的默认行为是拒绝(deny):只有明确允许(permit)策略授权,该请求才会被允许。

完整的策略可以在 TinyTodo 的 policies.cedar 文件找到(下文讨论)。更多 Cedar 语法和功能,请查看 Cedar 在线教程:https://www.cedarpolicy.com/

构建 TinyTodo

构建 TinyTodo 程序需要安装 Rust 和 Python3 以及 Python3 的 “requests” 模块。请按照以下步骤,下载并构建 TinyTodo 代码:

> git clone https://github.com/cedar-policy/tinytodo 
...downloading messages here 
> cd tinytodo 
> cargo build 
...build messages here

运行 “cargo build” 命令将自动下载并封装 Cedar 的 Rust 项目包,包括 Cedar-policy-core、cedar-policy-validator 和其他关联组件。并从 Rust 标准包 crates.io 注册,构建 TinyTodo 服务器 tiny-todo-server,我们可以通过 TinyTodo CLI 的 python 脚本 tinytodo.py 与服务器交互。基本架构如图 1 所示:

图表 1 TinyTodo 应用程序架构图

运行 TinyTodo

我们来运行一下 TinyTodo。首先启动服务器,然后用 “andrew” 的用户身份新建一个名为“Cedar blog post”的待办列表,并在该列表中添加两个任务,并完成其中一项。

> python -i tinytodo.py 
>>> start_server() 
TinyTodo server started on port 8080
>>> set_user(andrew) 
User is now andrew 
>>> get_lists() 
No lists for andrew 
>>> create_list("Cedar blog post") 
Created list ID 0
>>> get_list(0)
=== Cedar blog post === 
List ID: 0 
Owner: User::"andrew"
 Tasks:
 >>> create_task(0,"Draft the post") 
 Created task on list ID 0
 >>> create_task(0,"Revise and polish") 
 Created task on list ID 0
 >>> get_list(0)
 === Cedar blog post === 
 List ID: 0 
 Owner: User::"andrew" 
 Tasks:
 1. [ ] Draft the post 
 2. [ ] Revise and polish 
 >>> toggle_task(0,1) 
 Toggled task on list ID 0
 >>> get_list(0)
 === Cedar blog post === 
 List ID: 0 
 Owner: User::"andrew" 
 Tasks:
 1. [X] Draft the post 
 2. [ ] Revise and polish

“get_list”、“create_task” 和 “toggle_task” 命令均由上文提到的 Cedar 策略 1 授权:由于 andrew 是列表 ID 0 的所有者,因此他可以对列表 0 具有执行任何操作的权限。

接下来,我们将继续完成以下任务(TinyTodo 的团队用户身份和关系已按图表 2 完成配置):

图表 2 TinyTodo 的组织关系图

  1. 继续采用 “andrew” 的身份,将任务列表共享给实习生团队(intern),授予只读模式权限。
  2. 切换用户身份为 “aaron”,读取任务列表,并尝试完成剩下的任务,但该操作将被拒绝,因为 “aaron” 的实习生团队的一员,只具备查看任务列表权限,不能编辑。
  3. 切换用户身份为 “kesha” ,并尝试查看列表。该操作将被拒绝,因为 “kesha” 是“temp”团队成员,并未获得任何授权。
>>> share_list(0,interns,read_only=True) 
Shared list ID 0 with interns as reader 
>>> set_user(aaron) 
User is now aaron 
>>> get_list(0) 
=== Cedar blog post === 
List ID: 0 
Owner: User::"andrew" 
Tasks: 
1. [X] Draft the post 
2. [ ] Revise and polish 
>>> toggle_task(0,2) 
Access denied. User aaron is not authorized to Toggle Task on [0, 2] 
>>> set_user(kesha) 
User is now kesha 
>>> get_list(0) 
Access denied. User kesha is not authorized to Get List on [0] 
>>> stop_server() 
TinyTodo server stopped on port 8080

如以上执行代码所示:“aaron” 的 “get_list” 命令是由上文的 Cedar 策略 2 授权的,因为 “aaron” 是 interns 团队的成员,“andrew” 为列表 0 授予了只读权限。而“aaron” 的 “toggle_task” 操作和 “kesha” 的 “get_list” 操作都被拒绝,因为策略没有为他们的操作授权。

用管理员权限扩展 TinyTodo 的授权策略

因为 Cedar 策略是单独定义和维护的,所以我们可以在不更改程序代码的情况下直接更新策略,请将以下策略添加到 policies.cedar 文件的末尾,为管理员角色授权:

permit( 
principal in Team::"admin", 
action, 
resource in Application::"TinyTodo");

该策略表示,管理员团队(Team:: “Admin”)的任何成员用户都可以对 TinyTodo 应用的所有列表执行任意操作。由于用户 “emina” 是管理团队的成员用户(参见图 2),如果我们重启 TinyTodo 程序应用新的策略后,那么 “emina”用户将能够查看、编辑以及删除任何任务列表。

> python -i tinytodo.py
>>> start_server()
=== TinyTodo started on port 8080
>>> set_user(andrew)
User is now andrew
>>> create_list("Cedar blog post")
Created list ID 0
>>> set_user(emina)
User is now emina
>>> get_list(0)
=== Cedar blog post ===
List ID: 0
Owner: User::"andrew"
Tasks:
>>> delete_list(0)
List Deleted
>>> stop_server()
TinyTodo server stopped on port 8080

执行访问请求

当 TinyTodo 的服务器收到来自客户端的命令,例如 “get_list” 或 “toggle_task”时,它会通过调用 Cedar 授权引擎来确认是否允许执行该请求。服务器会将请求信息转换为 Cedar 范式的请求,并将其与关联数据一起传递给 Cedar 授权引擎,由授权引擎判断允许或拒绝请求。

下方是用 Rust 编写的 TinyTodo 服务器代码,每个命令都有相应的处理程序。执行命令之前,将会首先调用 “self.is_authorized” 来授权请求。代码如下:

pub fn is_authorized(
   &self,
   principal: impl AsRef<EntityUid>,
   action: impl AsRef<EntityUid>,
   resource: impl AsRef<EntityUid>,
) -> Result<()> {
   let es = self.entities.as_entities();
   let q = Request::new(
       Some(principal.as_ref().clone().into()),
       Some(action.as_ref().clone().into()),
       Some(resource.as_ref().clone().into()),
       Context::empty(),
   );
   info!("is_authorized request: …”);
   let resp = self.authorizer.is_authorized(&q, &self.policies, &es);
   info!("Auth response: {:?}", resp);
   match resp.decision() {
       Decision::Allow => Ok(()),
       Decision::Deny => Err(Error::AuthDenied(resp.diagnostics().clone())),
   }
}

Cedar 授权引擎存储在变量 “self.authorizer” 中,通过 “self.authorizer.is_authorized(&q, &self.policies, &es)” 调用。其中:

  1. 参数 “&q” 是访问请求,是否用户(principal)可以对上下文为空的资源执行操作。如之前代码样例所示的:用户 “kesha”(User:: “kesha”)能否在资源列表(List::“0″)上执行操作(Action::“getList”)。备注:这里用的 Type:: “id” 表示 Cedar 的 entity UID,代码中的 Rust 类型为 cedar_policy::entityUID。
  2. 参数 “&self.policies” 是引擎在决定请求时参考的 Cedar 策略集,这些策略会在服务器启动时加载。
  3. 参数 “&es” 是引擎在查阅策略时用于比对的实体。这些数据对象是策略涉及的 TinyTodo 的用户(Users)、团队(Teams)和列表(Lists)等。

如果 Cedar 授权返回的决定为允许(Decision:: Allow),那么向 TinyTodo 服务端提交的请求就会被处理;如果决定为拒绝 (Decision:: Deny),则访问请求就会被拒绝。所有请求及其返回结果,将通过调用“info!(…)”来记录日志。

更多信息

以上内容,仅仅通过 TinyTodo 应用程序展示了一部分 Cedar SDK 的功能。如果感兴趣的话,可以在 Tinytodo 源代码目录的 “TUTORIAL.md” 中找到完整的教程,里面包含:

  1. 全套 TinyTodo 的 Cedar 策略;
  2. TinyTodo 的 Cedar 数据模型,如: TinyTodo 如何将用户、团队、列表和任务的信息存储为 Cedar 的实体;
  3. 如何将 TinyTodo 访问请求的结构和数据模型,转换为 Cedar schema,并使用 Cedar SDK 的验证器(validator)来确保策略符合 Cendar schema 规范;
  4. 其他为完善 TinyTodo 功能所面临的挑战性问题。

Cedar 及开源资源

Cedar 是 Amazon Verified Permissions 和  AWS Verified Access 等托管服务使用的授权策略语言。目前 Cedar SDK 已在 GitHub 上开源发布,亚马逊云科技在 Cedar 的开发中保持透明,并真诚邀请社区贡献者参与,我们在 Cedar 的安全性方面共建信任。

Cedar 完整代码可在 https://github.com/cedar-policy/ 获取。工程的路线图和事件清单都真实地反映项目状态和个人贡献者的成果。我们欢迎通过 GitHub 事件提交问题和功能需求。我们使用“验证指导开发(verification-guided development)”的新流程构建了 Cedar SDK 的核心 SDK 组件(例如 authorizer)。“验证指导开发(verification-guided development)”正如 Amazon Science blog post 的文章解释的那样,能够为工程的安全性提供了额外的保证。如果您希望为这些组件发展做出贡献,可以提交 “request for comments”,并与核心团队联系获得批准。

同时,也欢迎大家加入公开的 Cedar Slack 工作区,自由地提问、评论和建议。大家也可以在线学习 Cedar 教程和开展实践,网址为 https://www.cedarpolicy.com/。

原文作者:Mike Hicks

博客原文:Using Open Source Cedar to Write and Enforce Custom Authorization Policies

本篇译者

刘楚楚

亚马逊云科技解决方案顾问,负责云计算市场探索与挖掘,为客户提供数字化转型咨询,帮助加速业务发展和创新。

倪刚

AWS 资深解决方案架构师,负责企业客户云计算方案架构的咨询和设计,致力于 AWS 云服务在国内和全球的应用和推广。在加入 AWS 之前,曾就职于 IBM、Philips 等公司,对基础架构、数据库等方面有着丰富经验,对金融、医疗行业有深入的了解。

William Yee

亚马逊云科技资深解决方案架构师。主要技术方向为云基础设施,容器和安全。具备全面的云计算技术知识和丰富的企业上云实践经验。