Para uma melhor experiência com caixas de código, experimente ver o site em um Desktop.
No post anterior, instalamos o MariaDB 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 root. Isso funciona para aprender, mas não é assim que um banco de dados deve ficar em produção.
Na prática, o MariaDB 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 root, a senha do banco em um arquivo que qualquer usuário consegue ler, a porta 3306 aberta para a rede inteira e todo mundo da equipe usando uma conta que pode fazer qualquer coisa no servidor.
Neste post, vamos montar o acesso ao MariaDB 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 MariaDB 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 MariaDB 10.11 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 MariaDB já instalado, como mostrado no post anterior, e de uma segunda máquina (uma VM serve) para testar o cenário 2.
Antes de pensar na aplicação, vale organizar quem acessa o servidor. Em muitas instalações, toda a equipe entra com a mesma conta, ou cada pessoa tem um usuário que pode rodar qualquer comando com sudo. Funciona, mas qualquer erro de digitação vira um problema no servidor inteiro, e depois não há como saber quem fez o quê.
A ideia aqui é separar três tipos de conta:
a conta do serviço, que roda o MariaDB;
as contas das pessoas (administradores e desenvolvedores), cada uma com o seu nome;
a conta da aplicação, que veremos nos cenários 1 e 2.
A conta do serviço já existe
O MariaDB não roda como root. A instalação cria a conta de sistema mysql, que é dona dos arquivos de dados e não permite login. Para conferir:
# Usuário que está rodando o servidor
ps -C mariadbd -o user,pid,cmd
# Shell da conta mysql (deve ser /bin/false ou /sbin/nologin)
getent passwd mysql
Essa conta não precisa de nenhum ajuste. O importante é nunca usá-la para outra coisa nem dar uma senha a ela.
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 o grupo precisa. No exemplo, o grupo se chama dba e o desenvolvedor, dev01:
# Criar o grupo e o usuário
sudo groupadd dba
sudo useradd -m -s /bin/bash -G dba dev01
sudo passwd dev01
Um arquivo de regras para o grupo
O sudo lê, além do arquivo principal /etc/sudoers, todos os arquivos da pasta /etc/sudoers.d/. Isso permite manter um arquivo para cada grupo ou função, em vez de um arquivo enorme com tudo misturado. Para tirar ou mudar as permissões de um grupo, basta mexer no arquivo dele, sem risco de afetar os outros.
Três cuidados com essa pasta:
Crie e edite os arquivos sempre com o visudo. Ele verifica a sintaxe antes de salvar; um erro em um arquivo de sudo pode bloquear o acesso de administrador ao servidor.
O nome do arquivo não pode ter ponto nem terminar com ~. O sudo ignora arquivos como dba.conf ou dba~, sem avisar.
O arquivo deve pertencer ao root e ter permissão 0440 (somente leitura).
Para criar o arquivo do grupo dba:
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 MariaDB,
# 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, para usar
# depois na regra. 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 do comando.
# ----------------------------------------------------------------
# Iniciar, parar, reiniciar e ver o status do serviço.
# O sudo compara o comando inteiro: o grupo pode reiniciar o
# mariadb, mas não qualquer outro serviço do servidor.
Cmnd_Alias MARIADB_SVC = /usr/bin/systemctl status mariadb, \
/usr/bin/systemctl start mariadb, \
/usr/bin/systemctl stop mariadb, \
/usr/bin/systemctl restart mariadb
# Ler os logs do serviço, sempre com --no-pager.
# Sem ele, o journalctl abre o less, e de dentro do less é possível
# chamar um shell, que rodaria como root.
Cmnd_Alias MARIADB_LOG = /usr/bin/journalctl --no-pager -u mariadb, \
/usr/bin/journalctl --no-pager -u mariadb -n 100
# Rocky Linux / Oracle Linux: o log principal também fica em um
# arquivo. O tail só mostra as linhas; não abre editor nem shell.
# No Debian, este apelido pode ficar: o arquivo não existe lá.
Cmnd_Alias MARIADB_LOGFILE = /usr/bin/tail -n 100 /var/log/mariadb/mariadb.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) e só grava
# o resultado no lugar certo. Mantenha só a linha da sua distribuição.
# Debian:
Cmnd_Alias MARIADB_CONF = sudoedit /etc/mysql/mariadb.conf.d/50-server.cnf
# Rocky Linux / Oracle Linux:
# Cmnd_Alias MARIADB_CONF = sudoedit /etc/my.cnf.d/mariadb-server.cnf
# ----------------------------------------------------------------
# 2) Regra do grupo
# Formato: QUEM ONDE=(COMO_QUEM) O_QUÊ
# %dba -> o % indica um grupo do Linux (sem %, seria um usuário)
# ALL -> vale em qualquer servidor que use este arquivo
# (root) -> os comandos rodam como root
# no final -> os apelidos liberados, separados por vírgula
# A senha do próprio usuário continua sendo pedida.
# ----------------------------------------------------------------
%dba ALL=(root) MARIADB_SVC, MARIADB_LOG, MARIADB_LOGFILE, MARIADB_CONF
Repare que toda a permissão do grupo cabe em uma única linha no final. Para dar uma nova permissão ao grupo dba, crie um novo Cmnd_Alias e acrescente o nome dele a essa linha; para tirar, basta remover o nome. Com um arquivo assim para cada grupo (dba, desenvolvimento, suporte), quem lê entende em segundos o que cada equipe pode fazer no servidor, e isso facilita muito uma auditoria.
Depois de salvar, confira o dono e a permissão do arquivo:
ls -l /etc/sudoers.d/dba
# Esperado: -r--r----- 1 root root ... /etc/sudoers.d/dba
Para ver o que um usuário pode fazer, entre com ele e rode:
sudo -l
O resultado lista exatamente os comandos liberados. Se aparecer (ALL) ALL, o usuário ainda tem sudo completo por outro caminho, como o grupo sudo no Debian ou wheel no Rocky, e precisa ser retirado dele.
Atenção: as versões atuais do systemd já protegem o paginador quando ele é chamado pelo sudo, mas não dependa disso; libere apenas a forma com --no-pager. Pelo mesmo motivo, nunca libere editores (vi, nano), less, more ou find em um arquivo de sudo: todos permitem abrir um shell como root.
Entrar no MariaDB sem senha, com a conta do Linux
O MariaDB tem um recurso que combina muito bem com essa separação: o plugin unix_socket. Com ele, o usuário do MariaDB é ligado ao usuário do Linux de mesmo nome. Quem está logado como dev01 no servidor entra no MariaDB como dev01, sem senha, e ninguém mais consegue usar essa conta.
No prompt do MariaDB (sudo mariadb), crie o usuário e dê acesso só ao banco de desenvolvimento:
CREATE USER 'dev01'@'localhost' IDENTIFIED VIA unix_socket;
GRANT ALL PRIVILEGES ON app_dev.* TO 'dev01'@'localhost';
Logado como dev01, basta digitar:
mariadb
O desenvolvedor trabalha à vontade no banco app_dev, mas não enxerga o banco de produção nem consegue mexer em usuários. 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 que o MariaDB, 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. Isso permite deixar o MariaDB completamente fechado para a rede.
Fechar o MariaDB para a rede
Há duas formas de fazer isso. Ambas vão na seção [mysqld] do arquivo de configuração do servidor, indicado no início de cada bloco.
A primeira forma é aceitar conexões de rede só do próprio servidor. No Debian, essa já é a configuração padrão; no Rocky Linux e no Oracle Linux, é preciso acrescentar a linha:
# Debian: /etc/mysql/mariadb.conf.d/50-server.cnf
# Rocky Linux / Oracle Linux: /etc/my.cnf.d/mariadb-server.cnf
[mysqld]
bind-address = 127.0.0.1
A segunda forma é desligar a rede de vez. O MariaDB passa a aceitar só o socket, e a porta 3306 nem chega a ser aberta:
# Debian: /etc/mysql/mariadb.conf.d/50-server.cnf
# Rocky Linux / Oracle Linux: /etc/my.cnf.d/mariadb-server.cnf
[mysqld]
skip-networking
O skip-networking é a opção mais fechada, mas só funciona se a aplicação conectar pelo socket. Em PHP, por exemplo, usar localhost como endereço do banco já faz a conexão pelo socket; usar 127.0.0.1 faz pela rede e deixa de funcionar com essa opção. Se tiver dúvida, comece com o bind-address.
Depois de alterar, reinicie o serviço e confira:
sudo systemctl restart mariadb
# Com bind-address, deve aparecer 127.0.0.1:3306
# Com skip-networking, não deve aparecer nada
sudo ss -tlnp | grep 3306
A aplicação vai precisar do caminho do socket, que muda conforme a distribuição. Para confirmar, no prompt do MariaDB:
SHOW VARIABLES LIKE 'socket';
O resultado padrão em cada distribuição é:
# Debian: /run/mysqld/mysqld.sock
# Rocky Linux / Oracle Linux: /var/lib/mysql/mysql.sock
Um usuário só para a aplicação
A aplicação nunca deve conectar como root. Crie um banco e um usuário só para ela. Para a senha, gere uma aleatória em vez de inventar:
openssl rand -base64 24
No prompt do MariaDB (sudo mariadb):
CREATE DATABASE app_prod CHARACTER SET utf8mb4;
CREATE USER 'app'@'localhost' IDENTIFIED BY 'senha_gerada';
GRANT SELECT, INSERT, UPDATE, DELETE ON app_prod.* TO 'app'@'localhost';
O 'app'@'localhost' só aceita conexões vindas do próprio servidor. Mesmo que alguém descubra a senha, não consegue usá-la de outra máquina.
No dia a dia, a aplicação só lê e grava dados, e as quatro permissões acima bastam. Mas a instalação e as atualizações da aplicação costumam criar e alterar tabelas. Para esses momentos, libere as permissões de estrutura e retire-as quando terminar:
-- Antes de instalar ou atualizar a aplicação
GRANT CREATE, ALTER, INDEX, DROP, REFERENCES ON app_prod.* TO 'app'@'localhost';
-- Depois que a instalação ou atualização terminar
REVOKE CREATE, ALTER, INDEX, DROP, REFERENCES ON app_prod.* FROM 'app'@'localhost';
Assim, se a aplicação tiver uma falha de segurança, quem a explorar consegue no máximo mexer nos dados, mas não apagar tabelas nem alterar a estrutura do banco. Algumas aplicações exigem essas permissões o tempo todo; nesse caso, consulte a documentação da aplicação e libere só o que ela pede.
Proteger o arquivo com a senha
A senha do banco fica gravada em algum arquivo de configuração da aplicação, como config/db.php ou .env. Se esse arquivo puder ser lido por qualquer usuário do servidor, todo o cuidado com o usuário do banco perde o sentido.
O servidor web roda com uma conta própria: www-data no Debian e apache (ou nginx) no Rocky Linux e no Oracle Linux. Deixe o arquivo com o dono root e o grupo do servidor web, e permissão de leitura só para os dois:
# 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
Com 640, o root pode ler e alterar, o servidor web só pode ler e os demais usuários não enxergam o conteúdo. Para testar:
# Deve mostrar o conteúdo
sudo -u www-data cat /var/www/app/config/db.php
# Com um usuário comum, deve dar "Permissão negada"
cat /var/www/app/config/db.php
Sempre que possível, mantenha esse arquivo fora da pasta publicada na web. Se ele precisar ficar dentro dela, confirme que o servidor web não entrega o conteúdo do arquivo a quem acessar o endereço pelo navegador.
Dica: aplicação sem senha nenhuma
Se a aplicação rodar com uma conta própria no Linux, como um pool do PHP-FPM com o usuário app, dá para usar o mesmo unix_socket da seção anterior. O MariaDB reconhece a conta do sistema, e a senha deixa de existir:
CREATE USER 'app'@'localhost' IDENTIFIED VIA unix_socket;
Evite fazer isso com a conta padrão do servidor web (www-data ou apache): todos os sites do servidor rodam com ela e ganhariam acesso ao banco.
Testando
Entre com o usuário da aplicação e confira o que ele pode fazer:
mariadb -u app -p app_prod
Dentro do prompt, rode:
SHOW GRANTS;
-- Deve funcionar
SELECT * FROM clientes;
-- Deve falhar com "command denied"
DROP TABLE clientes;
Quando a aplicação roda em outro servidor, o MariaDB precisa aceitar conexões pela rede. É aqui que a maioria das instalações fica exposta: a porta 3306 aberta para qualquer origem e um usuário que aceita conexão de qualquer lugar. Vamos abrir só o necessário, em camadas: o MariaDB escuta na rede, o usuário só aceita o servidor da aplicação e o firewall só deixa esse servidor passar.
Nos exemplos, os endereços são estes. Troque pelos da sua rede:
Servidor do banco (MariaDB): 192.168.10.10
Servidor da aplicação: 192.168.10.20
Escutar na rede
No servidor do banco, troque o bind-address pelo IP do próprio servidor, na seção [mysqld] do arquivo de configuração. Se você usou skip-networking no cenário 1, remova essa linha.
# Debian: /etc/mysql/mariadb.conf.d/50-server.cnf
# Rocky Linux / Oracle Linux: /etc/my.cnf.d/mariadb-server.cnf
[mysqld]
bind-address = 192.168.10.10
Usar o IP do servidor, e não 0.0.0.0, faz o MariaDB escutar só na interface dessa rede. Se o servidor tiver outra placa de rede, por exemplo de backup ou de gerenciamento, o banco não fica acessível por ela.
sudo systemctl restart mariadb
# Deve aparecer 192.168.10.10:3306
sudo ss -tlnp | grep 3306
Um usuário só para o servidor da aplicação
No MariaDB, o usuário é formado pelo nome e pela origem. 'app'@'localhost' e 'app'@'192.168.10.20' são contas diferentes, cada uma com sua senha e suas permissões. Crie a conta para o servidor da aplicação:
CREATE USER 'app'@'192.168.10.20' IDENTIFIED BY 'senha_gerada';
GRANT SELECT, INSERT, UPDATE, DELETE ON app_prod.* TO 'app'@'192.168.10.20';
Evite 'app'@'%'. Com o %, a conta aceita conexão de qualquer endereço, e a senha passa a ser a única proteção do banco. Se a aplicação rodar em mais de um servidor, crie uma conta para cada IP ou use a faixa da rede da aplicação, como 'app'@'192.168.10.%'.
As permissões de estrutura para instalar e atualizar a aplicação seguem a mesma regra do cenário 1: libere antes e retire depois.
Liberar o firewall só para a aplicação
O firewall é a camada que impede que qualquer outra máquina sequer chegue até o MariaDB.
No Rocky Linux e no Oracle Linux, o firewalld já vem ativo. Não libere o serviço mysql inteiro, que abre a porta para todos; 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="3306" protocol="tcp" accept'
sudo firewall-cmd --reload
# Conferir
sudo firewall-cmd --list-rich-rules
No Debian, não há firewall ativo por padrão. Se você ainda não usa um, o ufw é o caminho mais simples. Atenção à ordem dos comandos: libere o SSH antes de ativar o ufw, 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 3306 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 que o MariaDB use a porta 3306, e nada precisa ser feito. Só será preciso ajustar se você trocar a porta, por exemplo para 3307:
sudo semanage port -a -t mysqld_port_t -p tcp 3307
O ponto que mais confunde está 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
Não desative o SELinux para resolver esse tipo de problema. Ele está fazendo exatamente o trabalho dele, e o ajuste correto é liberar só o que a aplicação precisa.
Testando a partir do servidor da aplicação
Instale o cliente do MariaDB no servidor da aplicação e tente conectar:
# Debian
sudo apt install mariadb-client
# Rocky Linux / Oracle Linux
sudo dnf install mariadb
mariadb -h 192.168.10.10 -u app -p app_prod
Se não conectar, a mensagem de erro indica qual camada está barrando:
ERROR 2002 ... (110) ou (113)
→ Firewall bloqueando ou servidor inacessível. Confira as regras de firewall e a rota.
ERROR 2002 ... (111) Connection refused
→ O MariaDB não está escutando nesse IP. Confira o bind-address e o ss -tlnp.
ERROR 1130 ... Host '192.168.10.20' is not allowed to connect
→ Não existe usuário para esse IP. Confira o CREATE USER.
ERROR 1045 ... Access denied for user 'app'@'192.168.10.20'
→ O usuário 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. É essa falha que confirma que a camada está funcionando.
Com o banco aberto na rede, a senha da aplicação e todos os dados passam pelo cabo. Sem criptografia, qualquer pessoa que consiga capturar o tráfego da rede lê tudo em texto puro. O TLS resolve isso, e é o mesmo protocolo que protege os sites com https.
No cenário 1 isso não é necessário: a conexão pelo socket local nunca sai do servidor.
O MariaDB já vem com TLS?
Depende da versão. A partir do MariaDB 11.4 (o que vem no Debian 13), o servidor já gera um certificado sozinho e aceita conexões criptografadas desde a instalação. No MariaDB 10.11 (o que vem no Rocky Linux 10 e no Oracle Linux), o TLS vem desligado. Para conferir, no prompt do MariaDB:
SHOW VARIABLES LIKE 'have_ssl';
Mesmo quando o TLS já vem ligado, vale criar o seu próprio certificado. Com ele, a aplicação consegue confirmar que está falando com o servidor certo, e não com alguém se passando por ele no meio do caminho.
Gerar os certificados
Vamos criar uma autoridade certificadora (CA) própria e, com ela, assinar o certificado do servidor. Faça isso no servidor do banco, em uma pasta de trabalho:
mkdir ~/mariadb-tls && cd ~/mariadb-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=MariaDB Lab CA"
# 2) A chave do servidor e o pedido de certificado
openssl req -newkey rsa:2048 -nodes \
-keyout server-key.pem -out server-req.pem \
-subj "/CN=192.168.10.10"
# 3) O certificado do servidor, assinado pela CA
# O subjectAltName com o IP permite que o cliente valide o servidor
printf "subjectAltName=IP:192.168.10.10\n" > server-ext.cnf
openssl x509 -req -in server-req.pem -days 825 \
-CA ca.pem -CAkey ca-key.pem -CAcreateserial \
-extfile server-ext.cnf -out server-cert.pem
# Conferir se o certificado foi assinado pela CA
openssl verify -CAfile ca.pem server-cert.pem
Se o servidor tiver um nome no DNS e a aplicação conectar pelo nome, inclua o nome também: subjectAltName=IP:192.168.10.10,DNS:db01.exemplo.local.
Agora copie os arquivos do servidor para uma pasta protegida. A chave privada deve poder ser lida só pela conta mysql:
sudo mkdir -p /etc/mysql/ssl
sudo cp ca.pem server-cert.pem server-key.pem /etc/mysql/ssl/
sudo chown -R mysql:mysql /etc/mysql/ssl
sudo chmod 600 /etc/mysql/ssl/server-key.pem
sudo chmod 644 /etc/mysql/ssl/ca.pem /etc/mysql/ssl/server-cert.pem
# Rocky Linux / Oracle Linux: aplicar o contexto correto do SELinux
sudo restorecon -Rv /etc/mysql/ssl
A chave da CA (ca-key.pem) é o que permite criar certificados válidos em nome da sua CA. Ela não deve ficar no servidor do banco: guarde-a em um lugar seguro, fora do servidor, e apague a pasta de trabalho depois.
Configurar o MariaDB
No arquivo de configuração do servidor, na seção [mysqld]:
# Debian: /etc/mysql/mariadb.conf.d/50-server.cnf
# Rocky Linux / Oracle Linux: /etc/my.cnf.d/mariadb-server.cnf
[mysqld]
ssl_ca = /etc/mysql/ssl/ca.pem
ssl_cert = /etc/mysql/ssl/server-cert.pem
ssl_key = /etc/mysql/ssl/server-key.pem
tls_version = TLSv1.2,TLSv1.3
O tls_version recusa as versões antigas do protocolo, que já não são consideradas seguras. Reinicie o serviço:
sudo systemctl restart mariadb
Depois, no prompt do MariaDB:
SHOW VARIABLES LIKE 'have_ssl'; -- deve mostrar YES
SHOW VARIABLES LIKE 'ssl_cert'; -- deve mostrar o seu certificado
Exigir TLS do usuário da aplicação
Ligar o TLS no servidor não obriga ninguém a usá-lo. Para que a conta da aplicação só consiga conectar com criptografia:
ALTER USER 'app'@'192.168.10.20' REQUIRE SSL;
Se quiser exigir TLS de todas as conexões pela rede, e não só dessa conta, acrescente a linha abaixo. As conexões pelo socket local continuam funcionando normalmente.
# Debian: /etc/mysql/mariadb.conf.d/50-server.cnf
# Rocky Linux / Oracle Linux: /etc/my.cnf.d/mariadb-server.cnf
[mysqld]
require_secure_transport = ON
Testando a partir do servidor da aplicação
Copie o ca.pem (só ele, nunca as chaves) para o servidor da aplicação, por exemplo com scp, e coloque-o em /etc/mysql/ssl/ca.pem. Depois conecte validando o servidor:
mariadb -h 192.168.10.10 -u app -p \
--ssl-ca=/etc/mysql/ssl/ca.pem --ssl-verify-server-cert app_prod
Dentro do prompt, confirme que a conexão está criptografada:
SHOW STATUS LIKE 'Ssl_version'; -- deve mostrar TLSv1.2 ou TLSv1.3
SHOW STATUS LIKE 'Ssl_cipher'; -- deve mostrar o algoritmo em uso
Agora o teste contrário. Sem TLS, a conexão deve ser recusada com ERROR 1045 ... Access denied:
mariadb -h 192.168.10.10 -u app -p --skip-ssl app_prod
Se a conexão falhar mesmo com o --ssl-ca, com uma mensagem sobre o certificado, quase sempre o IP ou o nome usado no -h não está no subjectAltName do certificado.
Anote a data de validade
O certificado do servidor vale 825 dias. Quando ele vencer, a aplicação para de conectar. Anote a data no seu controle de mudanças ou no seu sistema de chamados e programe a renovação com antecedência:
openssl x509 -in /etc/mysql/ssl/server-cert.pem -noout -enddate
Com a aplicação conectando do jeito certo, o próximo passo é organizar as demais contas do banco. Com o tempo, vão aparecendo pedidos: alguém precisa de um relatório, outra pessoa precisa consultar uma tabela, um fornecedor precisa de acesso temporário. Se cada pedido vira um GRANT solto, em pouco tempo ninguém sabe mais quem pode fazer o quê.
Roles: permissões em grupo
Uma role é um conjunto de permissões com nome. Você dá as permissões para a role e depois dá a role para as pessoas. Quando o acesso precisar mudar, você altera a role, e todos que a têm passam a seguir a regra nova.
Exemplo: uma role só de leitura para quem tira relatórios do banco de produção.
-- Criar a role e dar a ela apenas leitura
CREATE ROLE leitura_relatorios;
GRANT SELECT ON app_prod.* TO leitura_relatorios;
-- Criar a conta de quem vai usar e dar a role
CREATE USER 'analista01'@'192.168.10.30' IDENTIFIED BY 'senha_gerada';
GRANT leitura_relatorios TO 'analista01'@'192.168.10.30';
-- Ativar a role automaticamente no login
SET DEFAULT ROLE leitura_relatorios FOR 'analista01'@'192.168.10.30';
O último comando é o que mais se esquece. No MariaDB, a role não fica ativa sozinha: sem o SET DEFAULT ROLE, a pessoa conecta, mas não enxerga nada até rodar SET ROLE leitura_relatorios; manualmente.
Se o relatório só precisa de algumas tabelas, libere apenas elas, em vez do banco inteiro:
GRANT SELECT ON app_prod.clientes TO leitura_relatorios;
Para limitar também quantas conexões uma conta pode abrir ao mesmo tempo, o que evita que um relatório pesado ocupe o banco inteiro:
ALTER USER 'analista01'@'192.168.10.30' WITH MAX_USER_CONNECTIONS 5;
Regras para senhas
O MariaDB tem um plugin que recusa senhas fracas na hora em que elas são criadas. Ative-o no arquivo de configuração:
# Debian: /etc/mysql/mariadb.conf.d/50-server.cnf
# Rocky Linux / Oracle Linux: /etc/my.cnf.d/mariadb-server.cnf
[mysqld]
plugin_load_add = simple_password_check
simple_password_check_minimal_length = 12
Reinicie o serviço para carregar o plugin:
sudo systemctl restart mariadb
Com o plugin ativo, toda senha nova precisa ter pelo menos 12 caracteres, com letras maiúsculas, minúsculas, números e símbolos. Um CREATE USER ou ALTER USER com senha fraca é recusado com erro. As senhas que já existiam não são verificadas; troque-as se necessário.
Para contas de pessoas, você também pode exigir troca periódica:
ALTER USER 'analista01'@'192.168.10.30' PASSWORD EXPIRE INTERVAL 90 DAY;
Não faça isso com a conta da aplicação: quando a senha expira, a aplicação para de conectar. Contas de aplicação usam senhas longas e aleatórias, trocadas de forma planejada.
Bloquear contas sem apagar
Quando alguém sai da equipe ou um acesso temporário termina, bloqueie a conta em vez de apagar na hora. As permissões ficam registradas, e o acesso pode ser restaurado se for preciso:
-- Bloquear
ALTER USER 'analista01'@'192.168.10.30' ACCOUNT LOCK;
-- Conferir (a saída mostra ACCOUNT LOCK)
SHOW CREATE USER 'analista01'@'192.168.10.30';
-- Desbloquear, se necessário
ALTER USER 'analista01'@'192.168.10.30' ACCOUNT UNLOCK;
Para quem usa unix_socket, como o dev01 da seção de contas no Linux, o bloqueio pode ser feito direto no Linux: com a conta do sistema expirada, a pessoa não entra no servidor e, portanto, também não entra no banco.
sudo usermod --expiredate 1 dev01
O usermod -L sozinho não basta nesse caso: ele bloqueia a senha, mas quem usa chave SSH continua entrando.
Revisão periódica
De tempos em tempos, por exemplo a cada trimestre, confira quem tem acesso ao banco. Três consultas cobrem o principal:
-- 1) Todas as contas e de onde podem conectar
SELECT User, Host, plugin FROM mysql.user ORDER BY User, Host;
-- 2) Contas que aceitam conexão de qualquer origem
SELECT User, Host FROM mysql.user WHERE Host = '%';
-- 3) Quem tem privilégios sobre o servidor inteiro
SELECT GRANTEE, PRIVILEGE_TYPE
FROM information_schema.USER_PRIVILEGES
ORDER BY GRANTEE;
Para cada conta que aparecer, confira se ela ainda é necessária e se as permissões continuam corretas com SHOW GRANTS FOR. 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 tudo com o cliente mariadb. 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. Ele usa o PDO, a forma padrão de conectar o PHP a bancos de dados, que a maioria das aplicações usa por baixo.
Instale o PHP de linha de comando e o driver do MariaDB no servidor da aplicação (no cenário 1, é o próprio servidor do banco):
# Debian
sudo apt install php-cli php-mysql
# Rocky Linux / Oracle Linux
sudo dnf install php-cli php-mysqlnd
O arquivo de configuração
Como na seção do cenário 1, 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:
<?php
// Cenário 1: aplicação e banco no mesmo servidor
$cfg = require '/var/www/app/config/db.php';
// Debian: /run/mysqld/mysqld.sock
// Rocky Linux / Oracle Linux: /var/lib/mysql/mysql.sock
$dsn = 'mysql:unix_socket=/run/mysqld/mysqld.sock;dbname=app_prod;charset=utf8mb4';
$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@localhost.
Cenário 2: conexão pela rede, com TLS
No servidor da aplicação, crie /var/www/app/teste-cenario2.php. A diferença está no endereço do servidor e nas duas opções de TLS:
<?php
// Cenário 2: aplicação em outro servidor, com TLS
$cfg = require '/var/www/app/config/db.php';
$dsn = 'mysql:host=192.168.10.10;port=3306;dbname=app_prod;charset=utf8mb4';
$pdo = new PDO($dsn, $cfg['usuario'], $cfg['senha'], [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
// Certificado da CA, para validar o servidor
PDO::MYSQL_ATTR_SSL_CA => '/etc/mysql/ssl/ca.pem',
// Recusa a conexão se o certificado não for válido
PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT => true,
]);
echo 'Conectado como: ', $pdo->query('SELECT CURRENT_USER()')->fetchColumn(), PHP_EOL;
$tls = $pdo->query("SHOW STATUS LIKE 'Ssl_version'")->fetch(PDO::FETCH_NUM);
echo 'Criptografia: ', $tls[1] ?: '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@192.168.10.20 e Criptografia: TLSv1.3 (ou TLSv1.2).
Nas versões mais novas do PHP, essas opções também existem com outro nome, como Pdo\Mysql::ATTR_SSL_CA. Os nomes usados acima continuam funcionando, mas, se o PHP mostrar um aviso de "deprecated", basta trocar pelo nome novo.
O que acontece sem cada camada
A melhor forma de confirmar que a segurança está funcionando é ver cada camada barrar o que deve barrar. 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 com skip-networking, trocando o SQLSTATE[HY000] [2002]
socket por host=127.0.0.1 Connection refused
Pelo script, tentar DROP TABLE clientes SQLSTATE[42000] ... 1142
DROP command denied
Cenário 2 sem as duas opções de TLS SQLSTATE[HY000] [1045]
Access denied (REQUIRE SSL)
Cenário 2 a partir de uma terceira máquina SQLSTATE[HY000] [2002]
Connection timed out (firewall)
Cenário 2 no Apache do Rocky, sem o SQLSTATE[HY000] [2002]
httpd_can_network_connect_db Permission denied (SELinux)
O último teste só aparece quando o script roda pelo servidor web, e não pelo terminal. Por isso, 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. Eles mostram informações sobre o banco e não devem ficar no servidor:
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; ninguém usa o root direto.
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 liberam editores nem paginadores.
Contas no MariaDB
A aplicação conecta com um usuário próprio, nunca com o root.
No dia a dia, o usuário da aplicação tem só SELECT, INSERT, UPDATE e DELETE no banco dela.
Nenhuma conta usa '%' como origem sem um motivo documentado.
As senhas novas passam pelo simple_password_check, e as contas de pessoas expiram.
As contas sem uso estão bloqueadas.
Rede
Cenário 1: o MariaDB escuta só em 127.0.0.1 ou usa skip-networking.
Cenário 2: o MariaDB escuta só no IP do servidor, e o firewall libera a porta 3306 apenas para o servidor da aplicação.
A porta 3306 nunca fica exposta para a internet.
As conexões pela rede usam TLS 1.2 ou 1.3, e as contas de rede têm REQUIRE SSL.
Arquivos
O arquivo com a senha do banco tem permissão 640, dono root e o grupo do servidor web, e fica fora da pasta publicada.
A chave privada do servidor tem permissão 600 e pertence à conta mysql.
A chave da CA não fica no servidor do banco.
Rotina
Os acessos ao banco são revisados a cada trimestre, e o resultado fica guardado.
A data de validade do certificado está anotada, com a renovação programada.
Os scripts de teste foram apagados.
Os backups do post anterior estão rodando e o restore já foi testado.
Com as contas separadas no Linux, um usuário próprio para a aplicação, a rede aberta só para quem precisa e a conexão criptografada, o MariaDB 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 MariaDB funcionando bem em produção: monitoramento, ajustes de desempenho, backups automáticos com o mariadb-backup, atualização de versão e replicação entre servidores.
Esta série tem três posts. Se você está começando, siga na ordem:
Segurança e acesso ao MariaDB (você está aqui)