Programação Por Equipe DevBras

Capa do artigo: MySQL em outro servidor: abrir o banco sem abrir para a internet

MySQL em outro servidor: abrir o banco sem abrir para a internet

Separe a aplicação do MySQL ajustando três travas: endereço de escuta, usuário amarrado ao IP da aplicação e firewall. Sem root e sem %.

Você moveu o MySQL para uma máquina própria, apontou a aplicação para o novo IP e veio o erro de conexão. Alguém sugere liberar o root para % e abrir a porta 3306 no firewall "só para testar". Funciona, e no dia seguinte a porta aparece em varredura automática. A resposta curta: o banco precisa de três travas ajustadas juntas. O MySQL escuta no IP privado, o usuário da aplicação existe só para o IP da aplicação, e o firewall deixa a porta passar só a partir desse IP. Nada de root, nada de %, nada de porta aberta para o mundo.

Separar aplicação e banco é um desenho saudável. Cada máquina cresce do seu jeito, e um pico de CPU do PHP não disputa recurso com a consulta pesada do relatório. O desenho só fica ruim quando a pressa de "fazer conectar" abre mais do que precisava.

Por que a conexão não entra, e por que abrir tudo é o erro

Numa instalação padrão, o MySQL escuta só em 127.0.0.1. Isso é proposital: ninguém de fora chega nele. Quando a aplicação muda de máquina, três coisas precisam mudar, e cada uma tem um sintoma diferente:

  • Endereço de escuta. Se o MySQL só escuta em localhost, a conexão de fora é recusada na hora.
  • Usuário e host. No MySQL, usuário é o par nome e origem. 'app'@'localhost' e 'app'@'10.0.0.4' são contas diferentes, com senha e permissões próprias. Se não existe conta para a origem da aplicação, o servidor responde, mas recusa o login.
  • Firewall. O do sistema operacional e o do provedor de nuvem, quando há um. Se algum deles barra a porta, a conexão fica pendurada até estourar o tempo limite.

O atalho do root com % resolve as três de uma vez abrindo demais. Se a porta também está exposta, qualquer robô da internet passa a tentar senha nesse usuário, e um vazamento da senha da aplicação vira acesso administrativo ao banco inteiro.

Como liberar o MySQL só para a aplicação

Nos exemplos, o servidor do banco tem o IP privado 10.0.0.5 e a aplicação roda em 10.0.0.4, na mesma rede privada. Troque pelos seus.

  1. Escute no IP privado. No Ubuntu com MySQL, o arquivo costuma ser /etc/mysql/mysql.conf.d/mysqld.cnf. No MariaDB, algo como /etc/mysql/mariadb.conf.d/50-server.cnf. Ajuste a linha bind-address para o IP privado, não para 0.0.0.0, e reinicie o serviço.
  2. Confirme onde ele escuta. Rode sudo ss -tlnp | grep 3306. Você quer ver 10.0.0.5:3306, não 0.0.0.0:3306.
  3. Crie o usuário da aplicação amarrado ao IP dela, com permissão só no schema dela.
  4. Libere a porta só para esse IP no firewall do sistema e no grupo de segurança do provedor.
  5. Teste a partir do servidor da aplicação, não do seu notebook.
# /etc/mysql/mysql.conf.d/mysqld.cnf
[mysqld]
bind-address = 10.0.0.5
mysqlx-bind-address = 127.0.0.1

O MySQL 8 também abre a porta do X Protocol, 33060, com configuração própria. Se a aplicação não usa, deixe essa escutando só localmente, como no exemplo.

CREATE USER 'loja_app'@'10.0.0.4' IDENTIFIED BY 'gere-uma-senha-longa-e-aleatoria';
GRANT SELECT, INSERT, UPDATE, DELETE ON loja.* TO 'loja_app'@'10.0.0.4';

Com CREATE USER e GRANT, não precisa de FLUSH PRIVILEGES. Esse comando só é necessário quando alguém mexe direto nas tabelas internas de permissão, o que você não deve fazer.

Repare que o usuário não tem CREATE, ALTER nem DROP. Se as migrations rodam no deploy, dê a elas um usuário separado, com essas permissões, usado só nesse momento. A aplicação que atende o público não precisa poder apagar tabela.

No firewall do servidor do banco, com ufw:

sudo ufw allow from 10.0.0.4 to any port 3306 proto tcp
sudo ufw status numbered

