PRODPlataforma BaaS soberana europeaAbrir panel →

Ingeniería · 10 lectura mínima

RLS frente a una base de datos dedicada por proyecto

Affane Daylami · Fondateur · 27 de julio de 2026

volver al blog

Busque "RLS postgres multiinquilino" y encontrará el mismo esquema en casi todas partes: una base de datos compartida, una columna de id_inquilino, una política que filtra las filas. Este no es el modelo que utiliza Aurabase para aislar sus proyectos entre sí. Cada proyecto recibe su propia base de datos Postgres 16, nunca compartida con otro cliente; el RLS permanece allí, pero en otro piso: en su base de datos, para sus propios usuarios.

Este texto en inglés se generó automáticamente a partir del original en francés y aún no ha sido revisado.
Esta página fue traducida automáticamente. La versión en inglés es autorizada.

"RLS" y "multi-inquilino" se encuentran uno al lado del otro en casi todo el contenido ya publicado sobre el tema, una elección legítima para muchas arquitecturas SaaS, pero que no es la elección de Aurabase para separar a sus propios clientes entre sí. Esta publicación explica la diferencia con el motor de aprovisionamiento real y las políticas RLS realmente aplicadas, no una descripción de marketing simplificada. Para obtener una descripción general de lo que cubre el motor de Postgres administrado de Aurabase más allá del aislamiento, consulte la documentación de la base de datos .

Lo esencial

  • Entre proyectos, Aurabase aísla mediante la base dedicada de Postgres, nunca solo mediante RLS: cada proyecto tiene su propia base física, en un clúster CNPG dedicado (nivel de empresa) o en el clúster CNPG de su propia organización (libre/profesional/equipo), nunca compartido con otra organización.
  • El RLS (auth.uid(), auth.role(), auth.jwt()) permanece activo y recomendado dentro de su base, para aislar a sus propios usuarios, la misma convención que Supabase.
  • service_role y los roles de administración basados en proyectos evitan RLS por diseño (BYPASSRLS): una elección arquitectónica supuesta para las operaciones del servidor, no una falla.
  • Una regresión ya corregida en este repositorio (derechos PUBLIC concedidos por error en esquemas compartidos antiguos) ilustra concretamente por qué un borde en el nivel base resiste mejor que un borde puramente de aplicación.
#
El modelo predeterminado

El atajo que toman la mayoría de las guías RLS multiinquilino

El patrón más documentado para Postgres multiinquilino consta de tres líneas: una base única, una columna tenant_id en cada tabla, una política RLS que compara esta columna con un valor extraído del JWT. Es económico (un conjunto de conexiones, un diagrama, una sola instancia para ejecutar) y funciona bien cuando los inquilinos son numerosos, pequeños y de bajo interés individual.

El compromiso es real: el límite entre dos clientes se convierte en una expresión SQL, evaluada tabla por tabla. Una política olvidada en una nueva tabla, una conexión ejecutándose con un rol de superusuario, un script de depuración lanzado en vivo: cada uno de estos incidentes, por trivial que sea su funcionamiento, puede exponer silenciosamente las líneas de todos los inquilinos al mismo tiempo. El límite de seguridad y el límite técnico (la base) son entonces exactamente lo mismo.

Información

Esta no es una mala elección en sí misma: es el compromiso correcto para muchos productos. El objetivo de esta publicación está en otra parte: este no es el compromiso que Aurabase ha hecho para separar a sus propios clientes (proyectos completos, potencialmente con diferentes requisitos de cumplimiento) entre sí.

#
en el codigo

Dos arquitecturas, nunca una base compartida entre proyectos

Desde una reciente consolidación del aprovisionador (marcado en el código como "Tarea 12"), un proyecto de Postgres activo en Aurabase cae exactamente bajo dos arquitecturas: los modelos antiguos con una base realmente compartida entre varios proyectos se han eliminado de la ruta de aprovisionamiento.

provisioning/instances.rsrust
pub enum ProjectInstanceKind {
    /// Clúster CNPG dedicado a ESTE ÚNICO proyecto (a nivel de empresa).
    FullyDedicated,

    /// Base de datos sobre el cluster CNPG del proyecto ORGANIZACIÓN —
    /// compartido con OTROS proyectos de la MISMA organización,
    /// nunca con una organización de terceros.
    SharedClusterDedicated,
}

El nivel del proyecto decide cuál de los dos se aplica, y es el código el que decide, no una casilla marcada en un panel:

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)
}

// debería_route_to_fleet(): cualquier nivel que no sea de la empresa (gratis/pro/equipo)
// ruta al cluster CNPG de SU PROPIA organización, nunca a la que
// de otro: consulte migración 073, consolidación en 2 arquitecturas.
DimensionesTotalmente Dedicada (empresa)SharedClusterDedicated (gratis/pro/equipo)
Clúster CNPGDedicado a este proyectoCompartido, pero nunca entre dos organizaciones.
base de datos postgresaplicación, solo proyecto en ellaproject_<uuid>, uno por proyecto en el clúster
Iniciar sesión en PostgreSQLUn único proyecto en el cluster: sin riesgo de membresía cruzadaInicio de sesión por proyecto (F-013), miembro de sus únicos roles inquilino_<uuid>

