Files
atlas-seller-kit/docs/architecture/database-security.md
T

4.6 KiB

PostgreSQL 角色与 RLS 执行约束

1. 目标

数据库迁移和日常应用访问必须使用不同的登录身份,避免应用连接碰巧成为表所有者而静默绕过工作空间 RLS。

身份 用途 表所有权 日常数据访问
atlas_migrator 执行 schema migration 拥有 atlas schema 和其中的表 仅部署时使用
atlas_app_login API、BullMQ Worker 连接数据库 不拥有任何业务表 通过 atlas_app 获得最小 DML 权限,受 RLS 限制
atlas_app 无登录的权限组角色 不拥有任何业务表 承载 API/Worker 的表和序列权限

atlas_app_login 和 atlas_app 都必须是 NOBYPASSRLS,也不能是 PostgreSQL 超级用户。生产环境不得把 atlas_migrator 的连接串写入 API 或 Worker 的环境变量。

角色链与请求上下文

三层角色链是固定的,不能简化为“应用直接使用迁移账号”:

atlas_migrator ──拥有──> atlas schema 与业务表

atlas_app_login ──登录身份,NOINHERIT──> SET LOCAL ROLE atlas_app
atlas_app       ──无登录权限组──────────> 最小 DML 与序列权限

API 和 Worker 只以 atlas_app_login 建立连接。每笔业务事务必须先执行 SET LOCAL ROLE atlas_app,再设置 SET LOCAL app.workspace_id。前者收窄为 应用所需的最小数据库权限,后者为 RLS 策略提供当前工作空间上下文;任何一步缺失, 都不应使跨工作空间数据变得可见。

2. 一次性数据库初始化

以下由云数据库管理员或基础设施脚本执行;它不属于 NestJS 的业务 migration,因为托管 PostgreSQL 常不允许应用账户创建角色。密码应从密钥管理服务注入,示例中不填写真实密码。

CREATE ROLE atlas_migrator LOGIN NOINHERIT NOBYPASSRLS PASSWORD '<managed-secret>';
CREATE ROLE atlas_app NOLOGIN NOINHERIT NOBYPASSRLS;
CREATE ROLE atlas_app_login LOGIN NOINHERIT NOBYPASSRLS PASSWORD '<managed-secret>';

GRANT atlas_app TO atlas_app_login;
GRANT CREATE ON DATABASE atlas TO atlas_migrator;
CREATE SCHEMA IF NOT EXISTS atlas AUTHORIZATION atlas_migrator;

GRANT USAGE ON SCHEMA atlas TO atlas_app;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA atlas TO atlas_app;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA atlas TO atlas_app;

ALTER DEFAULT PRIVILEGES FOR ROLE atlas_migrator IN SCHEMA atlas
    GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO atlas_app;
ALTER DEFAULT PRIVILEGES FOR ROLE atlas_migrator IN SCHEMA atlas
    GRANT USAGE, SELECT ON SEQUENCES TO atlas_app;

首次 DDL 与后续 migration 都以 atlas_migrator 执行,确保新表的所有者一致。迁移角色拥有数据库级 CREATE 的唯一目的,是创建其拥有的 atlas schema;它不授予 API 或 Worker 任何额外权限。若云服务统一由管理员创建 schema,可将 atlas_migrator 设为 schema/表所有者或由管理员代为执行 migration;无论哪种方式,应用登录角色都不得成为表所有者。

3. RLS 的运行方式

迁移为每张带 workspace_id 的业务表执行:

ALTER TABLE atlas.example ENABLE ROW LEVEL SECURITY;
ALTER TABLE atlas.example FORCE ROW LEVEL SECURITY;

FORCE 会让表所有者的普通 SELECT / INSERT / UPDATE / DELETE 也遵守策略,避免错误配置在通常情况下被掩盖。PostgreSQL 超级用户和拥有 BYPASSRLS 属性的角色仍能绕过它,因此它们只可用于受控运维,不可用于 API 或 Worker。

API 的数据库事务顺序固定为:验证登录用户与工作空间成员关系 → 开启事务 → SET LOCAL app.workspace_id = '<workspace UUID>' → 执行业务 SQL → 提交/回滚。SET LOCAL 只在当前事务内有效,连接归还连接池后不会遗留工作空间上下文。

Worker 没有浏览器会话,也必须先从受控任务记录解析出工作空间,再在每一笔业务事务中设置相同的 app.workspace_id。跨工作空间的运维任务使用专门的受控运维连接,不复用应用连接池。

4. 部署检查

  • 使用迁移连接串运行 DDL;使用应用连接串启动 API 与 Worker。
  • 在 staging 中用应用连接验证:未设置 app.workspace_id 时查询不到业务数据;设置 A 工作空间后不能读取 B 的记录。
  • 检查 pg_roles.rolbypassrls,确保应用角色为 false;检查 pg_tables.tableowner,确保应用角色不是业务表所有者。
  • 业务服务仍要做成员授权、资源归属和平台/门店校验;RLS 是纵深防御,不是授权逻辑的替代品。