Para uma melhor experiência com caixas de código, experimente ver o site em um Desktop.
No post anterior, instalamos o PostgreSQL e aprendemos a gerenciar usuários, bancos de dados, tabelas e backups. Até aqui, tudo foi feito no próprio servidor, com o usuário postgres. Isso funciona para aprender, mas não é assim que um banco de dados deve ficar em produção.
Na prática, o PostgreSQL existe para atender uma aplicação: um sistema web de chamados, de inventário, um site. E é na hora de ligar a aplicação ao banco que aparecem os erros mais comuns de segurança: a aplicação conectando como postgres, a senha do banco em um arquivo que qualquer usuário consegue ler, a porta 5432 aberta para a rede inteira e um pg_hba.conf que aceita qualquer um.
Neste post, vamos montar o acesso ao PostgreSQL seguindo um princípio simples: cada um com a sua conta, e cada conta só com o acesso de que precisa. Isso vale para as pessoas que administram o servidor, para os desenvolvedores e para a própria aplicação.
Vamos trabalhar com os dois cenários mais comuns:
Cenário 1: aplicação e banco no mesmo servidor. A aplicação conecta pelo socket local e o PostgreSQL nem precisa ficar exposto na rede.
Cenário 2: aplicação em outro servidor. O banco passa a aceitar conexões pela rede, e por isso precisamos limitar quem pode conectar, liberar o firewall só para o servidor da aplicação e criptografar a conexão.
Como no post anterior, os comandos foram escritos para o PostgreSQL 16 ou mais recente e funcionam no Debian, no Rocky Linux e no Oracle Linux. Quando houver diferença entre as distribuições, ela aparece indicada no texto. Para acompanhar, você precisa do PostgreSQL já instalado, como mostrado no post anterior, e de uma segunda máquina (uma VM serve) para testar o cenário 2.
Antes dos cenários, é preciso entender o arquivo que decide quem entra no PostgreSQL: o pg_hba.conf. Cada linha é uma regra, com cinco colunas:
# TIPO BANCO USUÁRIO ENDEREÇO MÉTODO
local all all peer
host all all 127.0.0.1/32 scram-sha-256
hostssl app_db app 192.168.10.20/32 scram-sha-256
TIPO: local é a conexão pelo socket, dentro do servidor; host é a conexão pela rede, com ou sem criptografia; hostssl só aceita conexões pela rede com TLS.
BANCO e USUÁRIO: a quem a regra se aplica. all vale para todos.
ENDEREÇO: de onde a conexão pode vir, no formato IP/máscara. Não existe nas linhas local.
MÉTODO: como a pessoa prova quem é. peer confere o usuário do Linux, sem senha; scram-sha-256 pede senha, guardada de forma segura; reject recusa.
O detalhe mais importante: o PostgreSQL lê o arquivo de cima para baixo e usa a primeira linha que combinar com a conexão. Uma regra específica colocada depois de uma regra genérica nunca é usada. Por isso, as regras novas deste post sempre vão acima das linhas que já existem.
O arquivo fica no mesmo lugar do postgresql.conf, visto no post anterior. Depois de qualquer alteração, recarregue o serviço:
sudo systemctl reload postgresql
Para conferir as regras que o servidor está usando de fato, e se alguma linha tem erro, no psql como postgres:
SELECT line_number, type, database, user_name, address, auth_method, error
FROM pg_hba_file_rules;
Um detalhe que muda entre as distribuições: no Debian, as conexões pela rede local (127.0.0.1) já usam scram-sha-256. No Rocky Linux e no Oracle Linux, essas linhas vêm com o método ident, que não aceita senha. As regras deste post resolvem isso para os usuários da aplicação.
A ideia aqui é a mesma da série do MariaDB: separar a conta do serviço, as contas das pessoas e a conta da aplicação.
A conta do serviço já existe
O PostgreSQL não roda como root. A instalação cria a conta de sistema postgres, dona dos arquivos de dados. Para conferir:
# Usuário que está rodando o servidor
ps -C postgres -o user,pid,cmd | head -n 3
Nunca dê uma senha para a conta postgres do Linux. Quem precisa dela entra com sudo -u postgres, e o sudo registra no log quem fez isso.
Um grupo para quem cuida do banco
Em vez de dar sudo completo para cada pessoa, crie um grupo e libere só os comandos de que ele precisa:
sudo groupadd dba
sudo useradd -m -s /bin/bash -G dba dev01
sudo passwd dev01
Crie o arquivo de regras do grupo sempre com o visudo, que confere a sintaxe antes de salvar. O nome do arquivo não pode ter ponto nem terminar com ~, e ele deve ficar com permissão 0440:
sudo visudo -f /etc/sudoers.d/dba
Conteúdo do arquivo:
# /etc/sudoers.d/dba
# Permissões do grupo dba: administrar o serviço do PostgreSQL,
# ler os logs e editar a configuração, sem acesso de root completo.
# Linhas que começam com # são comentários e são ignoradas.
# ----------------------------------------------------------------
# 1) Apelidos de comandos (Cmnd_Alias)
# Um Cmnd_Alias dá um nome a uma lista de comandos. O nome deve
# ser em MAIÚSCULAS. A barra invertida (\) no fim da linha continua
# a lista na linha de baixo. Use sempre o caminho completo.
# ----------------------------------------------------------------
# Serviço. No Debian, o status detalhado fica na unidade do cluster
# (postgresql@17-main); troque 17 pela sua versão.
Cmnd_Alias PG_SVC = /usr/bin/systemctl status postgresql, \
/usr/bin/systemctl status postgresql@17-main, \
/usr/bin/systemctl start postgresql, \
/usr/bin/systemctl stop postgresql, \
/usr/bin/systemctl restart postgresql, \
/usr/bin/systemctl reload postgresql
# Logs. Mantenha só a parte da sua distribuição.
# Debian:
Cmnd_Alias PG_LOG = /usr/bin/tail -n 100 /var/log/postgresql/postgresql-17-main.log
# Rocky Linux / Oracle Linux (um arquivo por dia da semana):
# Cmnd_Alias PG_LOG = /usr/bin/tail -n 100 /var/lib/pgsql/data/log/postgresql-Mon.log, \
# /usr/bin/tail -n 100 /var/lib/pgsql/data/log/postgresql-Tue.log, \
# /usr/bin/tail -n 100 /var/lib/pgsql/data/log/postgresql-Wed.log, \
# /usr/bin/tail -n 100 /var/lib/pgsql/data/log/postgresql-Thu.log, \
# /usr/bin/tail -n 100 /var/lib/pgsql/data/log/postgresql-Fri.log, \
# /usr/bin/tail -n 100 /var/lib/pgsql/data/log/postgresql-Sat.log, \
# /usr/bin/tail -n 100 /var/lib/pgsql/data/log/postgresql-Sun.log
# Editar a configuração com o sudoedit, que abre uma cópia do
# arquivo com o editor do próprio usuário (sem root).
# Debian:
Cmnd_Alias PG_CONF = sudoedit /etc/postgresql/17/main/postgresql.conf, \
sudoedit /etc/postgresql/17/main/pg_hba.conf
# Rocky Linux / Oracle Linux:
# Cmnd_Alias PG_CONF = sudoedit /var/lib/pgsql/data/postgresql.conf, \
# sudoedit /var/lib/pgsql/data/pg_hba.conf
# ----------------------------------------------------------------
# 2) Regra do grupo
# Formato: QUEM ONDE=(COMO_QUEM) O_QUÊ
# %dba -> o % indica um grupo do Linux
# ALL -> vale em qualquer servidor que use este arquivo
# (root) -> os comandos rodam como root
# no final -> os apelidos liberados, separados por vírgula
# ----------------------------------------------------------------
%dba ALL=(root) PG_SVC, PG_LOG, PG_CONF
Repare na lista de logs do Rocky Linux: em vez de um caractere curinga, como postgresql-*.log, o arquivo lista os sete nomes. No sudo, o * dos argumentos aceita qualquer coisa, inclusive espaços, e uma regra como tail -n 100 /var/lib/pgsql/data/log/postgresql-*.log permitiria ler qualquer arquivo do servidor acrescentando outro caminho no final. Evite curingas nos argumentos de um arquivo de sudo.
Para conferir o que um usuário pode fazer, entre com ele e rode sudo -l. Se aparecer (ALL) ALL, ele ainda tem sudo completo por outro caminho, como o grupo sudo no Debian ou wheel no Rocky.
Um aviso: não libere sudo -u postgres psql para o grupo. Dentro do psql, o comando \! abre um shell, que rodaria com a conta postgres, com acesso a todos os dados. Esse acesso é de quem administra o banco de verdade, e deve ser dado de forma individual e consciente.
Entrar no banco sem senha, com a conta do Linux
No PostgreSQL, isso já vem pronto: é o método peer da linha local all all peer do pg_hba.conf. Basta criar uma role com o mesmo nome do usuário do Linux. No psql, como postgres:
CREATE ROLE dev01 LOGIN;
CREATE DATABASE app_dev OWNER dev01;
Logado como dev01 no Linux, basta digitar:
psql -d app_dev
O desenvolvedor trabalha à vontade no banco app_dev, de que é dono, mas não tem acesso ao banco de produção. Não há senha guardada em lugar nenhum, e se a conta dev01 for desativada no Linux, o acesso ao banco cai junto.
Quando a aplicação roda no mesmo servidor, ela não precisa de rede para falar com o banco: a conexão passa pelo socket local, um arquivo especial que só quem está dentro do servidor consegue usar.
Fechar o PostgreSQL para a rede
O padrão já é aceitar conexões de rede só do próprio servidor (localhost). Para desligar a rede de vez e aceitar apenas o socket, deixe a opção vazia:
# Debian: /etc/postgresql/17/main/postgresql.conf
# Rocky Linux / Oracle Linux: /var/lib/pgsql/data/postgresql.conf
listen_addresses = ''
Essa opção só vale depois de reiniciar o serviço:
sudo systemctl restart postgresql
# Não deve aparecer nada
sudo ss -tlnp | grep 5432
Se alguma ferramenta do servidor precisar conectar por 127.0.0.1, use listen_addresses = 'localhost', que é o padrão.
Dois usuários para a aplicação
No PostgreSQL, o jeito mais limpo de separar as permissões é usar duas roles: uma dona do banco, usada só na instalação e nas atualizações da aplicação, que criam e alteram tabelas; e outra para o dia a dia, que só lê e grava dados. No psql, como postgres:
CREATE ROLE app_admin LOGIN;
\password app_admin
CREATE ROLE app LOGIN;
\password app
CREATE DATABASE app_db OWNER app_admin;
Para a senha, gere uma aleatória em vez de inventar, por exemplo com openssl rand -base64 24.
Agora, dentro do banco, dê ao usuário do dia a dia o acesso aos dados. O ALTER DEFAULT PRIVILEGES é o que faz a diferença: toda tabela que o app_admin criar no futuro já nasce com as permissões para o app, sem precisar de um GRANT a cada atualização da aplicação.
\c app_db
-- Ninguém além de quem tem permissão conecta neste banco
REVOKE CONNECT ON DATABASE app_db FROM PUBLIC;
GRANT CONNECT ON DATABASE app_db TO app;
GRANT USAGE ON SCHEMA public TO app;
-- Tabelas que o app_admin criar daqui para a frente
ALTER DEFAULT PRIVILEGES FOR ROLE app_admin IN SCHEMA public
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO app;
ALTER DEFAULT PRIVILEGES FOR ROLE app_admin IN SCHEMA public
GRANT USAGE, SELECT ON SEQUENCES TO app;
As permissões nas SEQUENCES são necessárias porque os ids gerados automaticamente usam uma sequência por trás; sem elas, o INSERT falha. Se o banco já tiver tabelas, dê as permissões para elas também:
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO app;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO app;
Se você seguiu o post anterior e o banco já tem tabelas criadas pelo app, passe-as para o novo dono com REASSIGN OWNED BY app TO app_admin;, rodado dentro do banco.
Liberar a aplicação no pg_hba.conf
O usuário app não tem conta no Linux, então o método peer não serve para ele. Acrescente uma linha que exige senha para ele, só neste banco, acima das linhas que já existem:
# Debian: /etc/postgresql/17/main/pg_hba.conf
# Rocky Linux / Oracle Linux: /var/lib/pgsql/data/pg_hba.conf
# TIPO BANCO USUÁRIO ENDEREÇO MÉTODO
local app_db app scram-sha-256
local app_db app_admin scram-sha-256
Recarregue o serviço:
sudo systemctl reload postgresql
Proteger o arquivo com a senha
A senha do banco fica em algum arquivo de configuração da aplicação, como config/db.php ou .env. Deixe-o com o dono root e o grupo do servidor web, que só pode ler:
# Debian (troque o caminho pelo da sua aplicação)
sudo chown root:www-data /var/www/app/config/db.php
sudo chmod 640 /var/www/app/config/db.php
# Rocky Linux / Oracle Linux
sudo chown root:apache /var/www/app/config/db.php
sudo chmod 640 /var/www/app/config/db.php
Sempre que possível, mantenha esse arquivo fora da pasta publicada na web. A senha do app_admin não deve ficar na aplicação: ela é usada só por quem instala e atualiza.
Testando
Entre com o usuário da aplicação, pelo socket:
psql "host=/var/run/postgresql dbname=app_db user=app"
Dentro do prompt, confira:
\conninfo
-- Deve funcionar
SELECT * FROM clientes;
-- Deve falhar com "must be owner of table clientes"
DROP TABLE clientes;
Quando a aplicação roda em outro servidor, o PostgreSQL precisa aceitar conexões pela rede. Vamos abrir só o necessário, em camadas: o servidor escuta na rede, o pg_hba.conf só aceita o servidor da aplicação, com TLS, e o firewall só deixa esse servidor passar.
Nos exemplos, os endereços são estes. Troque pelos da sua rede:
Servidor do banco (PostgreSQL): 192.168.10.10
Servidor da aplicação: 192.168.10.20
Escutar na rede
No servidor do banco, inclua o IP do próprio servidor no listen_addresses. Manter o localhost na lista não atrapalha e ajuda as ferramentas locais:
# Debian: /etc/postgresql/17/main/postgresql.conf
# Rocky Linux / Oracle Linux: /var/lib/pgsql/data/postgresql.conf
listen_addresses = 'localhost,192.168.10.10'
Usar o IP do servidor, e não '*', faz o PostgreSQL escutar só na interface dessa rede. Reinicie o serviço e confira:
sudo systemctl restart postgresql
# Deve aparecer 192.168.10.10:5432
sudo ss -tlnp | grep 5432
Liberar o servidor da aplicação no pg_hba.conf
Acrescente a regra acima das linhas que já existem. O tipo hostssl só aceita a conexão se ela vier criptografada:
# Debian: /etc/postgresql/17/main/pg_hba.conf
# Rocky Linux / Oracle Linux: /var/lib/pgsql/data/pg_hba.conf
# TIPO BANCO USUÁRIO ENDEREÇO MÉTODO
hostssl app_db app 192.168.10.20/32 scram-sha-256
Recarregue o serviço:
sudo systemctl reload postgresql
O /32 no fim do endereço quer dizer "só este IP". Evite regras como 0.0.0.0/0, que aceitam qualquer origem. Se a aplicação rodar em mais de um servidor, acrescente uma linha para cada IP.
No Debian, o TLS já vem ligado, e a regra funciona de imediato. No Rocky Linux e no Oracle Linux, ela só vai funcionar depois que o TLS for ativado, na próxima seção.
Liberar o firewall só para a aplicação
No Rocky Linux e no Oracle Linux, o firewalld já vem ativo. Use uma regra que aceita só o servidor da aplicação:
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.10.20" port port="5432" protocol="tcp" accept'
sudo firewall-cmd --reload
# Conferir
sudo firewall-cmd --list-rich-rules
No Debian, não há firewall ativo por padrão. Com o ufw, libere o SSH antes de ativar o firewall, ou você perde o acesso ao servidor:
sudo apt install ufw
sudo ufw allow OpenSSH
sudo ufw allow from 192.168.10.20 to any port 5432 proto tcp
sudo ufw enable
# Conferir
sudo ufw status numbered
SELinux no Rocky Linux e no Oracle Linux
No servidor do banco, o SELinux já permite a porta 5432. Só é preciso ajustar se você trocar a porta, por exemplo para 5433:
sudo semanage port -a -t postgresql_port_t -p tcp 5433
No servidor da aplicação, se ele também for Rocky Linux ou Oracle Linux e a aplicação rodar no Apache, o SELinux bloqueia, por padrão, conexões do servidor web com bancos de dados na rede. O teste pelo terminal funciona, mas a aplicação não conecta. Para liberar:
# No servidor da aplicação
sudo setsebool -P httpd_can_network_connect_db on
Testando a partir do servidor da aplicação
Instale o cliente do PostgreSQL no servidor da aplicação e tente conectar:
# Debian
sudo apt install postgresql-client
# Rocky Linux / Oracle Linux
sudo dnf install postgresql
psql "host=192.168.10.10 dbname=app_db user=app sslmode=require"
Se não conectar, a mensagem de erro indica qual camada está barrando:
Connection timed out
→ Firewall bloqueando ou servidor inacessível.
Connection refused
→ O PostgreSQL não está escutando nesse IP. Confira o listen_addresses e o ss -tlnp.
no pg_hba.conf entry for host "192.168.10.20", user "app", database "app_db"
→ Não existe regra para essa origem. Confira o pg_hba.conf e se ele foi recarregado.
Se a mensagem terminar com "no encryption", a conexão não usou TLS.
password authentication failed for user "app"
→ A regra existe, mas a senha está errada.
Faça também o teste contrário: de uma terceira máquina da rede, a conexão deve falhar com o erro de firewall.
Com o banco aberto na rede, a senha e os dados passam pelo cabo. O TLS criptografa a conexão, e a regra hostssl do pg_hba.conf garante que nenhuma conexão sem criptografia seja aceita. No cenário 1 isso não é necessário: o socket local nunca sai do servidor.
O PostgreSQL já vem com TLS?
No Debian, sim: a instalação liga o TLS com um certificado genérico do sistema (o "snakeoil"). Ele criptografa a conexão, mas não permite que a aplicação confirme que está falando com o servidor certo. No Rocky Linux e no Oracle Linux, o TLS vem desligado. Para conferir, no psql:
SHOW ssl;
Nos dois casos, vale criar o seu próprio certificado.
Gerar os certificados
Vamos criar uma autoridade certificadora (CA) própria e, com ela, assinar o certificado do servidor. No servidor do banco, em uma pasta de trabalho:
mkdir ~/pg-tls && cd ~/pg-tls
# 1) A CA: chave e certificado, válidos por 10 anos
openssl req -x509 -newkey rsa:4096 -nodes -days 3650 \
-keyout ca-key.pem -out ca.pem \
-subj "/CN=PostgreSQL Lab CA"
# 2) A chave do servidor e o pedido de certificado
openssl req -newkey rsa:2048 -nodes \
-keyout server.key -out server.csr \
-subj "/CN=192.168.10.10"
# 3) O certificado do servidor, assinado pela CA
printf "subjectAltName=IP:192.168.10.10\n" > server-ext.cnf
openssl x509 -req -in server.csr -days 825 \
-CA ca.pem -CAkey ca-key.pem -CAcreateserial \
-extfile server-ext.cnf -out server.crt
# Conferir
openssl verify -CAfile ca.pem server.crt
Copie o certificado e a chave para a pasta de dados do PostgreSQL. A chave precisa pertencer à conta postgres e ter permissão 600; com uma permissão mais aberta, o PostgreSQL se recusa a usá-la:
# Debian (troque 17 pela sua versão)
sudo cp server.crt server.key /var/lib/postgresql/17/main/
sudo chown postgres:postgres /var/lib/postgresql/17/main/server.*
sudo chmod 600 /var/lib/postgresql/17/main/server.key
# Rocky Linux / Oracle Linux
sudo cp server.crt server.key /var/lib/pgsql/data/
sudo chown postgres:postgres /var/lib/pgsql/data/server.*
sudo chmod 600 /var/lib/pgsql/data/server.key
sudo restorecon -v /var/lib/pgsql/data/server.*
A chave da CA (ca-key.pem) não deve ficar no servidor do banco: guarde-a em um lugar seguro e apague a pasta de trabalho depois.
Configurar o PostgreSQL
# Debian: /etc/postgresql/17/main/postgresql.conf
# Rocky Linux / Oracle Linux: /var/lib/pgsql/data/postgresql.conf
ssl = on
ssl_cert_file = '/var/lib/postgresql/17/main/server.crt'
ssl_key_file = '/var/lib/postgresql/17/main/server.key'
# Rocky Linux / Oracle Linux: '/var/lib/pgsql/data/server.crt' e '/var/lib/pgsql/data/server.key'
ssl_min_protocol_version = 'TLSv1.2'
No Debian, as linhas ssl_cert_file e ssl_key_file já existem, apontando para o certificado genérico; altere-as em vez de criar linhas novas. As configurações de TLS valem com um reload:
sudo systemctl reload postgresql
Testando a partir do servidor da aplicação
Copie o ca.pem (só ele, nunca as chaves) para o servidor da aplicação, em /etc/ssl/certs/postgres-lab-ca.pem. Depois conecte validando o servidor com sslmode=verify-full:
psql "host=192.168.10.10 dbname=app_db user=app sslmode=verify-full sslrootcert=/etc/ssl/certs/postgres-lab-ca.pem"
Dentro do prompt, confirme que a conexão está criptografada:
SELECT ssl, version FROM pg_stat_ssl WHERE pid = pg_backend_pid();
-- Esperado: t | TLSv1.3
Agora o teste contrário. Sem TLS, a regra hostssl não combina, e a conexão é recusada com no pg_hba.conf entry ... no encryption:
psql "host=192.168.10.10 dbname=app_db user=app sslmode=disable"
Anote a data de validade
O certificado vale 825 dias. Quando ele vencer, a aplicação para de conectar. Anote a data no seu controle de mudanças e programe a renovação:
# Debian
sudo openssl x509 -in /var/lib/postgresql/17/main/server.crt -noout -enddate
# Rocky Linux / Oracle Linux
sudo openssl x509 -in /var/lib/pgsql/data/server.crt -noout -enddate
Com a aplicação conectando do jeito certo, o próximo passo é organizar as demais contas do banco: relatórios, consultas pontuais, acessos temporários.
Roles de grupo
No PostgreSQL, uma role sem login funciona como um grupo. Você dá as permissões para o grupo e coloca as pessoas nele. Exemplo: um grupo só de leitura para quem tira relatórios do banco de produção. No psql, como postgres:
-- O grupo, sem login
CREATE ROLE leitura NOLOGIN;
\c app_db
GRANT CONNECT ON DATABASE app_db TO leitura;
GRANT USAGE ON SCHEMA public TO leitura;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO leitura;
-- Tabelas que o app_admin criar no futuro também ficam legíveis
ALTER DEFAULT PRIVILEGES FOR ROLE app_admin IN SCHEMA public
GRANT SELECT ON TABLES TO leitura;
Agora, crie a conta de quem vai usar, já dentro do grupo:
CREATE ROLE analista01 LOGIN IN ROLE leitura;
\password analista01
Diferente do MariaDB, aqui não há passo extra para ativar o grupo: por padrão, a role herda as permissões dos grupos de que faz parte assim que conecta.
Libere a conta no pg_hba.conf para o IP de onde ela vai conectar, acima das linhas genéricas:
# Debian: /etc/postgresql/17/main/pg_hba.conf
# Rocky Linux / Oracle Linux: /var/lib/pgsql/data/pg_hba.conf
hostssl app_db analista01 192.168.10.30/32 scram-sha-256
Lembre-se de liberar também esse IP no firewall, com a mesma regra do cenário 2.
Para limitar quantas conexões a conta pode abrir ao mesmo tempo, o que evita que um relatório pesado ocupe o banco inteiro:
ALTER ROLE analista01 CONNECTION LIMIT 5;
Senhas e validade
O PostgreSQL não tem, por padrão, uma regra de complexidade de senhas. Por isso, gere senhas longas e aleatórias, de preferência com um gerenciador de senhas. Para contas de pessoas, você pode definir uma data de validade; depois dela, a senha deixa de funcionar:
ALTER ROLE analista01 VALID UNTIL '2027-03-31';
Não faça isso com a conta da aplicação: quando a validade vence, a aplicação para de conectar.
Bloquear contas sem apagar
Quando alguém sai da equipe ou um acesso temporário termina, tire o login em vez de apagar a role. As permissões ficam registradas, e o acesso pode ser restaurado:
-- Bloquear
ALTER ROLE analista01 NOLOGIN;
-- Desbloquear, se necessário
ALTER ROLE analista01 LOGIN;
Para quem entra pelo método peer, como o dev01, o bloqueio pode ser feito direto no Linux. O usermod -L sozinho não basta, porque quem usa chave SSH continua entrando; expire a conta:
sudo usermod --expiredate 1 dev01
Revisão periódica
De tempos em tempos, por exemplo a cada trimestre, confira quem tem acesso ao banco:
-- 1) Todas as roles, com login, superusuário e validade
SELECT rolname, rolcanlogin, rolsuper, rolvaliduntil
FROM pg_roles
WHERE rolname NOT LIKE 'pg_%'
ORDER BY rolname;
-- 2) As regras de acesso em uso
SELECT line_number, type, database, user_name, address, auth_method
FROM pg_hba_file_rules;
Dentro de cada banco, o psql também mostra as permissões das tabelas e as permissões padrão:
\dp
\ddp
Só o postgres deve aparecer como superusuário. Contas sem dono conhecido devem ser bloqueadas primeiro e apagadas depois de um tempo sem reclamação. Guarde o resultado da revisão: em uma auditoria, mostrar que os acessos são revisados com frequência vale tanto quanto os acessos estarem corretos.
Até aqui, testamos com o cliente psql. Mas quem vai conectar de verdade é a aplicação. Vamos usar um script PHP bem simples, que faz o mesmo que uma aplicação web faz ao abrir uma conexão, usando o PDO.
Instale o PHP de linha de comando e o driver do PostgreSQL no servidor da aplicação (no cenário 1, é o próprio servidor do banco):
# Debian
sudo apt install php-cli php-pgsql
# Rocky Linux / Oracle Linux
sudo dnf install php-cli php-pgsql
O arquivo de configuração
A senha fica em um arquivo separado e protegido, e não dentro do código. Crie o arquivo:
sudo mkdir -p /var/www/app/config
sudo nano /var/www/app/config/db.php
Conteúdo:
<?php
// Configuração de acesso ao banco
// Dono root, grupo do servidor web, permissão 640
return [
'usuario' => 'app',
'senha' => 'senha_gerada',
];
Ajuste o dono e a permissão do arquivo:
# Debian
sudo chown root:www-data /var/www/app/config/db.php
sudo chmod 640 /var/www/app/config/db.php
# Rocky Linux / Oracle Linux
sudo chown root:apache /var/www/app/config/db.php
sudo chmod 640 /var/www/app/config/db.php
Cenário 1: conexão pelo socket
Crie o script de teste em /var/www/app/teste-cenario1.php. No PDO do PostgreSQL, um host que começa com barra é a pasta do socket:
<?php
// Cenário 1: aplicação e banco no mesmo servidor
$cfg = require '/var/www/app/config/db.php';
$dsn = 'pgsql:host=/var/run/postgresql;dbname=app_db';
$pdo = new PDO($dsn, $cfg['usuario'], $cfg['senha'], [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
]);
echo 'Conectado como: ', $pdo->query('SELECT current_user')->fetchColumn(), PHP_EOL;
Rode o script com a conta do servidor web, que é quem executa a aplicação de verdade:
# Debian
sudo -u www-data php /var/www/app/teste-cenario1.php
# Rocky Linux / Oracle Linux
sudo -u apache php /var/www/app/teste-cenario1.php
O resultado esperado é Conectado como: app.
Cenário 2: conexão pela rede, com TLS
No servidor da aplicação, crie /var/www/app/teste-cenario2.php. As opções de TLS vão direto na linha de conexão:
<?php
// Cenário 2: aplicação em outro servidor, com TLS
$cfg = require '/var/www/app/config/db.php';
$dsn = 'pgsql:host=192.168.10.10;port=5432;dbname=app_db;'
. 'sslmode=verify-full;sslrootcert=/etc/ssl/certs/postgres-lab-ca.pem';
$pdo = new PDO($dsn, $cfg['usuario'], $cfg['senha'], [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
]);
echo 'Conectado como: ', $pdo->query('SELECT current_user')->fetchColumn(), PHP_EOL;
$tls = $pdo->query('SELECT version FROM pg_stat_ssl WHERE pid = pg_backend_pid()')->fetchColumn();
echo 'Criptografia: ', $tls ?: 'NENHUMA', PHP_EOL;
Rode o script com a conta do servidor web:
# Debian
sudo -u www-data php /var/www/app/teste-cenario2.php
# Rocky Linux / Oracle Linux
sudo -u apache php /var/www/app/teste-cenario2.php
O resultado esperado é Conectado como: app e Criptografia: TLSv1.3 (ou TLSv1.2).
O que acontece sem cada camada
Faça os testes abaixo e compare com o resultado esperado:
Teste Resultado esperado
---------------------------------------------------------------------------
Rodar o script com o seu usuário comum, "Permission denied" ao ler o
sem sudo -u db.php (a senha está protegida)
Cenário 1 sem a linha "local app_db app" Peer authentication failed
no pg_hba.conf for user "app"
Pelo script, tentar DROP TABLE clientes must be owner of table clientes
Cenário 2 com sslmode=disable no pg_hba.conf entry ...
no encryption
Cenário 2 a partir de uma terceira máquina Connection timed out (firewall)
Cenário 2 no Apache do Rocky, sem o Permission denied (SELinux)
httpd_can_network_connect_db
O último teste só aparece quando o script roda pelo servidor web, e não pelo terminal. Quando a aplicação não conecta mas o teste pelo terminal funciona, o SELinux é o primeiro lugar para olhar.
Ao terminar, apague os scripts de teste:
sudo rm /var/www/app/teste-cenario1.php /var/www/app/teste-cenario2.php
Use esta lista para revisar o seu servidor. Cada item corresponde a uma seção deste post.
Contas no Linux
Cada pessoa entra no servidor com a sua própria conta; a conta postgres do Linux não tem senha.
Quem cuida do banco está no grupo dba, sem sudo completo (o sudo -l confirma).
Os arquivos em /etc/sudoers.d/ foram criados com o visudo, têm permissão 0440 e não usam curingas nos argumentos.
Contas no PostgreSQL
A aplicação usa duas roles: uma dona do banco, só para instalar e atualizar, e outra só com leitura e gravação de dados.
As permissões padrão (ALTER DEFAULT PRIVILEGES) estão configuradas para as tabelas novas.
Só o postgres é superusuário.
As contas de pessoas têm validade, e as contas sem uso estão sem login.
pg_hba.conf e rede
As regras específicas ficam acima das genéricas, e nenhuma regra aceita 0.0.0.0/0.
Cenário 1: listen_addresses com localhost ou vazio.
Cenário 2: listen_addresses com o IP do servidor, regras hostssl, e o firewall libera a porta 5432 apenas para os IPs necessários.
A porta 5432 nunca fica exposta para a internet.
Arquivos e certificados
O arquivo com a senha do banco tem permissão 640, dono root e o grupo do servidor web.
A chave privada do servidor tem permissão 600 e pertence à conta postgres.
A chave da CA não fica no servidor do banco, e a validade do certificado está anotada.
Rotina
Os acessos são revisados a cada trimestre, e o resultado fica guardado.
Os scripts de teste foram apagados.
Os backups do post anterior estão rodando, incluindo o backup das roles.
Com as contas separadas no Linux, roles diferentes para instalar e para usar a aplicação, um pg_hba.conf que só aceita quem precisa e a conexão criptografada, o PostgreSQL deixa de depender de uma única senha para estar seguro. Cada camada barra um tipo de problema, e uma falha em uma delas não abre o banco inteiro.
No próximo post da série, o foco é manter o PostgreSQL funcionando bem em produção: monitoramento, ajustes de desempenho, VACUUM, backups contínuos, atualização de versão e replicação.
Esta série tem três posts. Siga na ordem para ir da instalação até um servidor pronto para produção.
Segurança e acesso ao PostgreSQL (você está aqui)