En ambas arquitecturas, la base o el clúster nunca alberga dos organizaciones diferentes, por lo que la pregunta no es "¿sus datos están aislados" sino "¿su proyecto tiene cómputo CloudNativePG para sí mismo o lo comparte con otros proyectos de la misma organización?".

Esta consolidación de dos arquitecturas es reciente: el código anteriormente llevaba dos rutas adicionales: un "maestro compartido" donde varios proyectos coexistían en la misma base de datos, aislados sólo por diagrama, y una variante postgrest_dedicated_shared_db. Una migración dedicada los eliminó y apretó la restricción de la tabla projects a solo los dos valores restantes, precisamente porque el modelo de esquema compartido fue la fuente del error que se describe a continuación.

#
Límite de seguridad

Por qué una base dedicada supera a una EPIRB compartida entre clientes

Una base de datos de Postgres separada es un límite a nivel de conexión, no un límite a nivel de fila. Una función de aplicación conectada a la base de datos del proyecto A simplemente no puede consultar las tablas del proyecto B: no tiene una sesión abierta. Esta propiedad se mantiene incluso si una política RLS está mal escrita, falta en una tabla o es omitida por una función importante: el peor de los casos permanece confinado dentro de una única base de datos.

Este depósito también lleva la huella de un error real que ilustra el riesgo contrario. Según el antiguo modelo de esquema compartido (ya retirado), provision_postgres_schema otorgó por error derechos a GRANT ALL ... TO PUBLIC para cada esquema de proyecto; PUBLIC aplicó a todos los roles en la base de datos sin condiciones de membresía, un inicio de sesión aislado por proyecto podría leer y escribir en el esquema de otro. Una migración correctiva (066) eliminó estos derechos del existente.

La lección aprendida

El parche no agregó una política RLS más para tapar la fuga: eliminó la posibilidad misma de que dos proyectos compartieran una base de datos. Sobre las dos arquitecturas actuales, un comentario de provisioning.rs lo documenta en blanco y negro: “cada proyecto ya tiene su propia base de datos física de Postgres”. Un límite de nivel básico hace que toda una clase de errores de este tipo sean simplemente inalcanzables, en lugar de depender de que cada política esté siempre escrita correctamente.

Un parche del 23 de agosto de 2026 va en la misma dirección: un REVOKE ALL ON SCHEMA public colocado incondicionalmente por el aprovisionador se condicionó a la topología, porque solo proporcionaba un aislamiento real en el antiguo modelo de base de datos compartida; en las dos arquitecturas actuales, bloqueaba sin beneficio la importación de volcados de SQL que hacen referencia explícita a public.<table>.

#
SPI en la práctica

EPIRB permanece ahí: en su base de datos, para sus usuarios

Nada de lo anterior inutiliza la EPIRB: simplemente cambia de piso. Una vez en la base de datos de su proyecto, Aurabase expone exactamente la convención PostgREST adoptada por Supabase: tres funciones SQL que leen las notificaciones JWT establecidas por la puerta de enlace en request.jwt.claims.

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
$$;

Estos ayudantes se utilizan en las políticas reales de Aurabase, no solo se documentan para la suya. Aquí está la política que protege storage_objects, tal como se coloca en el repositorio (formateada en varias líneas para su lectura):

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)
      )
    )
  );

En su propio esquema project_<uuid>, el que contiene las tablas de su aplicación, Aurabase deliberadamente no coloca ninguna política en su nombre; el código lo documenta como un "modelo Supabase": el RLS de sus tablas sigue siendo su responsabilidad, con las mismas funciones, la misma sintaxis.

#
BYPASSRLS asumió

service_role omite RLS: por diseño, no por accidente

Postgres ofrece de forma nativa un atributo de rol , BYPASSRLS, que ignora todas las políticas. Aurabase lo utiliza voluntariamente en dos familias de roles: aura_service_role (el rol de servidor, nunca expuesto en el lado del navegador) y el rol de administración específico de cada proyecto, utilizado durante operaciones DDL como ALTER SCHEMA ... OWNER TO.

provisioning/roles.sqlsql
create role aura_service_role nologin bypassrls;
-- Rol del servidor: nunca expuesto en el lado del navegador, nunca miembro
-- roles de otro proyecto.

El rol que atiende sus solicitudes anon/authenticated — tenant_<uuid> — no tiene ningún BYPASSRLS: el RLS se le aplica normalmente, sin excepción. Como beneficio adicional, los diagramas del sistema específicos de cada proyecto (_auth, _storage, _platform) reciben un RLS activado sin política; por lo tanto, un rechazo total de forma predeterminada para cualquier función que no sea de omisión, una defensa en profundidad en caso de que una ruta de aplicación acceda a ella algún día por error.

