Postgres、PostGraphile、RLS 与 SQL
概述
本堆栈的数据层机制。实现了纵深防御(RLS 作为最终防线)和唯一合法数据路径。堆栈特定——不适用于没有 PostGraphile/Postgres 的项目。
数据路径
浏览器 → 应用服务器 → PostGraphile → PostgreSQL。 恰好一条合法路径。
- 浏览器从不直接与 GraphQL/数据库通信;应用也从不直接查询数据库以获取应用数据。
- 应用仅以标头形式传递会话上下文(用户 ID、组织提示)——应用中不含授权逻辑;上下文在下游验证,不被信任。
- 单一路径 = 单一位置来强制授权上下文、验证和 RLS。侧通道是绕过强制执行的漏洞。
安全规则
- RLS 是最终强制执行层。 应用层的检查只是用户体验;数据库才是壁垒。即使上层存在错误,也不会泄露其他租户的行。
- 角色模拟真实会话状态——未登录、已登录无组织、已登录有组织、仅连接角色、仅迁移角色。角色具有实际意义。
- 特权连接角色从不执行应用查询。 应用查询在受限、受 RLS 约束的角色下运行。
SECURITY DEFINER函数在其定义中内联设置search_path——每次,无一例外。- 缺失值为
NULL,绝不能是空字符串。 禁止使用空字符串哨兵;缺失时应省略该值。 - GraphQL 是 DoS 攻击面——必须加以约束。 强制查询深度限制和成本/复杂度分析;拒绝超出预算的查询。限制分页大小。为应用角色设置数据库**
statement_timeout**。在生产环境中禁用内省,除非故意需要。
SQL 风格
- 每个对象一个文件——每个表、函数、策略、授权集各存于其自身文件中。
- 按
schema/object_type/name组织——目录镜像数据库的组织方式。 - 包含顺序遵循依赖顺序; 包含清单就是目录。
- 内联文档。
COMMENT ON与对象位于同一文件中——说明是什么和为什么。 - 描述性别名(表名的单数形式,而非单个字母);遵循 Postgres 习惯用法(
current_*getter)。 - 类型和面向用户的角色是数据,而非硬编码的约束——使用键/标签/描述构成的查找表,通过外键验证,可在不更改模式的情况下扩展。
快速参考
| 规则 | 检查项 |
|---|---|
新的 SECURITY DEFINER 函数 | 是否内联设置了 search_path? |
| 包含租户数据的新表 | RLS 策略 + 拒绝测试(参见 graphql-contract-testing)? |
| 缺失值 | 是 NULL,不是 ''? |
| 新的 GraphQL 表面 | 深度/成本限制和分页限制? |
| 新对象 | 独立文件、依赖顺序包含、内联 COMMENT ON? |
完整原理:参见工程宪法第 XIII、XIV、XVIII 条,位于 engineering-constitution/references/engineering-constitution.md。迁移:zero-downtime-migrations。契约测试:graphql-contract-testing。