Seguridad a Nivel de Fila
La Seguridad a Nivel de Fila (RLS) filtra qué filas puede ver o modificar cada consulta, evaluado por sentencia basándose en las políticas adjuntas a las tablas.
Receta
ALTER TABLE app.orders ENABLE ROW LEVEL SECURITY;
ALTER TABLE app.orders FORCE ROW LEVEL SECURITY;
CREATE POLICY orders_tenant_isolation ON app.orders
FOR ALL
TO app_api
USING (tenant_id = current_setting('app.tenant_id')::bigint)
WITH CHECK (tenant_id = current_setting('app.tenant_id')::bigint);-- La aplicación establece el inquilino por solicitud (seguro con pool de conexiones usando SET LOCAL)
BEGIN;
SET LOCAL app.tenant_id = '42';
SELECT * FROM app.orders;
COMMIT;Cuándo usar esto: SaaS multi-inquilino en un esquema compartido, defensa en profundidad cuando los errores de la aplicación podrían omitir WHERE tenant_id, o separación regulatoria dentro de una sola base de datos.
Ejemplo de Trabajo
CREATE TABLE app.invoices (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
tenant_id bigint NOT NULL,
amount numeric(12,2) NOT NULL
);
ALTER TABLE app.invoices ENABLE ROW LEVEL SECURITY;
-- Política de solo lectura para analistas
CREATE POLICY invoices_select ON app.invoices
FOR SELECT TO app_ro
USING (tenant_id = ANY (current_setting('app.allowed_tenants')::bigint[]));
-- Política de escritura de la aplicación
CREATE POLICY invoices_write ON app.invoices
FOR INSERT TO app_api
WITH CHECK (tenant_id = current_setting('app.tenant_id')::bigint);
CREATE POLICY invoices_update ON app.invoices
FOR UPDATE TO app_api
USING (tenant_id = current_setting('app.tenant_id')::bigint)
WITH CHECK (tenant_id = current_setting('app.tenant_id')::bigint);Lo que esto demuestra:
- Políticas separadas por comando (
SELECT,INSERT,UPDATE) y rol USINGfiltra filas existentes;WITH CHECKvalida filas nuevas/modificadasSET LOCALlimita GUC a la transacción - seguro con el pooling de transacciones de PgBouncer
Inmersión Profunda
Comandos de Política
Cláusula FOR | Se aplica a |
|---|---|
ALL | SELECT, INSERT, UPDATE, DELETE |
SELECT | Visibilidad de lectura |
INSERT | Solo WITH CHECK |
UPDATE | USING + WITH CHECK |
DELETE | USING |
ENABLE vs FORCE
ALTER TABLE t ENABLE ROW LEVEL SECURITY; -- el propietario omite a menos que se use FORCE
ALTER TABLE t FORCE ROW LEVEL SECURITY; -- el propietario también está sujeto a las políticas- El propietario de la tabla y los superusuarios omiten RLS a menos que se use
FORCE. - Las tablas de inquilinos en producción deben usar
FORCEcuando el propietario sea solo un rol de migración.
Patrones Multi-Inquilino
-- Variable de sesión (común con ORMs)
SET LOCAL app.tenant_id = '99';
-- Reclamación JWT a través de GUC personalizado establecido en el inicio de la conexión (hook de aplicación)
-- current_setting('request.jwt.claim.tenant_id', true)
-- Política de subconsulta para jerarquía de organizaciones
USING (tenant_id IN (SELECT tenant_id FROM app.user_tenants WHERE user_id = current_user_id()))Errores Comunes
- Olvidar GUC SET tenant - La política evalúa la configuración NULL; devuelve cero filas o deniega todo. Solución: El middleware establece GUC en cada solicitud; fallar cerrando si no está configurado.
- Omisión del propietario sin FORCE - Las migraciones ejecutadas como propietario ven todas las filas en psql. Solución:
FORCE ROW LEVEL SECURITYen tablas de inquilinos. - Falta WITH CHECK en INSERT - El usuario inserta una fila para otro inquilino. Solución: Reflejar la expresión
USINGenWITH CHECK. - RLS + vistas SECURITY DEFINER - Una vista elevada puede filtrar filas. Solución: Vistas de barrera de seguridad o evitar atajos de definidor.
- GUC obsoleto del pool de conexiones -
SETsinLOCALfiltra el inquilino entre solicitudes. Solución: Siempre usarSET LOCALdentro de la transacción.
Alternativas
| Alternativa | Usar cuando | No usar cuando |
|---|---|---|
| Esquema por inquilino | Aislamiento estricto, menos consultas entre inquilinos | Miles de inquilinos |
| Base de datos por inquilino | Aislamiento máximo | Alto costo operativo |
WHERE tenant_id solo en la aplicación | Inquilino único simple | El cumplimiento requiere aislamiento forzado por la base de datos |
Preguntas Frecuentes
¿Costo de rendimiento de RLS?
Pequeño cuando las columnas de política están indexadas. Ver la página de Rendimiento de RLS para patrones de índices.
¿Superusuario y RLS?
El superusuario omite RLS a menos que se use FORCE e incluso entonces el superusuario todavía omite - usar roles de aplicación que no sean superusuario.
¿Probar RLS en CI?
Establecer GUC, afirmar recuentos de filas por fixture de inquilino, intentar INSERT entre inquilinos esperando un fallo.
¿FOR ALL vs políticas separadas?
FOR ALL es conciso; las políticas separadas permiten diferentes expresiones por comando y rol.
Relacionados
- Omitir RLS y Superusuario - quién escapa a las políticas
- Rendimiento de RLS - indexar columnas de políticas
- Conceptos Básicos Multi-Inquilino - contexto de arquitectura
Versiones de Stack: Esta página fue escrita para PostgreSQL 18.4 (estable 18, mantenimiento 17), pgvector 0.8+, PgBouncer 1.x, Patroni 3.x, y PostGIS 3.5+.