Roles y RLS en Profundidad
El modelo de control de acceso de PostgreSQL se basa en una única idea que confunde a las personas que vienen de otras bases de datos: no existe un concepto separado de "usuario" y "grupo".
Ambos son el mismo objeto subyacente, un rol, y todo lo demás en esta sección, desde GRANT hasta la seguridad a nivel de fila, se construye sobre esa única unificación.
Resumen
- Un rol es el único tipo de principal de PostgreSQL; si se comporta como un "usuario" o un "grupo" depende únicamente de sus atributos y de cómo se otorgan otros roles a él.
- Por qué Importa: Los privilegios a nivel de objeto (
GRANT) y la seguridad a nivel de fila (RLS) responden a dos preguntas diferentes: "¿puede este rol tocar esta tabla?" y "¿qué filas dentro de esa tabla puede ver?", y confundirlas causa brechas de seguridad reales. - Conceptos Clave: rol, membresía, herencia, privilegio, política de seguridad a nivel de fila,
FORCE ROW LEVEL SECURITY. - Cuándo Usar: Diseñar una jerarquía de roles de mínimo privilegio, construir aislamiento de filas multi-inquilino o auditar por qué una consulta devuelve menos filas de las esperadas.
- Limitaciones / Compensaciones: RLS añade un costo real de planificación y ejecución por fila, y los propietarios de tablas las omiten silenciosamente a menos que lo fuerces explícitamente.
- Temas Relacionados: patrones multi-inquilino, agrupación de conexiones y variables de sesión, concesiones de privilegios en tiempo de migración, auditoría de seguridad.
Fundamentos
La documentación más antigua de PostgreSQL todavía se refiere a CREATE USER, pero ese comando es solo un envoltorio de conveniencia: crea un rol con el atributo LOGIN establecido, y nada más.
Un rol sin LOGIN se comporta como lo que otros sistemas llaman un "grupo", existiendo puramente para mantener privilegios que otros roles pueden heredar.
Ese diseño de objeto único significa que GRANT app_readers TO app_api y GRANT SELECT ON orders TO app_readers son el mismo tipo de sentencia, solo que otorgan cosas diferentes a un rol.
Una analogía útil es un sistema de insignias en un edificio: un rol es una insignia, la membresía en otro rol es enganchar una segunda insignia en tu cordón, e INHERIT decide si el lector de insignias revisa ambas insignias automáticamente o solo revisa la que presentas explícitamente.
GRANT, REVOKE y la membresía de roles juntos deciden qué objetos un rol tiene permitido tocar en absoluto: una tabla, un esquema, una función.
La seguridad a nivel de fila es una capa separada y posterior que decide qué filas dentro de un objeto un rol tiene permitido ver, una vez que ya ha superado la verificación a nivel de objeto.
Mecánica e Interacciones
Las comprobaciones de privilegios y las comprobaciones de RLS ocurren en puntos diferentes y responden a preguntas diferentes, que es la distinción más importante en todo este modelo.
Una comprobación de privilegio a nivel de objeto ocurre una vez, cuando el planificador confirma que tu rol tiene derechos SELECT, INSERT, UPDATE o DELETE sobre la tabla.
Una política de seguridad a nivel de fila, una vez habilitada, se integra en el plan de consulta como un filtro implícito, y su predicado se evalúa por fila en lugar de una vez por sentencia.
Eso significa que RLS no es un permiso en el sentido de GRANT; está más cerca de una cláusula WHERE inyectada automáticamente que el rol no puede ver ni eliminar.
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON orders
USING (tenant_id = current_setting('app.tenant_id')::int);USING controla qué filas existentes son visibles para SELECT, UPDATE y DELETE; WITH CHECK controla por separado qué filas se permite que sean una fila nueva o modificada, y omitir WITH CHECK en una política relevante para UPDATE reutiliza silenciosamente la cláusula USING en su lugar.
El propietario de la tabla y cualquier rol con el atributo BYPASSRLS omiten todas las políticas por defecto, lo cual es una elección de diseño deliberada para que los roles de administración y migración no se bloqueen accidentalmente fuera de sus propias tablas.
FORCE ROW LEVEL SECURITY existe específicamente para cerrar esa brecha cuando el rol propietario en sí mismo aún debe estar sujeto a sus propias políticas, lo que importa más cuando el código de la aplicación se ejecuta como el rol propietario.
Consideraciones Avanzadas y Aplicaciones
Las políticas de RLS comúnmente se basan en una variable de sesión configurada una vez por conexión, como current_setting('app.tenant_id'), lo que reintroduce silenciosamente la distinción de estado de sesión cliente/servidor cubierta en otras partes de este manual.
Esa dependencia es exactamente por qué RLS y la agrupación de conexiones necesitan una coordinación deliberada: una conexión agrupada reutilizada entre inquilinos sin restablecer esa variable de sesión puede filtrar filas de un inquilino a la solicitud de otro inquilino.
El rendimiento es el otro lugar donde RLS se examina seriamente, porque una columna no indexada referenciada en la cláusula USING de una política convierte cada consulta contra esa tabla en un filtro implícito por fila sin un índice contra el cual podar.
Indexar las columnas en las que se filtran tus políticas, típicamente tenant_id, no es opcional a escala; es la diferencia entre que RLS no cueste nada extra y que RLS duplique silenciosamente la latencia de la consulta.
Los privilegios por defecto (ALTER DEFAULT PRIVILEGES) resuelven un problema relacionado pero distinto: sin ellos, cada tabla que crea una migración necesita que sus sentencias GRANT se repitan manualmente, y es fácil enviar una tabla nueva sin concesiones, lo que falla de forma segura para la seguridad pero rompe la aplicación en tiempo de ejecución.
| Mecanismo | Fortaleza | Debilidad | Mejor Ajuste |
|---|---|---|---|
GRANT / REVOKE (privilegios de objeto) | Simple, bien entendido, verificado una vez por sentencia | Todo o nada por objeto; no puede restringir a un subconjunto de filas | Controlar qué tablas/esquemas puede tocar un rol en absoluto |
| Políticas de seguridad a nivel de fila | Granular, aplicado incluso contra consultas ad hoc | Costo real por fila; invisible a menos que sepas que debes buscarlo | Aislamiento de filas multi-inquilino, datos con ámbito de inquilino |
Filtrado a nivel de aplicación (WHERE tenant_id = ? en código de aplicación) | Sin costo del lado de la base de datos; más simple de razonar localmente | Tan fuerte como cada ruta de código que recuerde el filtro | Herramientas internas de bajo riesgo donde un filtro omitido no es catastrófico |
Conceptos Erróneos Comunes
- "PostgreSQL tiene conceptos separados de USER y ROLE."
CREATE USERes un envoltorio delgado alrededor deCREATE ROLE ... LOGIN; debajo, usuarios y grupos son el mismo tipo de objeto. - "Las políticas de RLS son un tipo de GRANT."
GRANTdecide si un rol puede tocar una tabla en absoluto; RLS decide qué filas de una tabla ya permitida puede ver, y las dos comprobaciones ocurren en etapas diferentes. - "Habilitar RLS en una tabla la protege automáticamente del propietario." El propietario de la tabla omite RLS por defecto; solo
FORCE ROW LEVEL SECURITYsomete al propietario a sus propias políticas. - "Una política con solo USING también restringe qué filas se pueden escribir." Sin un
WITH CHECKexplícito,UPDATEeINSERTreutilizan la expresiónUSING, que a menudo no es la restricción que realmente pretendías para las escrituras. - "RLS es gratis una vez habilitado." Un predicado de política no indexado se convierte en un filtro implícito por fila sin un índice contra el cual podar, lo que puede ralentizar significativamente cada consulta contra esa tabla.
Preguntas Frecuentes
¿Cuál es la diferencia real entre un "usuario" y un "rol" en PostgreSQL?
No hay ninguna a nivel de objeto: CREATE USER simplemente crea un rol con el atributo LOGIN establecido, por lo que "usuario" y "grupo" son instancias del mismo objeto ROLE.
¿Cómo se relaciona la membresía de roles con la herencia?
La membresía (GRANT roleA TO roleB) hace que roleB sea miembro de roleA, y INHERIT (el valor por defecto) significa que roleB obtiene automáticamente los privilegios de roleA sin necesidad de SET ROLE primero.
¿Qué controla GRANT en comparación con una política de seguridad a nivel de fila?
GRANT controla si un rol puede tocar un objeto en absoluto (una tabla, esquema o función); RLS controla qué filas específicas dentro de una tabla ya permitida puede ver o modificar el rol.
¿Se comprueban los privilegios de objeto y RLS en el mismo punto de la ejecución de la consulta?
No: los privilegios de objeto se comprueban una vez por sentencia durante la planificación, mientras que el predicado de una política de RLS se integra en el plan y se evalúa por fila.
¿El propietario de la tabla tiene que seguir las políticas de RLS en su propia tabla?
No por defecto: los propietarios y los roles con BYPASSRLS omiten todas las políticas a menos que la tabla tenga FORCE ROW LEVEL SECURITY establecido.
¿Cuál es la diferencia entre USING y WITH CHECK en una política?
USING filtra qué filas existentes son visibles para lecturas, actualizaciones y eliminaciones; WITH CHECK gobierna por separado qué filas se permite que resulten de una escritura, y por defecto utiliza la expresión USING si se omite.
¿Por qué RLS depende comúnmente de una variable de sesión como current_setting('app.tenant_id')?
Porque la política necesita algún valor por conexión para compararlo con cada fila, y una GUC de sesión configurada una vez en el momento de la conexión es la forma estándar de pasar ese contexto a la expresión de la política.
¿Puede la agrupación de conexiones romper el aislamiento de inquilinos basado en RLS?
Sí, si una conexión agrupada se reutiliza entre inquilinos sin restablecer la variable de sesión de la que depende la política, lo que puede filtrar filas de un inquilino a la consulta de otro inquilino.
¿Habilitar RLS ralentiza las consultas?
Puede hacerlo, especialmente si la columna referenciada en la política no está indexada, ya que el predicado se ejecuta como un filtro no indexado por fila en cada consulta contra esa tabla.
¿Por qué son importantes los privilegios por defecto (ALTER DEFAULT PRIVILEGES) para las migraciones?
Sin ellos, cada tabla nueva que crea una migración comienza sin concesiones, por lo que un rol de aplicación puede fallar en tiempo de ejecución en una tabla que se creó correctamente pero nunca se concedió explícitamente.
¿Cuándo es el filtrado a nivel de aplicación una alternativa razonable a RLS?
Para herramientas internas de bajo riesgo donde omitir WHERE tenant_id = ? no es catastrófico; en cualquier lugar donde un filtro omitido sea un incidente de seguridad real, la garantía aplicada por el servidor de RLS vale su costo.
¿Es RLS suficiente por sí solo para hacer segura una tabla multi-inquilino?
Solo combinado con un manejo correcto de variables de sesión e indexación: RLS aplica la lógica de la política correctamente, pero una variable de sesión filtrada o no establecida, o un predicado no indexado, socava tanto su seguridad como su rendimiento.
Relacionados
- Conceptos Básicos de Roles - creación de roles de inicio de sesión y de grupo
- Patrones GRANT/REVOKE - mecánica de privilegios a nivel de objeto
- Seguridad a Nivel de Fila - sintaxis de políticas y
USING/WITH CHECKen detalle - Rendimiento de RLS - indexación de columnas de políticas para velocidad
- Omitir RLS y Superusuario - cuándo las políticas no se aplican
Versiones de Stack: Esta página es conceptual y no está vinculada a una versión específica de stack; las mecánicas de roles y seguridad a nivel de fila descritas aquí son consistentes en las versiones principales actuales de PostgreSQL, incluida PostgreSQL 18.4 (línea estable 18, línea de mantenimiento 17).