PROD欧洲主权BaaS平台打开仪表板 →

工程 · 10 最小读取值

RLS 与每个项目专用数据库

Affane Daylami · Fondateur · 2026年7月27日

返回博客

搜索“RLS multi-tenant postgres”,您几乎到处都会遇到相同的模式:共享数据库、tenant_idcolumn、过滤行的策略。这不是 Aurabase 用于将其项目相互隔离的模型。每个项目都会收到自己的 Postgres 16 数据库,从未与其他客户共享 - RLS 保留在那里,但在另一层:在您的数据库中,供您自己的用户使用。

该英文文本是根据法文原文自动生成的,尚未经过审查。
该页面已自动翻译。英文版具有权威性。

“RLS”和“多租户”几乎出现在已发布的有关该主题的所有内容中——对于许多 SaaS 架构来说这是一个合理的选择,但这并不是 Aurabase 将自己的客户彼此分开的选择。这篇文章解释了与真实配置引擎和实际应用的 RLS 策略的差异,而不是简化的营销描述。有关 Aurabase 的托管 Postgres 引擎涵盖的隔离之外的内容的概述,请参阅 数据库文档。

要点

  • 在项目之间,Aurabase 通过专用的 Postgres 基础进行隔离,而不仅仅是通过 RLS 进行隔离 - 每个项目都有自己的物理基础,位于专用的 CNPG 集群(公司级别)或其自己组织(免费/专业/团队)的 CNPG 集群上,从不与其他组织共享。
  • RLS(auth.uid()、 auth.role()、 auth.jwt())在 您的 基础中保持活动状态并推荐使用,以隔离您自己的用户 — 与 Supabase 相同的约定。
  • service_role 和基于项目的管理角色通过设计绕过 RLS (BYPASSRLS):服务器操作的假定架构选择,而不是缺陷。
  • 此存储库中已纠正的回归 - 在旧共享模式上错误授予的 PUBLIC 权限 - 具体说明了为什么基础级别的边界比纯粹的应用程序边界具有更好的抵抗力。
#
默认型号

大多数多租户 RLS 指南采用的捷径

多租户 Postgres 记录最多的模式由三行组成:单个基础、每个表上的 tenant_id 列、RLS 策略 ,该策略将此列与从 JWT 中提取的值进行比较。它很经济——一个连接池、一个图表、一个运行实例——而且当租户数量众多、规模较小且个人股权较低时,它效果很好。

妥协是真实的:两个客户端之间的边界变成了 SQL 表达式,逐个表进行评估。忘记在新表上的策略、以超级用户角色运行的连接、实时启动的调试脚本 — 这些事件中的每一个,无论操作起来多么微不足道,都可能同时悄悄地暴露所有租户的线路。那么安全边界和技术边界(基础)是完全相同的。

信息

这本身并不是一个糟糕的选择——对于许多产品来说这是正确的折衷方案。这篇文章的重点在其他地方:这不是 Aurabase 为将自己的客户(整个项目,可能具有不同的合规性要求)彼此分开而做出的妥协。

#
在代码中

两种架构,项目之间从不共享基础

自从最近对配置程序进行了整合(在代码中标记为“任务 12”)以来,Aurabase 中的一个活跃 Postgres 项目恰好属于两种架构 - 具有在多个项目之间实际共享的基础的旧模型已从配置路径中删除。

provisioning/instances.rsrust
pub enum ProjectInstanceKind {
    /// CNPG 集群致力于此唯一项目(公司级别)。
    FullyDedicated,

    /// 项目 ORGANIZATION 的 CNPG 集群上的数据库 —
    /// 与同一组织的其他项目共享,
    /// 绝不与第三方组织合作。
    SharedClusterDedicated,
}

项目级别决定了两者中的哪一个适用 - 决定的是代码,而不是仪表板中选中的框:

provisioning/rules.rsrust
fn should_provision_dedicated(engine: &str, db_url_encrypted: &Option<String>, plan: &str) -> bool {
    matches!(engine, "postgres" | "postgresql")
        && plan == "enterprise"
        && !non_empty(db_url_encrypted)
}

// should_route_to_fleet():任何非公司级别(免费/专业/团队)
// 通往您自己组织的 CNPG 集群的路线,而不是唯一的路线
// 来自另一个 — 请参阅迁移 073,整合到 2 种架构。
尺寸完全奉献(公司)SharedClusterDedicated(免费/专业/团队)
CNPG集群致力于这个项目共享,但绝不在两个组织之间共享
Postgres数据库应用程序,仅在其上进行投影project_<uuid>,集群上的每个项目一个
登录集群上的单一项目:无跨成员风险按项目登录 (F-013),其唯一角色的成员tenant_<uuid>

