Introdução
Em um cenário onde a segurança dos dados é fundamental, a gestão adequada das permissões de acesso em um banco de dados é essencial. Ao concedermos e revogarmos permissões de acesso aos objetos do banco, assumimos um controle preciso sobre quem pode realizar quais operações e onde. Esse controle é particularmente crucial em ambientes de produção, onde a integridade e a segurança dos dados são prioritárias.
Ao estabelecermos usuários com papéis bem definidos e acesso restrito, fortalecemos as defesas contra possíveis ameaças à segurança. Essa abordagem não apenas protege os dados sensíveis, mas também reduz o risco de acessos não autorizados e potenciais danos ao banco de dados.
Neste artigo, exploraremos a importância das permissões de acesso, papéis de usuários e como gerenciar essas permissões com os comandos GRANT e REVOKE.
É importante destacar que, para melhor entendimento da mudança de papéis ao longo dos exemplos, estamos usando o prompt do psql com uma personalização, com a seguinte configuração (dentro do arquivo psqlrc; mais informações no artigo Utilização do psql):
\set PROMPT1 '%n@%/%R%#%x '
Essa configuração exibe o nome do usuário na sessão atual, seguido de um “@”, e o nome da base de dados.
Roteiro:
- GRANT / REVOKE:
- Aplicação em SCHEMA;
- Aplicação em TABLE e SEQUENCE;
- Aplicação em COLUMN;
- Aplicação em ROLE;
- Aplicação em DATABASE;
- Aplicação em FUNCTION / PROCEDURE.
- ALTER DEFAULT PRIVILEGES
- DROP OWNED / REASSIGN OWNED
GRANT / REVOKE
Os comandos GRANT e REVOKE são usados para conceder e revogar permissões de acesso a objetos de banco de dados, como tabelas, esquemas, funções e sequências. Eles desempenham um papel fundamental na gestão de segurança e controle de acesso no banco de dados.
O comando GRANT é usado para conceder permissões de acesso a usuários ou roles específicas em objetos de banco de dados. As permissões comumente concedidas incluem SELECT
, INSERT
, UPDATE
, DELETE
, USAGE
, EXECUTE
entre outras, e podem ser concedidas para objetos individuais ou para todos os objetos de um determinado tipo em um esquema. Por exemplo, você pode conceder a um usuário permissão para ler dados de uma tabela específica usando o seguinte comando:
GRANT SELECT ON tabela TO usuário;
Por outro lado, o comando REVOKE é usado para revogar permissões previamente concedidas. Ele permite remover permissões de acesso de usuários ou roles em objetos de banco de dados específicos, restringindo acessos. Por exemplo, você pode revogar a permissão de um usuário para modificar dados em uma tabela usando o comando a seguir:
REVOKE UPDATE ON tabela FROM usuário;
Considerando nossas roles, schemas e usuários criados no artigo anterior, abaixo vamos exemplificar a aplicação de GRANT e REVOKE em diversos objetos para melhor entendimento, sempre utilizando o superusuário, que no nosso caso é o administrador do banco de dados (dba) para conceder e revogar privilégios.
Aplicação em SCHEMA
Aqui vamos conceder permissão de uso (USAGE) do schema aplicacao1 para os usuários app1_admin
, app1_user1
e app1_user2
, que possibilita consultar os objetos desse schema, por exemplo, as tabelas que criamos:
dba@aplicacao=# GRANT USAGE ON SCHEMA aplicacao1 TO app1_user1;
app1_user1@aplicacao=> SELECT * FROM aplicacao1.tabela1_app1;
id | nome | idade
----+-------+-------
1 | João | 30
2 | Maria | 25
3 | Pedro | 40
(3 rows)
Antes de rodarmos o mesmo comando para o usuário app1_user2
, confira o exemplo da tentativa de consulta da mesma tabela sem permissão no schema:
app1_user2@aplicacao=> SELECT * FROM aplicacao1.tabela1_app1;
ERROR: permission denied for schema aplicacao1
LINE 1: SELECT * FROM aplicacao1.tabela1_app1;
Como esse privilégio concedido diz especificamente sobre a possibilidade de consultar os objetos do esquema, não é possível modificá-los, como tentar inserir dados:
app1_user1@aplicacao=> INSERT INTO aplicacao1.tabela1_app1 (nome, idade) VALUES
('Novo nome', '0');
ERROR: permission denied for table tabela1_app1
Isso será possível quando concedermos permissões a níveis de tabela, no próximo tópico.
Aplicação em TABLE e SEQUENCE
Agora, vamos conceder a permissão de consulta, inserção, atualização e exclusão de dados (SELECT, INSERT, UPDATE, DELETE, respectivamente) ao usuário app1_admin nas tabelas do schema aplicacao1 (tabela1_app1, tabela2_app1, tabela3_app1):
dba@aplicacao=# GRANT SELECT, INSERT, UPDATE, DELETE ON aplicacao1.tabela1_app1 TO app1_admin;
GRANT SELECT, INSERT, UPDATE, DELETE ON aplicacao1.tabela2_app1 TO app1_admin;
GRANT SELECT, INSERT, UPDATE, DELETE ON aplicacao1.tabela3_app1 TO app1_admin;
Outra forma de fazer isso seria usando a cláusula ALL TABLES IN SCHEMA, o que facilita o processo encurtando o comando, portanto, aqui vamos conceder as permissões para o usuário app2_admin sobre as tabelas do schema aplicacao2 e a permissão de leitura para os demais usuários:
dba@aplicacao=# GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA aplicacao1 TO app1_admin;
dba@aplicacao=# GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA aplicacao2 TO app2_admin;
dba@aplicacao=# GRANT SELECT ON ALL TABLES IN SCHEMA aplicacao2 TO app1_user1;
dba@aplicacao=# GRANT SELECT ON ALL TABLES IN SCHEMA aplicacao2 TO app1_user2;
dba@aplicacao=# GRANT SELECT ON ALL TABLES IN SCHEMA aplicacao2 TO app2_user1;
dba@aplicacao=# GRANT SELECT ON ALL TABLES IN SCHEMA aplicacao2 TO app2_user2;
Além disso, para possibilitar que a inserção de dados ocorra bem, também precisamos conceder permissão de USAGE na sequência das tabelas, por exemplo, ‘tabela1_app1_id_seq’, por se tratar de uma tabela com uma coluna SERIAL (com o id de cada linha se autoincrementando). Para isso, vamos rodar:
dba@aplicacao=# GRANT USAGE ON SEQUENCE aplicacao1.tabela1_app1_id_seq TO app1_admin;
Sendo assim, o administrador da aplicação torna-se o único usuário que consegue inserir novas linhas nas tabelas selecionadas:
app1_admin@aplicacao=> INSERT INTO aplicacao1.tabela1_app1 (nome, idade) VALUES
(Novo nome', '0');
app1_admin@aplicacao=> SELECT * FROM aplicacao1.tabela1_app1;
id | nome | idade
----+------------+-------
1 | João | 30
2 | Maria | 25
3 | Pedro | 40
4 | Novo nome | 0
(4 rows)
Por consequência, caso o administrador da aplicação 2 (app2_admin) tente consultar ou realizar qualquer outra transação no schema da aplicação 1, haverá erro:
app2_admin@aplicacao=> SELECT * FROM aplicacao1.tabela1_app1;
ERROR: permission denied for schema aplicacao1
LINE 1: SELECT * FROM aplicacao1.tabela1_app1;
Em outro exemplo, temos uma tabela (aplicacao1.tabela3_app1) que estritamente não pode ser editada, e para esse controle iremos revogar a permissão de edição da role public, que engloba todos os usuários:
dba@aplicacao=# REVOKE DELETE, UPDATE ON aplicacao1.tabela3_app1 FROM public;
REVOKE
Assim, caso haja tentativa de edição nessa tabela por qualquer usuário além do DBA, ocorrerá o seguinte erro:
app1_user1@aplicacao=> INSERT INTO aplicacao1.tabela3_app1 (email)
VALUES ('exemplo@example.com');
ERROR: permission denied for table tabela3_app1
Time: 1.684 ms
Aplicação em COLUMN
Agora, vamos considerar uma situação em que o usuário app1_user1 precisa atualizar uma linha específica em uma das tabelas da aplicação 1, mas apenas em uma coluna em especial. Por exemplo, suponha que o usuário precise atualizar a idade de uma pessoa na tabela tabela1_app1.
Vamos conceder permissões apropriadas para que o usuário app1_user1 possa realizar essa operação:
dba@aplicacao=# GRANT UPDATE (idade) ON aplicacao1.tabela1_app1 TO app1_user1;
Após conceder essa permissão, o usuário app1_user1 será capaz de atualizar a coluna idade na tabela tabela1_app1, mas não terá permissão para atualizar outras colunas ou realizar outras operações na tabela.
Por exemplo, o usuário app1_user1 pode executar a seguinte operação:
app1_user1@aplicacao=> UPDATE aplicacao1.tabela1_app1 SET idade = '35' WHERE id = 1;
app1_user1@aplicacao=> SELECT * FROM aplicacao1.tabela1_app1;
id | nome | idade
----+------------+-------
2 | Maria | 25
3 | Pedro | 40
4 | Novo nome | 0
1 | João | 35
(4 rows)
No entanto, se ele tentar atualizar outras colunas ou realizar outras operações, como inserção ou exclusão de linhas, ele receberá uma mensagem de erro informando que a permissão é negada para essa ação específica:
app1_user1@aplicacao=> INSERT INTO aplicacao1.tabela1_app1 (nome, idade) VALUES ('Joana', '28');
ERROR: permission denied for table tabela1_app1
E, após a modificação necessária, é possível remover essa permissão do usuário com o REVOKE:
dba@aplicacao=# REVOKE UPDATE (idade) ON aplicacao1.tabela1_app1 FROM app1_user1;
No entanto, isso impossibilita novas alterações de idade por esse usuário:
app1_user1@aplicacao=> UPDATE aplicacao1.tabela1_app1
SET idade = '30'
WHERE id = 1;
ERROR: permission denied for table tabela1_app1
Esse é um exemplo simples de como conceder permissões em nível de coluna para usuários específicos, permitindo que eles realizem operações precisas em dados específicos em uma tabela. Isso proporciona um controle granular sobre o que os usuários podem fazer em um banco de dados.
Aplicação em ROLE
Para exemplificar o controle de acesso utilizando role como um papel e não como um usuário, vamos criar uma nova role chamada editores_app1
, que vai possibilitar que alguém se torne editor das tabelas do schema aplicacao1
, tendo como administrador o usuário app1_admin
; em seguida, adicionaremos o usuário app1_user2
nessa role.
Para criar a role:
dba@aplicacao=# CREATE ROLE editores_app1 WITH ADMIN app1_admin;
Depois, vamos dar a permissão de edição nas tabelas para a role criada:
dba@aplicacao=# GRANT UPDATE ON ALL TABLES IN SCHEMA aplicacao1 TO editores;
Agora, podemos adicionar o usuário app1_user2 nessa role:
dba@aplicacao=# GRANT editores TO app1_user2;
Então, podemos editar o que for necessário com esse usuário (abaixo vemos uma consulta à tabela antes e pós-edição):
app1_user2@aplicacao=> SELECT * FROM aplicacao1.tabela1_app1;
id | nome | idade
----+------------+-------
2 | Maria | 25
3 | Pedro | 40
4 | Novo nome | 0
1 | João | 35
(4 rows)
app1_user2@aplicacao=> UPDATE aplicacao1.tabela1_app1
SET nome = 'João Vitor'
WHERE id = 1;
UPDATE 1
app1_user2@aplicacao=> SELECT * FROM aplicacao1.tabela1_app1;
id | nome | idade
----+------------+-------
2 | Maria | 25
3 | Pedro | 40
4 | Novo nome | 0
1 | João Vitor | 35
(4 rows)
Após feita a alteração, também é possível remover esse usuário da role criada por meio do REVOKE:
dba@aplicacao=# REVOKE editores FROM app1_user2;
REVOKE ROLE
Como pudemos ver, utilizando roles, simplificamos o controle de acesso ao atribuir permissões específicas a papéis, gerenciando ambientes com vários usuários de maneira mais ampla e eficiente.
Aplicação em DATABASE
A concessão de permissões a nível de DATABASE oferece controle sobre todas as tabelas e objetos contidos dentro do banco de dados específico. Esse método é útil quando se deseja aplicar permissões de forma abrangente, abordando todas as tabelas e esquemas presentes no banco de dados.
Para exemplificar, consideremos o cenário em que precisamos revogar a permissão para conexão com os bancos de dados de um usuário. Vamos supor que o usuário app1_user1 não possa mais se conectar com nosso servidor por motivos de segurança. Para remover o acesso do usuário app1_user1, considerando a permissão de CONNECT, devemos começar revogando essa permissão da role public, que é uma role padrão do PostgreSQL, gerada automaticamente quando criamos um banco (também existe um schema padrão de mesmo nome, que todos os usuários podem mexer e acessar). Para isso, vamos rodar o seguinte comando:
dba@aplicacao=# REVOKE CONNECT ON DATABASE aplicacao FROM public;
Dessa forma, não é mais possível se conectar ao banco aplicacao. Para resolver isso, vamos conceder essa permissão separadamente para cada usuário que desejamos, desconsiderando, então, o usuário app1_user1:
dba@aplicacao=# GRANT CONNECT ON DATABASE aplicacao TO app1_admin, app1_user2, app2_admin, app2_user1, app2_user2;
Sendo assim, caso queiramos revogar o acesso de outro usuário, basta revogar a permissão CONNECT ao database aplicacao da role/usuário desejado. Essa é a visualização de erro na tentativa de acesso do usuário que não possui a permissão CONNECT:
$ psql -h localhost -p 5432 -U app1_user1 -d aplicacao;
Password for user app1_user1:
psql: error: connection to server at "localhost" (::1), port 5432 failed: FATAL: permission denied for database "aplicacao"
DETAIL: User does not have CONNECT privilege.
Aplicação em FUNCTION/PROCEDURE
Vamos, agora, exemplificar o gerenciamento do acesso as functions concedendo permissão de execução para a função last_day
, que pertence ao banco pagila (exemplo de banco de dados disponibilizado pelo PostgreSQL para fins educacionais e de treinamento).
Para isso, o primeiro passo é revogar todas as permissões em relação às funções da role padrão public
(aquele grupo em que todos os usuários estão), especificando o schema onde se encontra a função que desejamos gerenciar, no nosso caso, o schema pagila
:
postgres@pagila=# REVOKE ALL ON FUNCTION schema_pagila.last_day FROM public;
REVOKE
Feito isso, assim como para acesso ao database, precisamos especificar quais usuários podem executar essa função explicitamente, já que removemos a permissão de todos ao revogar a role public
. Então, para garantir que nosso usuário user_pagila
possa executar essa função, podemos rodar:
postgres@pagila=# GRANT EXECUTE ON FUNCTION last_day TO user_pagila;
GRANT
Pronto! Para os usuários que não foram contemplados com a permissão, aparecerá a seguinte mensagem de erro:
user_pagila2@pagila=> SELECT schema_pagila.last_day(current_date);
ERROR: permission denied for function last_day
ALTER DEFAULT PRIVILEGES
O comando ALTER DEFAULT PRIVILEGES
oferece uma maneira de configurar os privilégios padrão que serão aplicados a objetos criados no futuro. Em outras palavras, ele permite definir as permissões que novos objetos herdarão automaticamente assim que forem criados.
Ao contrário do que muitos podem pensar, esse comando não afeta os privilégios de objetos existentes, apenas os novos objetos que serão criados após a configuração. Você pode definir privilégios padrão globalmente para todos os objetos criados no banco de dados atual ou especialmente para objetos criados em schemas específicos.
Por padrão, normalmente, os privilégios de novos objetos são herdados do papel de usuário que os cria. No entanto, o comando ALTER DEFAULT PRIVILEGES permite personalizar esses privilégios de acordo com as necessidades do ambiente. Por exemplo, você pode conceder permissões de leitura para o público em geral em todas as tabelas futuras criadas em um determinado esquema ou permitir que um papel específico execute funções recém-criadas.
Para exemplificar, vamos criar mais dois usuários, o app1_user3 e o app_user4:
dba@aplicacao=# CREATE ROLE app1_user3 LOGIN PASSWORD 'senha';
dba@aplicacao=# CREATE ROLE app1_user4 LOGIN PASSWORD 'senha';
E, claro, conceder a eles o uso do schema aplicacao1, como também a permissão para se conectarem com a base de dados aplicacao:
dba@aplicacao=# GRANT USAGE ON SCHEMA aplicacao1 TO app1_user3;
dba@aplicacao=# GRANT USAGE ON SCHEMA aplicacao1 TO app1_user4;
dba@aplicacao=# GRANT CONNECT ON DATABASE aplicacao TO app1_user3;
dba@aplicacao=# GRANT CONNECT ON DATABASE aplicacao TO app1_user4;
Então, vamos usar o comando ALTER DEFAULT PRIVILEGES para definir, como padrão, a possibilidade de consulta nas novas tabelas criadas para o usuário app1_user3:
dba@aplicacao=# ALTER DEFAULT PRIVILEGES IN SCHEMA aplicacao1 GRANT SELECT ON TABLES TO app1_user3;
Para testarmos, vamos criar uma nova tabela no schema aplicacao1
, chamada tabela4_app1
, inserindo, nela, dados de exemplo:
dba@aplicacao=# CREATE TABLE aplicacao1.tabela4_app1 (
id SERIAL PRIMARY KEY,
dado VARCHAR(150)
);
CREATE TABLE
dba@aplicacao=# INSERT INTO aplicacao1.tabela4_app1 (dado)
VALUES ('algum dado');
INSERT 0 1
Assim, é possível que o usuário app1_user3
consulte as informações da tabela por mais que o comando de GRANT SELECT
não tenha sido rodado explicitamente; isso graças ao ALTER DEFAULT PRIVILEGES
rodado antes da criação da tabela, que também servirá para futuros objetos caso criemos mais:
app1_user3@aplicacao=> SELECT * FROM aplicacao1.tabela4_app1;
id | dado
----+------------
1 | algum dado
(1 row)
Em contrapartida, o outro usuário, o app1_user4
, não pode fazer a consulta porque não rodamos o ALTER DEFAULT PRIVILEGES
para ele e nem o GRANT SELECT
de forma explícita para essa tabela:
app1_user4@aplicacao=> SELECT * FROM aplicacao1.tabela5_app1;
ERROR: permission denied for table tabela5_app1
DROP OWNED / REASSIGN OWNED
Os comandos DROP OWNED e REASSIGN OWNED são usados para gerenciar a propriedade dos objetos do banco de dados, sendo úteis em situações de manutenção de usuários. Enquanto o DROP OWNED remove todos os objetos pertencentes a uma role específica e revoga suas permissões, o REASSIGN OWNED transfere a propriedade desses objetos para outra role, sem removê-los.
DROP OWNED
O comando DROP OWNED é utilizado para remover todos os objetos (como tabelas, sequências, funções etc.) que pertencem a uma ou mais roles específicas, além de revogar todas as permissões que essas roles possuem em outros objetos do banco de dados. É frequentemente usado antes de excluir um usuário ou uma role, garantindo que não restem objetos associados ou permissões residuais.
Por exemplo, suponha que você precise remover todos os objetos pertencentes à role app1_admin. Para isso, você pode usar o seguinte comando:
DROP OWNED BY app1_admin CASCADE;
Isso removerá todos os objetos que pertencem à app1_admin e revogará todas as permissões associadas, incluindo a remoção de qualquer objeto que dependa dos que pertencem a essa role.
Outra opção desse comando é a RESTRICT, que impede a remoção de objetos caso existam dependências, ou seja, outros objetos que dependam daqueles que pertencem à role especificada. Essa opção é útil quando você deseja garantir que não está removendo inadvertidamente objetos importantes que poderiam afetar o funcionamento do banco de dados. Veja um exemplo de erro de dependência:
ERROR: cannot drop objects owned by role because other objects depend on them
DETAIL: Table "tabela_dependente" depends on table "tabela1_app1".
Esse erro pode ser resolvido com a RESTRICT:
DROP OWNED BY app1_user2 RESTRICT;
REASSIGN OWNED
O comando REASSIGN OWNED
permite transferir a propriedade de todos os objetos pertencentes a uma role para outra, servindo para quando um usuário ou uma role estão prestes a serem removidos, mas seus objetos associados ainda precisam ser mantidos, só que sob a propriedade de outra role.
Por exemplo, para transferir todos os objetos da app1_admin para dba
, podemos utilizar o seguinte comando:
REASSIGN OWNED BY app1_admin TO dba;
Com esse comando, todos os objetos que pertenciam à role app1_admin
agora passam a ser de propriedade da role dba
, garantindo a continuidade de acesso e administração.
Conclusão
Lidar com segurança é chato; não tem adjetivo melhor que esse para descrever o trabalho de dar permissões de tabela em tabela para cada usuário. Ninguém gosta, nem os mais perfeccionistas. No entanto, em tempos de bancos de dados na nuvem, isso é importantíssimo. Colocamos este capítulo logo no começo para que você se acostume a lidar com esse tipo de coisa desde já. Acredite, dói menos assim.
O PostgreSQL tem um jeitinho aqui e ali de facilitar as coisas, mas se você não começar a pensar em segurança logo no início, depois vai ficar bem mais difícil e, sem dúvida, mais tedioso. Pense nisso agora. E pense nisso em todos os ambientes de trabalho, seja durante o desenvolvimento, seja em testes, homologação etc. Se deixar para ver isso só quando chegar no ambiente de produção, com certeza você terá problemas e receberá comentários desagradáveis quando sofrer uma auditoria de segurança ou, pior, quando sofrer uma tentativa de invasão no seu banco de dados. Ninguém quer que isso aconteça, claro, mas a questão não é se isso um dia vai acontecer, mas quando.