Confira se não sobrou uma regra antiga do tipo 3306 ALLOW Anywhere. Ela anula a regra restrita. No provedor de nuvem, a regra de entrada da porta 3306 deve ter como origem o IP da aplicação ou o grupo de segurança dela, nunca 0.0.0.0/0.

Por fim, do servidor da aplicação:

nc -zv 10.0.0.5 3306
mysql -h 10.0.0.5 -u loja_app -p loja -e "SELECT CURRENT_USER()"

O CURRENT_USER() mostra com qual conta o servidor te reconheceu. Se aparecer algo diferente de loja_app@10.0.0.4, outra conta mais genérica está casando primeiro.

Como ler o erro de conexão

Cada trava tem uma assinatura. Lendo a mensagem, você sabe qual falta, sem sair abrindo tudo:

  • Tempo esgotado ao conectar. O pacote não chega. É firewall, do sistema ou do provedor, ou rota de rede.
  • Conexão recusada. O pacote chega, mas ninguém escuta naquele IP e porta. É o bind-address ou o serviço parado.
  • "Host ... is not allowed to connect". O MySQL respondeu, mas não existe conta para essa origem. Falta o usuário com o host certo.
  • "Access denied for user". A conta existe, mas a senha não confere, ou o servidor casou sua conexão com outra conta.

Se do notebook entra e do servidor da aplicação não, a regra está liberando o lugar errado. Se dos dois entra sem VPN, a porta está no mundo.

Criptografia e acesso administrativo

Rede privada não é sinônimo de rede confiável. Em provedor de nuvem, o tráfego entre máquinas pode passar por infraestrutura compartilhada. O MySQL 8 suporta TLS, e você pode exigir conexão criptografada por usuário com REQUIRE SSL ou no servidor inteiro com require_secure_transport. No cliente, prefira o modo que verifica o certificado do servidor. Nunca resolva erro de certificado desligando a verificação.

Administração é outra conversa. Gente não conecta no banco direto da internet. Use VPN, um bastion ou um túnel SSH a partir do seu computador, com chave SSH em vez de senha:

ssh -L 3307:127.0.0.1:3306 voce@ip-publico-do-banco
# em outro terminal
mysql -h 127.0.0.1 -P 3307 -u admin -p

O túnel chega no MySQL como conexão local. O usuário administrativo pode existir só para localhost, e a porta 3306 continua fechada para fora. A senha da aplicação, por sua vez, mora em variável de ambiente ou num cofre de segredos, não no repositório. Se ela já foi para o Git, troque a senha antes de limpar o histórico.

Erros comuns

  • Usar bind-address = 0.0.0.0 em máquina com IP público. O banco passa a escutar na interface pública, e só o firewall separa ele da internet.
  • Criar 'app'@'%' por preguiça. A senha da aplicação passa a valer de qualquer origem. Vazou, entrou.
  • Conectar a aplicação como root. Uma injeção de SQL deixa de ler uma tabela e passa a poder apagar o servidor inteiro.
  • Esquecer o firewall do provedor. Você libera no ufw, o grupo de segurança barra, e a conexão fica pendurada sem mensagem útil.
  • Deixar regra antiga de teste no firewall. A regra restrita existe, mas a genérica de "liberar 3306" também, e vence.
  • Testar só do notebook. O teste passa pelo caminho errado e não prova nada sobre a aplicação.

Checklist

  • ss -tlnp mostra o MySQL escutando no IP privado, não em 0.0.0.0.
  • O X Protocol, se não é usado, escuta só em localhost.
  • O usuário da aplicação tem host igual ao IP da aplicação e permissão só no schema dela.
  • Migrations usam outro usuário, que a aplicação em execução não conhece.
  • Firewall do sistema e do provedor liberam a porta 3306 só para a aplicação.
  • Uma tentativa de conexão a partir de um IP qualquer da internet expira.
  • Administração passa por VPN, bastion ou túnel SSH.
  • A senha do banco está em variável de ambiente ou cofre, fora do Git.

Rode agora, no servidor do banco, SELECT user, host FROM mysql.user; e olhe a coluna de host. Quantas contas aparecem com %, e alguma delas é a que a sua aplicação usa?

Avalie este artigo

Referência: http://www.jack.eti.br/acessando-mysql-remotamente-seperando-aplicacao-php-do-banco-de-dados/