在这两种架构上,基础或集群永远不会托管两个不同的组织 - 因此问题不是“您的数据是否隔离”,而是“您的项目是否拥有全部计算 CloudNativePG,或者是否与同一组织中的其他项目共享它。”

这种两种架构的整合是最近发生的:之前的代码带有两个额外的路径——一个“共享主”,其中多个项目共存于同一个数据库中,仅通过图表隔离,以及一个 postgrest_dedicated_shared_db变体。专用迁移删除了它们,并将 projects 表的约束收紧为仅剩下的两个值,正是因为共享架构模型是下面描述的错误的根源。

#
安全边界

为什么专用基地优于客户之间共享的 EPIRB

单独的 Postgres 数据库是连接级边界,而不是行级边界。连接到项目 A 数据库的应用程序角色根本无法查询项目 B 的表 — 它没有打开的会话。即使 RLS 策略编写得不好、表中缺失或被高级角色绕过,此属性仍然成立:最坏的情况仍然限制在单个数据库内。

该存款还带有真实错误的痕迹,这说明了相反的风险。在旧的共享模式模型(已撤销)下,provision_postgres_schema 错误地向每个项目模式授予 GRANT ALL ... TO PUBLIC 权限 - PUBLIC 在没有成员资格条件的情况下应用于数据库中的 所有 角色,每个项目隔离的登录可以在另一个项目的模式中读写。纠正性迁移 (066) 从现有权利中删除了这些权利。

吸取的教训

该补丁没有再添加一项 RLS 策略来堵住漏洞,而是消除了两个项目共享数据库的可能性。对于当前的两种架构,provisioning.rs 的评论白纸黑字地记录了这一点:“每个项目已经拥有自己的物理 Postgres 数据库”。基本级别的边界使得整个类别的此类错误根本无法触及,而不是依赖于始终正确编写的每个策略。

2026 年 8 月 23 日的补丁朝着相同的方向发展:由配置者无条件放置的 REVOKE ALL ON SCHEMA public 是在拓扑上有条件的,因为它只在旧的共享数据库模型上提供真正的隔离 - 在当前的两个架构上,它阻止了显式引用 public.<table>的 SQL 转储的导入,但没有任何好处。

#
RLS 实践

EPIRB 保留在您的数据库中,供您的用户使用

上述所有因素都不会导致 EPIRB 毫无用处——它只是改变楼层。一旦进入 您的 项目数据库,Aurabase 就会准确地公开 Supabase所采用的 PostgREST 约定:三个 SQL 函数,它们读取 request.jwt.claims中网关设置的 JWT 声明。

libs/aura-migrations/golden/auth_global.sqlsql
create function auth.uid() returns uuid
    language sql stable security definer
    set search_path to 'pg_catalog', 'pg_temp'
    as $$
    select nullif(nullif(current_setting('request.jwt.claims', true), '')::json->>'sub', '')::uuid
$$;

这些助手用于 Aurabase 本身的实际政策中——而不仅仅是为您的政策记录。这是保护 storage_objects的策略,因为它被放置在存储库中(重新格式化为几行以供阅读):

libs/aura-migrations/golden/storage.sqlsql
alter table storage_objects enable row level security;

create policy storage_objects_select on storage_objects
  for select using (
    (owner_id = auth.uid())
    or (
      exists (
        select 1 from storage_buckets b
        where (b.name = storage_objects.bucket_name and b.public)
      )
    )
  );

在您自己的 project_<uuid>模式(承载应用程序表的模式)中,Aurabase 故意不代表您设置任何策略 - 代码将其记录为“Supabase 模型”:表的 RLS 仍然由您负责,具有相同的功能、相同的语法。

#
假设采用 BYPASSRLS

service_role 绕过 RLS — 有意为之,而非偶然

Postgres 原生提供 角色属性、 BYPASSRLS,它会忽略所有策略。 Aurabase 自愿在两个角色系列上使用它: aura_service_role (服务器角色,从不在浏览器端公开)和特定于每个项目的管理角色,在 DDL 操作期间使用,如 ALTER SCHEMA ... OWNER TO。

provisioning/roles.sqlsql
create role aura_service_role nologin bypassrls;
-- 服务器角色:从不暴露在浏览器端,从不是成员
-- 来自另一个项目的角色。

