通过逆向思维解决细粒度授权问题
内容
FGA基础知识### FGA作为增量计算问题### 用SQL构建增量FGA引擎### 没有免费的午餐!### 实现动态FGA模型### 总结
如果你一直在关注安全领域,你可能会注意到**细粒度授权(FGA)**访问控制模型的兴起。访问控制模型决定了谁可以访问应用程序以及他们可以执行哪些操作。这些模型通常需要根据应用程序的特定需求进行定制。FGA提供了一种原则性的方法来构建特定于应用程序的访问控制模型,同时将授权逻辑与应用程序逻辑分离。通过FGA,开发者可以使用声明性语言编写特定于应用程序的访问控制规则。FGA引擎在运行时动态执行这些规则。
FGA最初由Google的Zanzibar系统推广,现在有许多实现,包括开源和商业解决方案。
像任何强大的抽象一样,FGA的实现可能代价高昂。任何FGA平台的核心都是一个计算引擎,它实时评估FGA规则。在一个大型系统中,这个引擎必须在包含数百万个对象的对象图上每秒评估数千个请求。每个授权请求都可能需要进行昂贵的图遍历。
如何在不使云账单飙升的情况下构建这样的系统?在这篇博客中,我们展示了如何通过逆向思维实现这一目标。我们不是从头开始评估每个单独的授权请求,而是预先计算所有相关的授权决策,将授权请求转化为简单的键/值查找。
为了实现这一点,我们需要一种方法来保持预计算结果的更新。对象图不断变化,可能会使之前计算的授权决策失效。为了处理这些变化,我们需要一个计算层,能够增量更新其输出,而无需完全重新计算。为此,我们使用了Feldera,我们的增量SQL引擎。Feldera支持任意SQL查询的增量评估,包括相互递归的查询——这是在SQL中实现迭代图遍历的关键特性。
在本文的其余部分,我们将介绍FGA的基础知识,并使用几行SQL实现一个高性能的FGA引擎。
FGA基础知识
对象图
FGA策略定义在一个对象图上,其中节点表示系统对象,边捕获这些对象之间的关系。以一个简单的文件管理服务为例。该应用程序处理三种类型的对象:users
、user groups
和files
(为简单起见,我们使用单一对象类型对文件和文件夹进行建模),它们通过以下关系连接:
member
- 用户与组之间的关系。parent
- 文件之间的关系,定义了文件夹层次结构。editor
- 组与文件之间的关系,赋予组读取或写入文件的权限。viewer
- 组与文件之间的关系,赋予组读取文件的权限。
我们使用relationship(object1, object2)
表示法来表示关系,例如member(alice, engineering)
、parent(folder1, file1)
、editor(admins, folder1)
等。以下是一个示例对象图,显示了几个用户、组和文件。请注意,对象可以具有属性,例如is_banned
。
规则
FGA策略由从对象图派生新关系的规则组成。规则由前提条件和派生子句组成,前提条件必须满足才能触发规则,派生子句在前提条件满足时成立。
在我们的文件管理器示例中,我们定义了以下派生关系:
group-can-read(group, file)
-group
可以读取file
。group-can-write(group, file)
-group
可以写入file
。user-can-read(user, file)
-user
可以读取file
。user-can-write(user, file)
-user
可以写入file
。
这些关系由以下规则管理:
- 规则1:
editor(group, file) -> group-can-write(group, file)
- 如果组是文件的编辑者,它可以写入该文件。 - 规则2:
group-can-write(group, file1) and parent(file1, file2) -> group-can-write(group, file2)
- 如果组可以写入文件,那么它可以写入其任何子文件。 - 规则3:
viewer(group, file) -> group-can-read(group, file)
- 如果组是文件的查看者,那么它可以读取该文件。 - 规则4:
group-can-write(group, file) -> group-can-read(group, file)
- 对文件的写入权限意味着对同一文件的读取权限。 - 规则5:
group-can-read(group, file1) and parent(file1, file2) -> group-can-read(group, file2)
- 如果组可以读取文件,那么它可以读取其任何子文件。 - 规则6:
member(user, group) and group-can-write(group, file) and (not user.is_banned) -> user-can-write(user, file)
- 如果用户是可以写入文件的组的成员,并且用户未被禁止,那么用户可以写入该文件。 - 规则7:
member(user, group) and group-can-read(group, file) and (not user.is_banned) -> user-can-read(user, file)
- 如果用户是可以读取文件的组的成员,并且用户未被禁止,那么用户可以读取该文件。
根据这些规则,我们可以从上面的示例对象图中派生出对文件f1
的以下访问权限:
user-can-write(emily, f1)
user-can-read(emily, f1)
user-can-write(irene, f1)
user-can-read(irene, f1)
FGA作为增量计算问题
评估授权规则是一项昂贵的操作。每当用户尝试对资源执行操作时,FGA引擎必须检查对象图中是否存在一条从用户到资源的路径,该路径符合授权规则。例如,上图中的红色路径显示了user-can-write(emily, f1)
关系的派生。在图中查找路径的最坏情况复杂度与图的大小(节点数 + 边数)成正比。
而这只是一个授权检查!在一个大规模应用程序中,系统可能需要在每秒处理数千个这样的检查,每个检查的延迟预算为几十毫秒。
增量计算为这一挑战提供了一个优雅的解决方案。 不是为每个请求从头开始评估授权决策,而是预先计算所有授权决策并将其存储在键值存储中,将运行时授权检查简化为简单高效的查找。随着对象图的演变,增量计算确保只有受变化影响的规则派生被更新,避免了完全重新计算。一个高效的增量查询引擎(如Feldera)可以在对象图变化后的几毫秒内更新计算,即使对于非常大的图也是如此,确保运行时授权检查反映系统的当前状态。
用SQL构建增量FGA引擎
是时候写一些SQL了!让我们使用Feldera实现文件管理器授权模型。这里描述的实现可以作为Feldera在线沙箱中的预打包示例以及本地Feldera安装中的示例。
建模对象图
我们从将三种对象类型——用户、组和文件——建模为SQL表开始:
sql
createtable users (
id bigintnotnullprimary key,
name string,
is_banned bool
) with ('materialized'='true');
createtablegroups (
id bigintnotnullprimary key,
name string
) with ('materialized'='true');
createtable files (
id bigintnotnullprimary key,
name string,
-- 父文件夹id;根文件夹为NULL。 parent_id bigint) with ('materialized'='true');
请注意,parent_id
字段建模了文件之间的parent
关系。
接下来,我们建模member
、editor
和viewer
关系:
sql
-- Member关系建模用户在组中的成员资格。createtable members (
id bigintnotnullprimary key,
user_id bigintnotnull,
group_id bigintnotnull) with ('materialized'='true');
-- Editor关系赋予组读取或写入文件的权限。createtable group_file_editor (
group_id bigintnotnull,
file_id bigintnotnull) with ('materialized'='true');
-- Viewer关系赋予组读取文件的权限。createtable group_file_viewer (
group_id bigintnotnull,
file_id bigintnotnull) with ('materialized'='true');
实现规则
我们现在准备实现派生关系。我们从由以下两条规则定义的group-can-write
关系开始。
- 规则1:
editor(group, file) -> group-can-write(group, file)