Segurança em Nível de Linha
A Segurança em Nível de Linha (RLS) filtra quais linhas cada consulta pode ver ou modificar, avaliada por instrução com base em políticas anexadas a tabelas.
Receita
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);-- Aplicação define o tenant por requisição (seguro com pool de conexões usando SET LOCAL)
BEGIN;
SET LOCAL app.tenant_id = '42';
SELECT * FROM app.orders;
COMMIT;Quando usar isso: SaaS multi-tenant em um schema compartilhado, defesa em profundidade quando bugs na aplicação poderiam omitir WHERE tenant_id, ou separação regulatória dentro de um único banco de dados.
Exemplo de Trabalho
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 somente leitura 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 escrita da aplicação
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);O que isso demonstra:
- Políticas separadas por comando (
SELECT,INSERT,UPDATE) e role USINGfiltra linhas existentes;WITH CHECKvalida linhas novas/modificadasSET LOCALescopa GUC para a transação - seguro com pooling de transação do PgBouncer
Mergulho Profundo
Comandos de Política
Cláusula FOR | Aplica-se a |
|---|---|
ALL | SELECT, INSERT, UPDATE, DELETE |
SELECT | Visibilidade de leitura |
INSERT | Somente WITH CHECK |
UPDATE | USING + WITH CHECK |
DELETE | USING |
ENABLE vs FORCE
ALTER TABLE t ENABLE ROW LEVEL SECURITY; -- proprietário ignora a menos que FORCE
ALTER TABLE t FORCE ROW LEVEL SECURITY; -- proprietário também está sujeito às políticas- O proprietário da tabela e o superusuário ignoram RLS a menos que
FORCEseja definido. - Tabelas multi-tenant de produção devem usar
FORCEquando o proprietário for apenas a role de migração.
Padrões Multi-Tenant
-- Variável de sessão (comum com ORMs)
SET LOCAL app.tenant_id = '99';
-- Claim JWT via GUC customizado definido no início da conexão (hook da aplicação)
-- current_setting('request.jwt.claim.tenant_id', true)
-- Política de subconsulta para hierarquia de organização
USING (tenant_id IN (SELECT tenant_id FROM app.user_tenants WHERE user_id = current_user_id()))Armadilhas
- Esqueceu de definir o GUC do tenant - A política avalia a configuração NULL; retorna zero linhas ou nega tudo. Correção: O middleware define o GUC em cada requisição; falha fechando se não estiver definido.
- Bypass do proprietário sem FORCE - Migrações executadas como proprietário veem todas as linhas no psql. Correção:
FORCE ROW LEVEL SECURITYem tabelas de tenant. - Falta de WITH CHECK em INSERT - Usuário insere uma linha para outro tenant. Correção: Espelhar a expressão
USINGemWITH CHECK. - RLS + views SECURITY DEFINER - View elevada pode vazar linhas. Correção: Views de barreira de segurança ou evitar atalhos definidores.
- GUC obsoleto do pool de conexões -
SETsemLOCALvaza o tenant entre requisições. Correção: Sempre usarSET LOCALdentro da transação.
Alternativas
| Alternativa | Usar Quando | Não Usar Quando |
|---|---|---|
| Schema por tenant | Isolamento forte, menos consultas cross-tenant | Milhares de tenants |
| Banco de dados por tenant | Isolamento máximo | Alto overhead operacional |
WHERE tenant_id somente na aplicação | Single-tenant simples | Conformidade exige isolamento imposto pelo banco de dados |
FAQs
Custo de performance do RLS?
Pequeno quando as colunas da política são indexadas. Veja a página de Performance do RLS para padrões de índice.
Superusuário e RLS?
Superusuário ignora RLS a menos que FORCE e mesmo assim o superusuário ainda ignora - use roles de aplicação não-superusuário.
Testar RLS em CI?
Defina o GUC, afirme contagens de linhas por fixture de tenant, tente um INSERT cross-tenant esperando falha.
FOR ALL vs políticas separadas?
FOR ALL é conciso; políticas separadas permitem expressões diferentes por comando e role.
Relacionados
- Ignorar RLS & Superusuário - quem escapa das políticas
- Performance do RLS - indexar colunas de política
- Noções Básicas de Multi-Tenant - contexto da arquitetura
Versões do Stack: Esta página foi escrita para PostgreSQL 18.4 (estável 18, manutenção 17), pgvector 0.8+, PgBouncer 1.x, Patroni 3.x, e PostGIS 3.5+.