01Por qué debe garantizarlo la base de datos
En un SaaS con esquema compartido, cada tabla tiene una columna tenant_id y cada consulta debe filtrar por ella. La aplicación puede hacerlo, pero tiene que hacerlo siempre, en cada consulta, escrita por cada ingeniero. Con row-level security (RLS) es PostgreSQL quien aplica el filtro, y una cláusula olvidada no devuelve nada en lugar de devolver datos de otro cliente.
02La configuración mínima
Active RLS en la tabla y cree una política que compare el tenant de la fila con un valor definido para la transacción actual.
<schema.sql
01ALTER TABLE invoices ENABLE ROW LEVEL SECURITY;
02ALTER TABLE invoices FORCE ROW LEVEL SECURITY;
03
04CREATE POLICY tenant_isolation ON invoices
05 USING (tenant_id = current_setting('app.tenant_id')::uuid)
06 WITH CHECK (tenant_id = current_setting('app.tenant_id')::uuid);
USING filtra lo que una consulta puede leer. WITH CHECK bloquea inserts y updates que escribirían una fila en otro tenant. FORCE aplica la política también al propietario de la tabla.
03Definir el tenant en cada petición
La API obtiene el tenant del usuario autenticado y lo define al inicio de cada transacción. El tercer argumento de set_config limita el valor a la transacción, algo importante cuando las conexiones están en un pool.
<TenantScope.java
01@Transactional
02public <T> T inTenant(UUID tenantId, Supplier<T> work) {
03 jdbc.queryForObject("SELECT set_config('app.tenant_id', ?, true)",
04 String.class, tenantId.toString());
05 return work.get();
06}
04Tres errores que rompen el aislamiento
<Conectar como superusuario o con un rol con BYPASSRLS. Las políticas se ignoran sin ningún error. Use un rol dedicado para la aplicación.
<Definir el tenant a nivel de sesión con un pool de conexiones. La siguiente petición en la misma conexión hereda el tenant anterior.
<Olvidar las tablas nuevas. Añada una verificación en las migraciones que falle cuando una tabla con tenant_id no tenga RLS.
RLS es una segunda línea de defensa. Mantenga también los filtros de tenant en la aplicación y pruebe el aislamiento en CI con dos tenants y las mismas consultas.
05Probarlo en CI
<isolation_test.sql
01BEGIN;
02SELECT set_config('app.tenant_id', '00000000-0000-0000-0000-0000000000a1', true);
03SELECT count(*) FROM invoices
04 WHERE tenant_id = '00000000-0000-0000-0000-0000000000b2';
05
06ROLLBACK;
Ejecutamos esta verificación para cada tabla con columna de tenant en cada pull request. Tarda segundos y detecta el único error que costaría un cliente.
[ FOTO ]
SOBRE EL AUTOR[ PLACEHOLDER · Nombre, cargo y una línea de trayectoria ]
< Todos los artículos