满足您的请求 anon/authenticated — tenant_<uuid> — 的角色没有任何 BYPASSRLS:RLS 通常适用于它,无一例外。作为奖励,特定于每个项目的系统图(_auth、 _storage、 _platform)会收到一个激活的 RLS ,而没有策略 ——因此默认情况下完全拒绝任何非旁路角色,这是一种深度防御,以防有一天应用程序路径错误地访问它。

信息

使用提升的服务器角色绕过 RLS 并不是 Aurabase 独有的 — 它与 Supabase 端的 service_role 具有相同的构造。重点不是要避免 BYPASSRLS,而是永远不要将其授予可从客户端访问的角色,并将其限制在单个项目中。

此服务器角色是更广泛的状态(预定义角色、自定义 RBAC、审核日志)的一部分,详细信息请参见 安全和 RBAC页面。

#
纵深防御

在共享集群上,数据库不会单独完成所有工作

在 SharedClusterDedicated级别,来自同一组织的多个项目共存于单个 CNPG 集群上。物理数据库已经将项目彼此分开,但 PostgreSQL 角色(它们)是集群的全局对象,而不是数据库的全局对象。因此,Aurabase 添加了一个层:每个项目单独的 PostgreSQL 登录。

libs/aura-core/src/lib.rsrust
pub fn project_authenticator_role(project_id: &str) -> String {
    format!("project_{}_authenticator", project_id.replace('-', '_'))
}

// 默认处于活动状态 (F013_PER_PROJECT_AUTHENTICATOR),可停用
// 明确作为紧急逃生。

每个项目都使用自己的登录名进行连接,仅是其自己的 tenant_<uuid> / tenant_<uuid>_admin 角色的成员,而不是同一集群中其他项目的成员。数据库已经隔离了数据;每个项目的登录还隔离了连接到它的身份,因此项目上的事件不会为其登录提供任何继承到另一个项目的成员资格。

#
适合您的架构

单独的 RLS 还是专用基地:如何决定适合您自己的 SaaS

Aurabase 的选择并不是通用规则,而是针对特定情况的妥协:在客户无法控制的平台上将客户彼此隔离,可能具有不同的合规性要求。如果您正在构建自己的 SaaS,您也会遇到同样的问题,只是规模不同。

  • RLS 与 tenant_id 位于共享基地 中 — 当您的租户众多、个体风险较低且每个租户的基地成本不成比例时,此方法非常有用。在每个表上使用 pg_prove测试每个策略,无一例外。
  • 专用基础或图表 — 每当租户有自己的合规问题(健康、人力资源、公共部门)、证明性能隔离合理的卷或两个特定客户端之间的泄漏成本与额外基础设施的成本不成比例时,相关的。

Aurabase 定价水平对其自己的客户应用相同的仲裁:默认情况下由组织共享基础,当项目的挑战证明其合理时专用集群。对于您自己的数据库内的 RLS 模式(所有权、组织多租户、角色层次结构),生产环境中的 RLS 指南 详细介绍了 pgTAP测试的三种情况。如果您对图表上自动生成的 API 感兴趣而超出了 REST,那么 pg_graphql 与 Hasura 和 PostGraphile 的比较涵盖了 Aurabase 暴露的 Postgres 表面的另一半。

#
常见问题解答

常见问题解答

单独使用 RLS 是否足以隔离单个 Postgres 数据库中的租户?+
从技术上讲,是的,如果每个表都带有正确的策略并且没有连接逃脱非旁路角色。这正是 Aurabase 选择不在不同项目之间承担的运营风险——每个政策错误都将仅限于单个基地。在单个项目中,对于您自己的租户,RLS +tenant_id 仍然是合法且广泛使用的选择。
为什么免费项目没有像企业层那样有专用的 Postgres 集群?+
每个项目(包括免费项目)的专用 CloudNativePG 集群将增加预留的计算量,而与大多数计算量的实际使用没有任何关系。相反,Aurabase 在其自己组织的 CNPG 集群上为每个非企业项目提供了自己的物理 Postgres 数据库(从未与其他组织共享),而不是在多个彼此未知的客户端之间的公共数据库中提供模式。
从技术上讲,auth.uid() 如何知道我是谁?+
Aurabase 网关会验证您的 JWT,然后在交易期间将其声明放入 Postgres 会话参数 request.jwt.claims 中。 auth.uid() 只不过是从此 JSON 中提取子字段并将其转换为 uuid - 在没有提出声明(匿名请求或网关外部直接连接)的情况下,current_setting() 返回 NULL,因此 auth.uid() 返回 NULL,这会使用 (owner_id = auth.uid()) 关闭对策略的访问。

准备好部署了吗?

五分钟内完成您的后端。

无需信用卡 · 500 MB 免费 · 50,000 MAU