01Por que o banco deve garantir o isolamento
Num SaaS com schema compartilhado, toda tabela tem uma coluna tenant_id e toda consulta precisa filtrar por ela. A aplicação consegue fazer isso, mas precisa fazer sempre, em toda consulta, escrita por todo engenheiro. O row-level security (RLS) faz o próprio PostgreSQL aplicar o filtro, e uma cláusula esquecida passa a não retornar nada em vez de retornar dados de outro cliente.
02A configuração mínima
Ative o RLS na tabela e crie uma política que compare o tenant da linha com uma configuração definida para a transação atual.
<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 o que uma consulta pode ler. WITH CHECK bloqueia inserts e updates que gravariam uma linha em outro tenant. FORCE aplica a política também ao dono da tabela.
03Definindo o tenant a cada requisição
A API identifica o tenant pelo usuário autenticado e define o valor no início de cada transação. O terceiro argumento de set_config deixa o valor restrito à transação, o que importa quando as conexões ficam num 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}
04Três erros que quebram o isolamento
<Conectar como superusuário ou com um papel que tem BYPASSRLS. As políticas são ignoradas sem nenhum erro. Use um papel dedicado para a aplicação.
<Definir o tenant no nível da sessão com pool de conexões. A próxima requisição na mesma conexão herda o tenant anterior.
<Esquecer tabelas novas. Adicione uma checagem nas migrações que falhe quando uma tabela com tenant_id estiver sem RLS.
O RLS é uma segunda linha de defesa. Mantenha os filtros de tenant também na aplicação e teste o isolamento no CI com dois tenants e as mesmas consultas.
05Testando no 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;
Rodamos essa checagem para toda tabela com coluna de tenant em todo pull request. Leva segundos e pega o único erro que custaria um cliente.
[ FOTO ]
SOBRE O AUTOR[ PLACEHOLDER · Nome, cargo e uma linha de trajetória ]
< Todos os artigos