Hotdry.
ai-security

纯 Postgres 实现细粒度授权:基于关系代数、视图和触发器的 pgFGA

使用 Postgres 原生功能重写 OpenFGA,实现高效的 ReBAC 授权系统,支持多租户场景下的细粒度访问控制。

在现代 SaaS 应用中,细粒度授权是确保数据安全和合规的核心需求。传统的角色 - based 访问控制 (RBAC) 虽简单,但难以处理复杂的多租户场景,如组织与仓库的层次关系。关系 - based 访问控制 (ReBAC) 提供更灵活的解决方案,能基于实体间关系动态评估权限,例如 Google Drive 中的文件夹继承机制。然而,引入如 OpenFGA 等外部授权引擎往往带来数据库同步的复杂性和运维开销。本文探讨一种纯 Postgres 实现 ReBAC 的方法 ——pgFGA,通过关系代数、视图和触发器实现政策存储、评估和元组级访问控制,无需外部依赖。这种方法充分利用 Postgres 的原生能力,简化架构并提升一致性。

ReBAC 的核心在于关系元组(relationship tuples),形式为 {user, relation, object},如 {user:Isaac, member, org:Rover} 表示用户 Isaac 是 Rover 组织的成员。OpenFGA 基于 Google Zanzibar 论文,提供授权模型定义关系继承,例如组织成员自动获得仓库读权限。但在生产环境中,OpenFGA 需要独立的 Postgres 实例,每次主数据库变更(如用户加入组织)都需同步元组到 OpenFGA。这导致事务一致性问题、级联删除复杂,以及手动维护的负担。Rover 团队在实践中发现,这种分离增加了运维成本,尤其在小团队中不经济。

pgFGA 通过将授权逻辑内嵌到主数据库中解决这些痛点。其架构包括三个关键组件:授权模型表、关系元组视图和递归权限检查函数。首先,authz_model 表存储模型定义,使用 TypeID 作为主键,支持版本化管理。表结构如下:

CREATE TABLE "authz_model" (
    "id" text PRIMARY KEY DEFAULT typeid_generate_text('authz') NOT NULL,
    "schema_version" bigint NOT NULL,
    "entity_type" text NOT NULL,  -- 如 'organization'
    "relation" text NOT NULL,     -- 如 'member'
    "subject_type" text,          -- 直接关系的目标类型
    "implied_by" text,            -- 隐含关系,如 'admin' 隐含 'member'
    "parent_relation" text        -- 父关系,如仓库的组织继承
);

此表对应 OpenFGA 的 DSL 模型,例如定义组织角色继承:owner 隐含 admin,admin 隐含 member。插入模型时,可借助 OpenFGA CLI 的 JSON 输出作为胶水代码,确保兼容性。

其次,authz_relationship 视图动态生成元组,避免手动同步。视图从现有表(如 organization_user)union 所有关系,例如:

CREATE VIEW authz_relationship AS
SELECT 'user' AS user_type, ou.user_id AS user_id, ou.role AS relation,
       'organization' AS object_type, ou.organization_id AS object_id
FROM organization_user ou
UNION ALL
SELECT 'repository' AS user_type, r.id AS user_id, 'organization' AS relation,
       'organization' AS object_type, r.organization_id AS object_id
FROM repository r;

通过外键和级联删除,主表变更自动反映到视图中,支持多租户隔离(如每个组织独立命名空间)。为优化性能,可将视图物化为物化视图(materialized view),并在相关表上添加触发器刷新:

CREATE MATERIALIZED VIEW authz_relationship_mv AS
SELECT * FROM authz_relationship;

CREATE TRIGGER refresh_authz_mv
AFTER INSERT OR UPDATE OR DELETE ON organization_user
FOR EACH STATEMENT EXECUTE FUNCTION refresh_materialized_view('authz_relationship_mv', NULL);

这确保元组实时更新,同时利用 Postgres 的触发器机制处理并发。

最后,check_permission 函数是评估核心,使用 PL/pgSQL 递归遍历模型和元组图。函数签名支持版本指定:

CREATE OR REPLACE FUNCTION check_permission(
    model_version bigint DEFAULT NULL,
    user_type text,
    user_id text,
    relation text,
    object_type text,
    object_id text
) RETURNS boolean AS $$
-- 递归逻辑:从直接关系检查,扩展到隐含和父关系
$$ LANGUAGE plpgsql;

实现中,函数优先检查直接元组,若无则递归查询 implied_by 和 parent_relation,形成权限传播路径。例如,查询用户对仓库的 read 权限时,若用户是组织 member,则继承成功。递归深度受 Postgres 配置限制(default_work_mem 等),适合中小规模查询。

在实施 pgFGA 时,以下参数和清单确保落地可靠:

  1. 模型管理:使用环境变量设置当前 schema_version(如 PROD_SCHEMA_VERSION=1)。部署时运行迁移脚本验证模型一致性,避免 ULID 随机性问题。

  2. 性能调优:设置 statement_timeout=5000ms 限制长查询;索引 authz_model (entity_type, relation) 和视图 (user_type, user_id, object_type, object_id)。对于高并发,使用读副本分离查询。

  3. 事务集成:在 DAL 中包裹权限检查,如 BEGIN; INSERT 用户;PERFORM check_permission (...); COMMIT; 利用 Postgres 事务原子性。

  4. 监控要点:追踪 check_permission 执行时间(EXPLAIN ANALYZE),警报 >100ms;监控模型版本漂移(定期校验视图与模型匹配);审计日志记录权限拒绝事件。

  5. 回滚策略:版本化模型支持热更新,若新模型失效,回滚到上一版本并重建视图。测试环境中使用内存表模拟,避免生产影响。

  6. 扩展清单:支持条件元组时,添加环境属性列到模型表;多租户隔离通过 row-level security (RLS) 策略实现,如 ALTER TABLE authz_model ENABLE ROW LEVEL SECURITY; CREATE POLICY org_isolation ON authz_model USING (object_type = current_setting ('app.current_org'))。

pgFGA 的优势在于零额外依赖,降低延迟(单数据库查询 <10ms)和成本(无需独立服务)。在 Rover 的多租户场景中,它无缝处理组织 - 仓库继承,确保成员仅访问所属资源。限制包括视图维护的手动性(未来可自动化生成)和复杂模型的递归开销,但对大多数应用已足够。相比外部引擎,pgFGA 更易于小团队维护,并与 Postgres 生态深度集成。

总之,这种纯 Postgres ReBAC 实现证明了关系数据库在授权领域的潜力。通过视图和触发器的巧用,它实现了高效的政策评估和元组控制,推动了安全系统的简化。

资料来源

  • Rover Substack: "How we rewrote OpenFGA in pure Postgres" (2025-03-20)
  • GitHub: DocumaticAI/pgfga (MIT 许可,开源实现)
查看归档