Segurança a Nível de Linha no PostgreSQL
É muito comum as pessoas usarem o banco de dados sempre com um único login e senha. Este login e senha é usado por um servidor (feito em PHP, Java, Node JS, Python, Go, Ruby) para fazer todas as operações desejadas, por isso, esse login possui acesso total (leitura, escrita e deleção) às tabelas do banco. O que controla quem pode ler, editar, e apagar dados do banco é o software do backend e as regras de acesso que o desenvovedor implementou.
Embora seja raramente usada pelos desenvolvedores, o PostgreSQL possui por si só um cadastro de usuários, grupos (também chamados de cargos, funções ou papéis) e também um sistema de permissões.
Em outras palavras, é possível permitir detalhadamente quais tabelas, funções e views cada usuário ou cargos podem usar ou não.
Além das permissões a nível de tabela é também possível permitir ou não a leitura, escrita e exclusão de linhas específicas dessa tabela. Isso permite um controle de acesso extremamente detalhado, também chamado de baixa granularidade (fine grained access control).
Para começar nossos testes, vou apresentar alguns comandos que vão ajudar muito a testar as permissões.
Faça login no banco com um usuário com poderes de SUPERUSER, o usuário postgresql têm esse poder por padrão.
Você pode "virar" um outro usuário ou grupo através do comando abaixo. Com isso você pode testar rapidamente quem pode fazer o que:
SET ROLE maria;
Após esse comando, poderá ver que seu current_user foi alterado para "maria":
-- Para descobrir qual usuário atualmente estamos usando:SELECT session_user, current_user; /* ** session_user é o usuário utilizado na hora da conexão com o banco, é o usuário colocado no driver de conexão PostgreSQL utilizando da linguagem de programação que você está usando ** current_user é o usuário que foi impersonado. Por padrão ele é o mesmo usuário do session_user, mas você pode alterá-lo se tiver permissão para isso. */
Agora vamos habilitar o ROW LEVEL SECURITY em uma tabela:
ALTER TABLE tabela ENABLE ROW LEVEL SECURITY;
A partir desse momento apenas o dono da tabela pode ver, inserir, alterar e apagar dados dessa tabela, mesmo que haja permissões a nível de tabela para isso.
Esse bloqueio acontece porque agora, todas as interações com a tabela devem passar pelas permissões de tabela e também a verificação de POLICY, que são as permissões de acesso do ROW LEVEL SECURITY.
Vamos criar uma POLICY permitindo que maria possa fazer SELECT nas linhas onde a coluna id = 1:
CREATE POLICY maria_policy ON tabelaFOR SELECT TO maria USING (id = 1);
Resumo de comandos
Ativando o ROW LEVEL SECURITY:
ALTER TABLE table_name ENABLE ROW LEVEL SECURITY;
Neste momento nenhuma linha da tabela pode ser vista, escrita, alterada ou apagada.
Criando uma POLICY:
-- Criando uma POLICYCREATE POLICY name ON table_name /* Por padrão usa PERMISSIVE quando usamos PERMISSIVE, se a regra aqui definida for verdadeira a operação é aceita. quando usamos RESTRICTIVE, se a regra aqui definida for verdadeira a operação é negada. Exemplo: As policies abaixo são equivalentes: PERMISSIVE FOR ALL USING (id = 1) RESTRICTIVE FOR ALL USING (id <> 1) -- Ou para um usuário específico PERMISSIVE TO maria USING (id = 1) RESTRICTIVE TO maria USING (id <> 1) */ [ AS { PERMISSIVE | RESTRICTIVE } ] /* ALL aplica para SELECT, INSERT, UPDATE e DELETE as outras aplicam apenas para a selecionada */ [ FOR { ALL | SELECT | INSERT | UPDATE | DELETE } ] /* Define o usuário(s)/role(s) a quem a policy se aplica */ [ TO { role_name | PUBLIC | CURRENT_USER | SESSION_USER } [, ...] ] -- Existing table rows are checked against the expression specified in USING -- if false, lines are not visible [ USING ( using_expression ) ] -- New rows that would be created via INSERT or UPDATE are checked against the expression specified in WITH CHECK -- if false, returns an error [ WITH CHECK ( check_expression ) ] ;
Apagando uma POLICY:
DROP POLICY [ IF EXISTS ] name ON table_name [ CASCADE | RESTRICT ]
O que acontece quando você tem mais de uma POLICY PERMISSIVE na mesma tabela?
As regras são concatenadas com OR, ou seja, se pelo menos uma regra for verdadeira, o acesso é concedido.
O que acontece quando você tem mais de uma POLICY RESTRICTIVE na mesma tabela?
As regras são concatenadas com AND, ou seja, todas precisam ser verdadeiras para se ter autorização de acesso.
Visualizando POLICIES criadas:
SELECT *FROM pg_policies;
Tabela de referência - O que você quer fazer? O que você precisa liberar?
Essa tabela ajuda a descobrir quais POLICIES você precisa criar para fazer alguma ação no banco.
Na esquerda temos a ação que você deseja executar, e em cima temos quais POLICIES você precisa criar. Eis a tabela:
COMMAND | SELECT/ALL | INSERT/ALL | UPDATE/ALL | UPDATE/ALL | DELETE/ALL |
---|---|---|---|---|---|
USING | WITH CHECK | USING | WITH CHECK | USING | |
SELECT | Existing row | — | — | — | — |
SELECT FOR UPDATE/SHARE | Existing row | — | Existing row | — | — |
INSERT | — | New row | — | — | — |
INSERT ... RETURNING | New row [*] | New row | — | — | — |
UPDATE | Existing & new rows [*] | — | Existing row | New row | — |
DELETE | Existing row [*] | — | — | — | Existing row |
ON CONFLICT DO UPDATE | Existing & new rows | — | Existing row | New row | — |
[*] If read access is required to the existing or new row (for example, a WHERE
or RETURNING
clause that refers to columns from the relation).
Referências
Cybertec - Row Level Security
https://www.cybertec-postgresql.com/en/postgresql-row-level-security-views-and-a-lot-of-magic/
Documentação Oficial PostgreSQL - Row Level Security
https://www.postgresql.org/docs/current/ddl-rowsecurity.html
Documentação Oficial PostgreSQL - CREATE POLICY
https://www.postgresql.org/docs/current/sql-createpolicy.html