Como chegar a um banco de dados que só escuta em localhost?
Uma instância do Postgres num servidor remoto fica vinculada a 127.0.0.1 de propósito. Nada na rede consegue alcançá-la, só processos rodando naquela mesma máquina. Seu notebook não é essa máquina, então psql -h remote-server -p 5432 falha antes mesmo de chegar à autenticação. Abrir a porta para o mundo anula o motivo pelo qual ela foi vinculada ao loopback.
O que você quer é tomar emprestada, por alguns minutos, a visão que a máquina remota tem de 127.0.0.1, a partir do seu próprio terminal. O SSH já tem uma conexão criptografada e autenticada aberta entre as duas máquinas no momento em que você faz login. O port forwarding reaproveita essa conexão para carregar tráfego além do seu shell.
Como funciona o port forwarding do SSH por baixo dos panos?
Uma sessão SSH comum parece um único cano: as teclas entram, a saída do terminal volta. Por baixo, o protocolo multiplexa vários canais independentes nessa mesma conexão criptografada. O shell é um canal. Uma conexão TCP redirecionada é outro.
Peça ao ssh para redirecionar uma porta e ele abre um novo canal para cada conexão feita àquela porta, carrega os bytes brutos pela sessão existente e os entrega do outro lado. Sem certificado novo, sem segundo login. O túnel herda qualquer acesso que o login SSH já tenha.
Guarde isso antes de olhar para as flags. -L, -R e -D não são três recursos diferentes: são o mesmo mecanismo de channel-forwarding, apontado em direções diferentes.
Como usar o redirecionamento de porta local do SSH (-L)?
| |
Leia o argumento de -L como local_port:destination_host:destination_port. Isso abre a porta 5432 no seu notebook. Qualquer coisa que se conecte a ela é redirecionada pelo túnel até localhost:5432 como visto a partir de db-host, que é a instância do Postgres vinculada ao próprio loopback. Num segundo terminal:
| |
O psql acha que está falando com um banco de dados local. Na verdade está falando com a interface de loopback de db-host através do SSH.
O host de destino no meio não precisa ser o próprio servidor SSH:
| |
Isso redireciona através de jumpbox para uma terceira máquina que jumpbox consegue alcançar e você não. É o formato que a maioria das pessoas tem em mente ao dizer “túnel SSH”: uma máquina com o acesso que você não tem, redirecionando para uma rede que você não conseguiria tocar de outra forma. Se você já roda o Postgres localmente, a porta 5432 do seu notebook está ocupada; escolha outra porta local como 15432 e passe -p 15432 para o psql.
Como expor uma porta local com o redirecionamento remoto do SSH (-R)?
-R faz a mesma coisa ao contrário. A sintaxe é remote_port:destination_host:destination_port, mas o listener abre no lado remoto.
| |
Agora public-host escuta na porta 8080, e qualquer conexão a ela volta pelo túnel até localhost:3000 na sua máquina: seu servidor de desenvolvimento local. Você ganha um endereço alcançável para um provedor de webhooks ou um colega sem abrir uma porta no seu próprio firewall, emprestando uma máquina que já tem uma.
Um comportamento padrão costuma confundir. O sshd vincula portas remote-forwarded à interface de loopback do host remoto, então curl public-host:8080 a partir de uma terceira máquina falha mesmo com o redirecionamento ativo. Só processos no próprio public-host conseguem alcançá-la. Mudar isso exige GatewayPorts yes no sshd_config daquele host, algo que só o administrador do servidor controla. Nenhuma flag no seu comando ssh sobrepõe isso.
Como transformar um túnel SSH num proxy SOCKS (-D)?
-L e -R redirecionam, cada um, para um destino fixo, decidido ao rodar o comando. -D não escolhe destino nenhum:
| |
Isso abre um proxy SOCKS em localhost:1080. Qualquer aplicação apontada para ele escolhe o destino a cada conexão, e o ssh a retransmite a partir da posição de remote-host na rede:
| |
A parte -hostname importa. Com o simples --socks5, o curl resolve internal-service.corp no seu notebook, onde esse nome não existe, e falha antes mesmo de o proxy ser usado. --socks5-hostname manda o nome pelo túnel para que seja remote-host a resolvê-lo.
Os navegadores funcionam do mesmo jeito assim que você configura o proxy nas opções de rede (e ativa “proxy DNS when using SOCKS v5” no Firefox). Cada site então vê o IP de remote-host e passa pelas regras de firewall dele. É assim que você alcança uma rede interna inteira através de um único jump box, ou verifica o que um site entrega para outra região, sem escrever um redirecionamento separado para cada destino.
Qual tipo de redirecionamento SSH escolher: -L, -R ou -D?
-L local | -R remoto | -D dinâmico | |
|---|---|---|---|
| O listener abre em | Sua máquina | O host remoto | Sua máquina |
| Destino | Fixo, definido na linha de comando | Fixo, definido na linha de comando | Escolhido por conexão pela aplicação |
| Uso típico | Alcançar um serviço que só o host remoto enxerga | Expor seu serviço local para o lado remoto | Rotear tráfego arbitrário pela rede do host remoto |
| Precisa do outro lado | Nada além de um login SSH normal | GatewayPorts se outros precisarem alcançá-lo | Nada além de um login SSH normal |
Como manter um túnel SSH aberto em segundo plano?
Dois problemas aparecem rápido: digitar o comando completo toda vez, e o túnel morrer quando o terminal fecha. O ~/.ssh/config resolve o primeiro:
| |
ssh db-tunnel agora abre os dois redirecionamentos de uma vez. LocalForward, RemoteForward e DynamicForward correspondem a -L, -R e -D, uma linha por redirecionamento, quantas forem necessárias num bloco Host.
Para um redirecionamento que precisa ficar de pé sem supervisão, adicione -N (não executar nenhum comando remoto, só manter o túnel) e -f (ir para segundo plano após autenticar), mais um keepalive para que uma conexão caída silenciosamente seja notada em vez de ficar pendurada:
| |
Ainda assim isso não reconecta quando a rede cai. O ssh termina e o redirecionamento some. O autossh envolve o mesmo comando e o reinicia quando a conexão morre:
| |
-M 0 desliga a porta de monitoramento própria do autossh e deixa o keepalive ServerAlive* detectar uma conexão morta. No OpenSSH moderno, essa é a configuração a usar.
Quando não usar túneis SSH?
Uma porta redirecionada herda o acesso do login SSH que a abriu. Conveniente quando é você quem segura a chave, e uma exposição real se a chave ou a sessão forem comprometidas, porque o túnel entrega tudo que aquele login consegue alcançar. AllowTcpForwarding no sshd_config é o controle do lado do servidor (padrão yes). Definir como no fecha esse caminho, mas qualquer pessoa com acesso shell na máquina ainda pode rodar seu próprio forwarder, então isso só ajuda se você também restringir o acesso shell.
Túneis também param de escalar além de um punhado de pessoas ou serviços. Cada um é um processo preso a um terminal ou a uma instância do autossh. Não existe um lugar central para ver quem tem um túnel aberto, revogar acesso sem mexer em cada configuração de cliente, ou aplicar uma política única para todos os usuários. Nesse ponto, uma VPN, ou um proxy identity-aware na frente do serviço, deveria substituir a pilha de túneis pessoais.
Eles também não são a mesma ferramenta que um reverse proxy ou o Cloudflare Tunnel, ainda que os três resolvam o mesmo problema: tornar alcançável algo que antes não era. Um reverse proxy fica permanentemente na frente de um serviço e encara a internet pública. O Cloudflare Tunnel está mais perto do -R: uma conexão de saída do seu lado que se torna alcançável de fora, mas através da edge da Cloudflare, com um hostname estável e nenhuma porta de servidor para gerenciar. Um reverse proxy ou o Cloudflare Tunnel dependem de certificados, como descrito em como funciona o handshake TLS; um túnel SSH não precisa de nenhum. A criptografia é a própria sessão SSH, autenticada pela sua chave em vez de por um certificado em que um navegador confiaria.
O que configurar primeiro?
Pegue o túnel que você mais redigita e transforme-o hoje num bloco Host no ~/.ssh/config. Alcançar algo que só um host remoto enxerga é -L. Expor um serviço local através de uma máquina com endereço público é -R. Rotear o tráfego de uma aplicação por uma rede remota sem escolher destinos de antemão é -D. Uma vez no config, ssh -N -f db-tunnel é tudo que você precisa digitar, e você para de errar o número da porta às 23h.