Tenancy com Segurança em Nível de Linha
Modelos de política por nível de tenant transformam a disciplina de tenant_id em isolamento imposto pelo banco de dados - uma rede de segurança quando os filtros da aplicação falham.
Receita
Cartão de receita de referência rápida - pronto para copiar e colar.
ALTER TABLE invoices ENABLE ROW LEVEL SECURITY;
ALTER TABLE invoices FORCE ROW LEVEL SECURITY;
CREATE POLICY invoices_tenant_select ON invoices
FOR SELECT
USING (tenant_id = current_setting('app.tenant_id', true)::uuid);
CREATE POLICY invoices_tenant_insert ON invoices
FOR INSERT
WITH CHECK (tenant_id = current_setting('app.tenant_id', true)::uuid);
CREATE POLICY invoices_tenant_update ON invoices
FOR UPDATE
USING (tenant_id = current_setting('app.tenant_id', true)::uuid)
WITH CHECK (tenant_id = current_setting('app.tenant_id', true)::uuid);Quando usar isso:
- SaaS multi-tenant com esquema compartilhado e expectativas de conformidade (prontidão para SOC2, HIPAA).
- Múltiplos serviços consultam as mesmas tabelas com caminhos de código diferentes.
- Você precisa de defesa em profundidade além do escopo do ORM.
Exemplo de Funcionamento
BEGIN;
CREATE TABLE tenants (
tenant_id uuid PRIMARY KEY,
tier text NOT NULL CHECK (tier IN ('starter', 'business', 'enterprise'))
);
CREATE TABLE files (
tenant_id uuid NOT NULL REFERENCES tenants (tenant_id),
file_id bigint GENERATED ALWAYS AS IDENTITY,
name text NOT NULL,
PRIMARY KEY (tenant_id, file_id)
);
ALTER TABLE files ENABLE ROW LEVEL SECURITY;
ALTER TABLE files FORCE ROW LEVEL SECURITY;
-- Política padrão de isolamento de tenant
CREATE POLICY files_tenant_isolation ON files
USING (tenant_id = current_setting('app.tenant_id', true)::uuid)
WITH CHECK (tenant_id = current_setting('app.tenant_id', true)::uuid);
-- Suporte da plataforma com bypass somente leitura para tickets do tier enterprise
CREATE ROLE support_reader;
CREATE POLICY files_support_read ON files
FOR SELECT
TO support_reader
USING (
EXISTS (
SELECT 1 FROM tenants t
WHERE t.tenant_id = files.tenant_id
AND t.tier = 'enterprise'
)
);
GRANT SELECT ON files TO support_reader;
GRANT support_reader TO support_agent;
COMMIT;O que isso demonstra:
FORCE ROW LEVEL SECURITYaplica políticas até mesmo aos proprietários da tabela.- Políticas separadas por comando (
SELECT,INSERT,UPDATE) quando necessário. - Acesso de suporte específico do tier via política direcionada a role.
Mergulho Profundo
Como Funciona
- RLS anexa expressões de política a cada plano de consulta como qualificadores de filtro.
current_setting('app.tenant_id', true)retorna NULL se não estiver definido - a política deve falhar fechada (nenhuma linha).- O atributo
BYPASSRLSem superusuário/proprietário ignora políticas, a menos queFORCEesteja definido. - As políticas são por tabela - cada tabela de tenant precisa de cobertura; lacunas são vulnerabilidades.
Modelos de Política por Tier
| Tier | Modelo |
|---|---|
| Starter/Business | tenant_id = app.tenant_id estrito para todos os comandos |
| Enterprise + suporte | Política adicional de role somente leitura com auditoria |
| Administrador da plataforma | Role separado, conexão separada, log de auditoria completo |
Notas SQL
-- Verifica se RLS está habilitado em todas as tabelas de tenant
SELECT c.relname, c.relrowsecurity, c.relforcerowsecurity
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE n.nspname = 'public' AND c.relkind = 'r'
AND c.relname NOT LIKE 'pg_%';Armadilhas
- RLS sem FORCE - o proprietário da tabela ignora as políticas nas migrações. Correção:
FORCE ROW LEVEL SECURITYem tabelas de dados de tenant. - Política ausente em nova tabela - lança recurso com vazamento. Correção: checklist de migração + consulta CI contra
pg_policies. - GUC desatualizado em conexão agrupada - tenant errado após reutilização. Correção:
SET app.tenant_ida cada requisição; PgBouncerDISCARD ALL. - Ferramentas de BI de superusuário - Metabase conecta como proprietário, vê todos os tenants. Correção: role dedicado somente leitura sem bypass.
- Desempenho da política - subconsulta complexa por linha. Correção: compare
tenant_idcom GUC diretamente; indexetenant_id.
Alternativas
| Alternativa | Usar Quando | Não Usar Quando |
|---|---|---|
| Filtragem apenas na aplicação | Protótipo / ferramentas internas | SaaS multi-tenant em produção |
| Esquema por tenant | Isolamento de namespace mais forte | Milhares de esquemas |
| Views com security_barrier | Padrão legado | RLS nativo é mais claro no PG 18 |
| Distribuição de tenant Citus | Isolamento em nível de shard | Postgres de nó único padrão |
FAQs
RLS deixa as consultas lentas?
Políticas simples de tenant_id = constante adicionam sobrecarga insignificante quando tenant_id está indexado. Evite subconsultas por linha nas políticas.
JOINs respeitam RLS?
Sim - as políticas de cada tabela se aplicam. Garanta que as chaves de junção não possam parear linhas entre tenants (chaves estrangeiras compostas ajudam).
Como as migrações rodam com RLS?
A role de migração pode precisar de BYPASSRLS brevemente ou desabilitar RLS em uma janela de manutenção. Reabilite e teste as políticas antes do tráfego.
Posso usar RLS com prepared statements?
Sim. Defina o GUC antes de executar. Teste com pooling de transação do PgBouncer - o GUC deve ser definido dentro da mesma transação.
O que é comportamento de falha fechada?
app.tenant_id não definido deve corresponder a zero linhas, não a todas as linhas. Teste com RESET app.tenant_id na suíte de integração.
Como testar vazamentos entre tenants em CI?
Insira dois tenants, defina o GUC para o tenant A, afirme que a consulta não pode ver as linhas do tenant B. Teste negativo é necessário por tabela.
Políticas INSERT são suficientes?
É necessário USING para SELECT/UPDATE/DELETE e WITH CHECK para INSERT/UPDATE. Conjuntos de políticas incompletos vazam em alguns comandos.
RLS se aplica a superusuário?
Superusuário ignora a menos que FORCE ROW LEVEL SECURITY. Não execute a aplicação como superusuário.
Políticas podem referenciar o usuário da sessão?
Sim: USING (tenant_id = (SELECT tenant_id FROM users WHERE user_name = current_user)) - garanta consulta indexada.
Como as políticas específicas de tier se combinam?
Múltiplas políticas permissivas se combinam com OR para a mesma role/comando. Documente as combinações para evitar acesso amplo acidental.
Relacionado
- Esquema Compartilhado + tenant_id - design de coluna e índice
- Roles & Permissões RLS - mecânicas gerais de RLS
- Melhores Práticas Multi-Tenant - casos negativos de CI
- Esquema por Tenant - quando RLS não é suficiente
Versões da 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+.