feat: 方案设计、数据库和接口设计基本完成
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
# 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 的环境变量。
|
||||
|
||||
## 2. 一次性数据库初始化
|
||||
|
||||
以下由云数据库管理员或基础设施脚本执行;它不属于 NestJS 的业务 migration,因为托管 PostgreSQL 常不允许应用账户创建角色。密码应从密钥管理服务注入,示例中不填写真实密码。
|
||||
|
||||
```sql
|
||||
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;
|
||||
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` 执行,确保新表的所有者一致。若云服务统一由管理员创建 schema,可将 `atlas_migrator` 设为 schema/表所有者或由管理员代为执行 migration;无论哪种方式,应用登录角色都不得成为表所有者。
|
||||
|
||||
## 3. RLS 的运行方式
|
||||
|
||||
迁移为每张带 `workspace_id` 的业务表执行:
|
||||
|
||||
```sql
|
||||
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 是纵深防御,不是授权逻辑的替代品。
|
||||
Reference in New Issue
Block a user