Información

Omitir el RLS con una función de servidor elevada no es exclusivo de Aurabase: es la misma construcción que service_role en el lado de Supabase. El punto no es evitar BYPASSRLS, sino nunca otorgarlo a un rol accesible desde un cliente y limitarlo a un solo proyecto.

Esta función de servidor es parte de una postura más amplia (roles predefinidos, RBAC personalizado, registros de auditoría) que se detalla en la página Seguridad y RBAC.

#
Defensa en profundidad

En un clúster compartido, la base de datos no hace todo el trabajo sola

En el nivel SharedClusterDedicated, varios proyectos de la misma organización coexisten en un único clúster CNPG. La base de datos física ya separa los proyectos entre sí, pero los roles de PostgreSQL (ellos) son objetos globales para el clúster, no para la base de datos. Por lo tanto, Aurabase agrega una capa: un inicio de sesión de PostgreSQL separado por proyecto.

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

// Activo por defecto (F013_PER_PROJECT_AUTHENTICATOR), desactivable
// explícitamente como escape de emergencia.

Cada proyecto se conecta con su propio inicio de sesión, miembro únicamente de sus propios roles tenant_<uuid> / tenant_<uuid>_admin, nunca los de otro proyecto en el mismo clúster. La base de datos ya aísla los datos; Este inicio de sesión por proyecto también aísla la identidad que se conecta a él, de modo que un incidente en un proyecto no le da a su inicio de sesión ninguna membresía para heredar a otro.

#
Para tu arquitectura

RLS solo o base dedicada: cómo decidirse por su propio SaaS

La elección de Aurabase no es una regla universal: es un compromiso para un caso específico: aislar a los clientes entre sí, potencialmente con diferentes requisitos de cumplimiento, en una plataforma que no controlan. Si está creando su propio SaaS, le surge la misma pregunta, en una escala diferente.

  • RLS con tenant_id en una base compartida: relevante cuando sus inquilinos son numerosos, individualmente de baja participación y el costo de una base por inquilino sería desproporcionado. Pruebe cada política con pg_prove, en cada tabla, sin excepción.
  • Base o diagrama dedicado: relevante siempre que un inquilino tenga su propio problema de cumplimiento (salud, recursos humanos, sector público), un volumen que justifique el aislamiento del rendimiento o el costo de una fuga entre dos clientes específicos sería desproporcionado con el costo de la infraestructura adicional.

El nivel de precios de Aurabase aplica este mismo arbitraje a sus propios clientes: base compartida por organización por defecto, cluster dedicado cuando el desafío del proyecto lo justifica. Para patrones RLS dentro de su propia base de datos (propiedad, multiinquilino por organización, jerarquía de roles), la guía RLS en producción detalla los tres casos con pruebas pgTAP. Y si la API generada automáticamente en su diagrama le interesa más allá de REST, la comparación en pg_graphql versus Hasura y PostGraphile cubre la otra mitad de la superficie de Postgres expuesta por Aurabase.

#
Preguntas frecuentes

Preguntas frecuentes

¿Es suficiente RLS por sí solo para aislar a los inquilinos en una única base de datos de Postgres?+
Técnicamente sí, si cada tabla tiene una política correcta y ninguna conexión escapa a la función de no omisión. Este es exactamente el riesgo operativo que Aurabase decidió no asumir entre diferentes proyectos: cada error de política quedaría confinado a una sola base. Dentro de un solo proyecto, para sus propios inquilinos, RLS + tenant_id sigue siendo una opción legítima y ampliamente utilizada.
¿Por qué los proyectos gratuitos no tienen un clúster de Postgres dedicado como el nivel empresarial?+
Un clúster CloudNativePG dedicado por proyecto, incluidos los proyectos gratuitos, multiplicaría el cómputo reservado sin relación alguna con el uso real de la mayoría de ellos. En cambio, Aurabase proporciona a cada proyecto no empresarial su propia base de datos física de Postgres en el clúster CNPG de su propia organización (nunca compartida con otra organización) en lugar de un esquema en una base de datos común entre varios clientes desconocidos entre sí.
¿Cómo sabe auth.uid() quién soy, técnicamente?+
La puerta de enlace de Aurabase verifica su JWT y luego coloca sus reclamos en el parámetro de sesión de Postgres request.jwt.claims, durante la duración de la transacción. auth.uid() no hace más que extraer el subcampo de este JSON y convertirlo a uuid; sin realizar reclamaciones (solicitud anónima o conexión directa fuera de una puerta de enlace), current_setting() devuelve NULL y auth.uid(), por lo tanto, devuelve NULL, lo que cierra el acceso a una política usando (owner_id = auth.uid()).

¿LISTO PARA IMPLEMENTAR?

Tu backend en cinco minutos.

No se requiere tarjeta de crédito · 500 MB gratis · 50,000 MAU