Demesne

Fóir 所运行的授权引擎。访问规则只写一次,放在一份规范文件中,由 Demesne 将其编译为 Postgres 行级安全,因此数据库会对每个查询应用这些规则。我们为 Fóir 构建了它,并以 Apache 2.0 许可证发布。

它的作用

一份 Demesne 规范描述四件事:你的租户结构的形态,执行操作的用户和凭证的种类,它们所操作的表,以及把这些连接起来的关系,例如所有权、角色、共享和组成员身份。

编译器根据这份文件生成行级安全策略,以及这些策略所调用的数据库函数。它还会为 Go 和 TypeScript 生成一份权限映射,用于与行无关的问题(“该用户可以发布吗?”),并生成会话必须携带的声明。修改规范并重新生成,数据库和应用代码就会一起变化。

规则为什么放在数据库里

授权通常位于应用代码中:一个你去调用的服务,或是散布在各个请求处理器里的检查。无论哪种方式,它只保护那些记得去询问的路径。后台作业、新的端点,或在数据库控制台里输入的一条查询,都会绕过它。

Demesne 把决定放在数据上。同一条策略过滤每个查询,不论是哪段代码发出的,所以忘了检查的路径只会取回其调用方有权看到的行。

我们为什么写一个编译器

行级安全是放置规则的合适位置,但手写起来很别扭。多租户产品需要所有权、角色、嵌套分组和共享,并在每张表上重复,而这些模式很容易出现细微的错误。

与 Demesne 模型相同的引擎,包括 Google 的 Zanzibar 和沿袭它的开源项目,都作为独立服务运行。你的应用必须为每个决定调用该服务,并让它保存的关系副本与你的数据保持同步。我们想要这个模型,而不要那个服务,所以 Demesne 的规则在与查询相同的事务中读取你现有的表。

它的输出如何检查

Demesne 的构建会把每个生成的文件与已提交的副本逐字节比较。应用侧的每项权限检查都原样带着对应策略的条件,所以代码一旦与数据库偏离,构建就会失败。测试套件针对真实的 Postgres 运行,安装生成的函数,并逐个用例确认 SQL 与 Go 和 TypeScript 版本一致。

对于已经部署的数据库,demesne diff 会报告规范与线上策略之间的任何差异。

为什么开源

决定谁能读取你的数据的组件,应该是你能够检查的那一个。任何平台都可以说它的租户是隔离的。有了 Demesne,你可以阅读写出我们策略的编译器、它必须通过的测试以及它生成的 SQL,并且可以自己运行全部这些。

Demesne 中没有任何东西专属于 Fóir。它适用于任何 Postgres 数据库,读取你自己的登录提供方所签发的会话,仓库中的指南也介绍了如何在现有 schema 上采用它。对我们的平台没有兴趣的团队同样可以使用它,据我们所知,这是检验这个模型是否站得住脚的最好办法。

它在 Fóir 上意味着什么

Fóir 的访问模型就是一份 Demesne 规范,截至撰写时它管辖约一百张表。一项测试列出我们 schema 中的每张表,只要有表在新增时没有记录它如何受保护的决定,就会失败。

你为自己的项目设定的规则,例如谁拥有一条记录、哪些角色可以读取某个模型,都是这些策略所读取的数据,所以它们同样在数据库内部生效。在数据库中强制执行介绍了策略如何生成、强制执行和测试。

它随你带走

你的项目的 Postgres 导出包含行级安全策略,这些策略复现了你的每个用户能读取哪些行,可直接应用到你现在拥有的数据库上。如果你想把整个模型放在自己的基础设施上,引擎就在那里,其许可证允许你使用。

它不适用的场合

Demesne 只支持 Postgres。它是一个库和一个命令行工具,没有可供调用的托管服务。每条规则都必须能表达为 SQL 条件,而反向的问题(“谁能看到这条记录?”)只能给出保守的回答。仓库中附有与 Zanzibar、Ory Keto、OpenFGA、Cerbos 和 Oso 的对比,说明其中每一个在哪些情况下是更好的选择。

自己去读

规范语言、生成的 SQL 和测试套件都在仓库里。