SSH de Guerrilha

Boa parte do meu dia envolve usar ssh. Às vezes como comando direto para fazer acesso remoto a algum servidor e às vezes como ferramenta para poder executar alguma outra tarefa.

Nesse texto vamos ver os usos mais comuns que faço desse comando e algumas configurações que fui acumulando ao longo do tempo.

SSH Config

O primeiro ponto que temos que ter bastante atenção é o arquivo de configuração. Muita gente acha que apenas o ssh-server possui um config, mas isso não é verdade. O ssh-client (ou apenas ssh) também possui e perdemos a chance de melhorar muito seu uso quando deixamos de usar um config.

Server alias

O primeiro ponto que o config traz é a possibilidade de dar apelidos para servidores que você acessa com frequência.

A configuração mais simples que você pode fazer é essa:

Host meu-host
Hostname 10.234.3.26

A diretiva Host nos permite criar o alias. E a diretiva Hostname é o endereço real do servidor para o qual estamos criando esse alias.

A partir de agora você pode fazer:

$ ssh meu-host

Sempre que quiser acessar esse servidor. E tem um bônus, seu shell provavelmente auto completa esses alias, o que é uma mão a roda.

Nota: a cada linha encontrada do arquivo de configuração e que começa pela palavra Host significa um novo bloco de configurações. Esse bloco pode ser relacionado a um servidor (como acabamos de ver) ou a vários servidores (veremos isso já já).

Configuração para múltiplos servidores

Uma coisa que descobri depois de muito tempo usando SSH é que a diretiva Host permite o uso de wildcards, ou seja, posso criar um bloco de configuração que se aplica a múltiplos servidores, até mesmo servidores que não estão explicitamente configurados!

Um exemplo simples: Imagina que temos um config que usamos tanto em nossa máquina pessoal quanto na máquina do trabalho. Podemos então fazer o seguinte:

Nota: Não se importe, por enquanto, com diretivas novas nesse config de exemplo, veremos todas elas no detalhe mais adiante.

Host work.*
User usuario-do-trabalho
IdentityFile ~/.ssh/chave-do-trabalho

Host work.webserver
Hostname web.host.com.br

Host work.db
Hostname db.host.com.br

Host personal.app
Hostname myhost.com.br
User otheruser
IdentityFile ~/.ssh/otherkey

Nesse caso, sempre que fizermos ssh para um alias que comece com work. as configurações do bloco work.* serão aplicadas. Veja:

$ ssh -v work.db
OpenSSH_7.7p1, OpenSSL 1.1.0h  27 Mar 2018
debug1: Reading configuration data /home/daltonmatos/.ssh/config
debug1: /home/daltonmatos/.ssh/config line 1: Applying options for work.*
debug1: /home/daltonmatos/.ssh/config line 8: Applying options for work.db
debug1: Connecting to db.host.com.br [<IP>] port 22.
debug1: Connection established.
...
...
debug1: Authenticating to db.host.com.br:22 as 'usuario-do-trabalho'

Perceba as linhas:

debug1: /home/daltonmatos/.ssh/config line 1: Applying options for work.*
debug1: /home/daltonmatos/.ssh/config line 8: Applying options for work.db

Isso nos mostra que as duas configurações (work.* e work.db) foram aplicadas. Esse acesso que acabamos de fazer é equivalente a essa linha de comando (caso não tivéssemos nosso config)

$ ssh -i ~/.ssh/chave-do-trabalho usuario-do-trabalho@db.host.com.br

Bem melhor a versão com config né? E ainda tem auto-complete.

Configuração wildcard para hosts dinâmicos

Existe uma forma de se aplicar configurações wildcard para hosts que nem estão no config.

Apesar da diretiva Host designar um alias, podemos usar parte de um IP nessa diretiva, assim:

Host 10.235.*
User user
Port 22

Perceba que nem temos a diretiva Hostname e isso não é um problema. A partir de agora, sempre que acessarmos um servidor cujo “apelido” comece com 10.235 essas configurações serão aplicadas.

Então, no fim das contas, qualquer servidor que possua seu IP começado por 10.235 entra nessa lista, veja:

$ ssh -v 10.235.10.44
OpenSSH_7.7p1, OpenSSL 1.1.0h  27 Mar 2018
debug1: Reading configuration data /home/daltonmatos/.ssh/config
debug1: /home/daltonmatos/.ssh/config line 16: Applying options for 10.235.*
debug1: Connecting to 10.235.10.44 [10.235.10.44] port 22.
debug1: Connection established.

Veja que o ssh aplicou todas as configurações de Host 10.235.* quando fizemos esse acesso, mesmo não existindo um bloco de configuração Host 10.235.10.44.

Usar o IP como alias é útil quando você não quer configurar uma centena de máquinas manualmente no seu config e para isso coloca ali só parte do IP e assim consegue acessar todas elas sem precisar ficar mexendo no seu config toda hora.

Outras opções interessantes para usar no Config

Existem outras opções que podemos colocar em um bloco de configuração. Aqui vamos ver algumas, mas nem de longe isso vai cobrir todas as opções possíveis.

Uma lista completa você consegue encontrar no manual do ssh_config (man ssh_config)

User, Port, IdentityFile

No exemplo que acabamos de ver dissemos apenas qual o IP (ou DNS) do servidor. Mas podemos por exemplo escolher o usuário, porta e qual chave deverá ser usada.

Host meu-host
Hostname 10.234.3.26
User meu-user
Port 22
IdentityKey ~/.ssh/minha-chave

Nesse caso, mesmo que nosso usuário local seja outrouser, quando digitarmos ssh meu-host vamos usar meu-user como usuário de conexão. A porta usada será a 22 e a chave privada será o arquivo ~/.ssh/minha-chave.

ProxyCommand

Essa opção é extremamente útil quando você precisa usar uma máquina como “ponte” para chegar em outra.

Por muito tempo eu copiei minha chave privada para essa “máquina ponte” para depois, de lá, fazer o ssh para a máquina destino. Pensando nisso, hoje, que tenho minha chave privada guardada em um dispositivo físico, seria inviável fazer essa cópia, ou se na pior das hipóteses eu resolvesse realmente copiar a chave privada, usar um token físico para guardá-la perderia totalmente o sentido.

Pensa em uma infra estrutura que está em algum cloud provider e que todas as máquinas são privadas, ou seja, não possuem IP público. Como acessar essas máquina via ssh?

Entra o ProxyCommand, com ele conseguimos pedir ao ssh que faça o acesso remoto com a ajuda de um comando intermediário.

Vamos ao exemplo:

Temos duas máquinas, uma privada com IP 10.235.44.69 e uma com IP público, que pode ser acessada pelo nome public.server.com.br. Essa máquina com IP público serve apenas como ponte, ou seja, não roda nada a não ser ssh-server.

Com o comando abaixo conseguimos chegar na máquina privada, sem copiar nossa chave privada pra lugar nenhum e sem precisar expor a máquina privada para internet.

$ ssh -o "ProxyCommand=ssh public.server.com.br -W %h:%p" `10.235.44.69`

Podemos fazer a mesma coisa no arquivo de configuração, assim:

Host 10.235.*
ProxyCommand ssh public -W %h:%p

Host public
Hostname public.server.com.br

Agora podemos fazer apenas ssh 10.235.44.69 e conseguiremos o mesmo acesso.

Você pode também encadear quantos “saltos” você quiser. Cada bloco de configuração (Host) pode ter seu próprio ProxyCommand.

Como exemplo, para eu acessar minha estação de trabalho eu faço 2 “saltos”. O caminho é mais ou menos esse: Minha casa -> AWS -> Datacenter on-premise -> Minha estação de trabalho.

Isso funciona porque a máquina da aws é pública, essa conta da aws tem VPN com o datacenter on-premise e esse datacenter tem VPN com o escritório. Tudo isso é feito com ProxyCommand e ainda assim digito apenas ssh minha-maquina para cair já dentro da minha estação de trabalho.

LocalForward ou -L

O LocalForward é útil quando você quer se conectar a alguma porta de uma máquina remota, novamente estamos falando de máquinas privadas que nem sequer possuem IP público.

Host myhost
Hostname 10.234.33.77
LocalForward 3306 127.0.0.1:3306

Nota: Aqui estamos assumindo que as configurações de ProxyCommand (caso necessárias) já estão feitas.

Usando esse config, quando acessamos ssh myhost uma porta (3306) é aberta em nossa máquina local e todo o tráfego que chegar nessa porta é automaticamente redirecionado para a porta remota no endereço 127.0.0.1:3306. Como nesse caso colocamos o destino como 127.0.0.1 significa que estamos querendo uma porta no host de destino.

Mas isso não é uma restrição, ou seja, podemos nos conectar no host A mas fazer o LocalForward para um host B. Para isso basta que o host A tenha conectividade direta com o host B. E você precisa ter acesso ssh ao host A para que consiga fazer esse LocalForward. Sua conexão com o Host A pode passar por quantas “máquinas-ponte” você quiser, não há restrições nesse sentido.

Então podemos fazer isso:

Host myhost
Hostname 10.235.20.66
ProxyCommand ssh other-host -W %h:%p
LocalForward 3306 10.40.5.88:3306

Nesse caso estamos nos conectando ao host myhost, usando uma máquina como “ponte” (other-host, que pode inclusive estar usando outras máquinas-ponte pelo caminho) e estamos querendo nos conectar na porta 3306 de um terceiro servidor, 10.40.5.88.

Com esse acesso feito, se você se conectar na porta localhost:3306 você estará, na verdade, se conectando na porta 3306 do host 10.40.5.88 que está em alguma rede privada em algum lugar do mundo.

Veja um exemplo:

$ ssh myhost
...
...
...
Authenticated to 10.235.20.66 (via proxy).
debug1: Local connections to LOCALHOST:3306 forwarded to remote address 127.0.0.1:3306
debug1: Local forwarding listening on ::1 port 3306.
debug1: channel 0: new [port listener]
debug1: Local forwarding listening on 127.0.0.1 port 3306.

Esse trecho acima indica que o redirecionamento da porta está feito.

A sintaxe dessa diretiva é: LocalForward [bind_address]:port host:hostport. Se bind_address for omitido, será usado 127.0.0.1. Podemos também usar * no parâmetro bind_address, nesse caso todas as interfcaes locais estarão com a porta aberta.

Essa funcionalidade também está disponível como uma command line flag, -L. A sintaxe é quase a mesma. A diferença é que em vez se separar os argumentos por espaço, separamos com :, assim: -L 127.0.0.1:3306:127.0.0.1:3306.

DynamicForward ou -D

O DynamicForward é uma opção especialmente útil quando queremos acessar portas de servidores remotos mas sem precisar nos preocupar em ficar criando redirecionamentos (-L).

O que essa opção faz é ligar localmente um Proxy SOCKS5 que usa a conexão SSH como “meio de transporte”.

Vamos pegar o mesmo cenário de antes, um servidor privado com alguns sistemas rodando, por exemplo uma app HTTP rodando nas portas 80 e 8080. O que podemos fazer é acessar uma máquina que tenha conectividade com essa (onde está rodando a app) e usar um Proxy SOCKS5 para “tunelar” nosso acesso até a máquina destino, vejamos o config:

Host meu-server
Hostname 10.244.40.66
DynamicForward 127.0.0.1:10010

E assim fazemos o acesso, normalmente:

$ ssh -v meu-server
...
...
debug1: Local connections to 127.0.0.1:10010 forwarded to remote address socks:0
debug1: Local forwarding listening on 127.0.0.1 port 10010.

Essa é a parte importante do output, onde ele confirma que o redirecionamento está sendo feito.

Nesse momento, pelo tempo que essa sessão SSH ficar ativa, a porta 10010 estará aberta em nossa máquina local (127.0.0.1) e podemos usar essa porta como Proxy.

Um exemplo de acesso a uma app, na porta 80 em um servidor privado, usando esse proxy seria assim (usando curl):

$ curl --socks5 127.0.0.1:10010 http://10.234.55.89/healthcheck

Sendo 10.234.55.89 um servidor qualquer que possui conectividade direta com o meu-server (10.244.40.66).

O que está acontecendo aqui é que apesar de estarmos rodando o curl localmente, quem faz o acesso real à app é o meu-server, afinal o curl está usando um Proxy para fazer o acesso por ele.

Todos os browsers modernos possuem opções para usar um Proxy SOCKS5, então essa opção torna-se especialmente útil por outro motivo: Poder burlar regras de saída de firewalls.

Se você está em um ambiente controlado (não por você) e essa rede impõe limites de acesso, essa opção pode ser útil para driblar esses limites. Para isso basta que essa rede permita acesso remoto via SSH. Se isso estiver permitido, basta tunelar todo o seu tráfego e você poderá acessar qualquer destino, mesmo os explicitamente bloqueados pela rede onde você está.

Mesmo se a rede bloquear a porta 22 (padrão do SSH) ainda pode existir uma saída: Rodar SSH na porta 443. A porta 443 é a padrão para HTTPS e geralmente essa porta é liberada mesmo em ambientes bem controlados.

ForwardAgent ou -A

Essa opção é útil para que possamos fazer ssh a partir da máquina remota, mas usando as mesmas credenciais locais. Imagina você se logando em um server remoto e a partir dessa máquina fazendo um git pull (ou git clone) de um repositório privado. Veja um exemplo:

$ ssh meu-host
daltonmatos@meu-host $> ssh git@github.com
Warning: Permanently added the RSA host key for IP address '192.30.253.112' to the list of known hosts.
Permission denied (publickey).

Agora com -A (ou com ForwardAgent no arquivo de config)

$ ssh -A meu-host
daltonmatos@meu-host $> ssh git@github.com                          
PTY allocation request failed on channel 0
Hi daltonmatos! You've successfully authenticated, but GitHub does not provide shell access.
Connection to github.com closed.

Na segunda sessão, consegui fazer uma autenticação com sucesso, mas usando minhas credenciais locais, sem precisar copiar nenhuma chave para lugaar nenhum.

Se usado no config essa diretiva recebe apenas um parâmetro, yes ou no, assim:

Host meu-host
Hostname 10.234.55.69
ForwardAgent yes

-N

A opção -N é útil quando você quer dar a oportunidade de alguém conectar em um servidor via SSH, mas ao mesmo tempo não quer que essa pessoa tenha acesso ao Shell desse servidor, ou seja, que não seja possível para essa pessoa rodar comandos arbitrários, mas que ainda assim ela possa usar as outras opções mostradas aqui, como Proxy (-D) e redirecionamento de porta (-L).

Útil quando você não quer dar shell no server remoto mas mesmo assim quer permitir Port forwarding.

Adicionando redirecionamento de portas depois da conexão já estar feita

Muita vezes iniciamos a sessão ssh e só depois lembramos que deveríamos ter chamado o comando ssh (lá no host de origem) com algum redirecionamento de portas ativado.

Passei muito tempo fechando a conexão ssh atual só para poder pegar o comando no histórico e adicionar as opções que estavam faltando.

Mas existe uma forma de fazer isso sem se desconectar do host remoto.

Para isso precisamos acessar um shell interno do ssh. Temos que estar com o buffer do terminal limpo, sempre dou alguns Enters para “garantir” isso.

Para entrar nesse Shell interno digite ~<space>C. Isso mesmo: “til+espaço+C maiúsculo”. Isso deve mostrar um novo prompt, assim: ssh>

Se estamos em uma conexão remota e queremos ligar o proxy SOCKS5, podemos fazer:

$ ssh meu-server
...
...
daltonmatos@meu-server $>
daltonmatos@meu-server $>
daltonmatos@meu-server $>
 
ssh> -D 10010
Forwarding port.

A partir de agora esta conexão SSH está funcinando como se tivesse sido chamada, desde o início, com ssh -D 10010 meu-server.

Essas são as opções disponíveis nesse shell interno:

ssh> help
Commands:
      -L[bind_address:]port:host:hostport    Request local forward
      -R[bind_address:]port:host:hostport    Request remote forward
      -D[bind_address:]port                  Request dynamic forward
      -KL[bind_address:]port                 Cancel local forward
      -KR[bind_address:]port                 Cancel remote forward
      -KD[bind_address:]port                 Cancel dynamic forward

Desconectado uma sessão “congelada”

Muitas vezes nos vemos com um terminal congelado porque uma sessão SSH está presa, seja por algum problema remoto ou porque a conexão caiu mesmo. Nesse caso, em vez de fecharmos o terminal inteiro (fiz isso por muito tempo) podemos acessar esse mesmo “shel interno”, mas agora em vez de usar C (C maiúsculo) vamos usar ponto, assim: ~<space>.

daltonmatos@meu-server $>
daltonmatos@meu-server $>
daltonmatos@meu-server $>
Connection to 10.234.55.68 closed.

Shell alias para facilitar o acesso (e cópia de arquivos) a máquinas privadas

Preciso tanto usar ssh com máquinas privadas que escrevi algumas funções e criei alguns alias que facilitam minha vida. Em vez de precisar configurar toda e qualquer máquina privada que faço acesso e de ficar adicionando configurações de ProxyCommand para cada novo servidor uso um alias que criei chamado ssh-via. Com esse alias posso escolher qual máquina vou usar como ponte e a sintaxe é: ssh-via <ponte> <maquina-destino>.

Então posso ter um servidor, por exmeplo, private-server. Quero usar esse servidor como ponte para chegar em outro, ou melhor, quero usá-lo para poder chegar uma série de máquinas, pertencentes a uma mesma rede. Então posso fazer isso:

$ ssh-via private-server other-server

Quaisquer opções de linha de comando podem ser passadas normalmente, basta que sejam passadas após o argumento que define a máquina-ponte, assim:

$ ssh-via private-server -D 10010 -L 3306:127.0.0.1:3306 other-server

SCP

Fiz o mesmo para conseguir copiar arquivos direto da minha máquna local para uma máquina remota (e privada!) e também para fazer o contrário, ou seja, copiar dados do servidor remoto para minha máquina local. Esse alias é o scp-via. Funciona do mesmo jeito do scp normal, mas recebe como primeiro parametro o máquina-ponte, assim:

$ scp-via private-server /tmp/file other-server:/tmp/file

Novamente, podemos passar quaisquer opções que o scp aceite, basta que seja após o parametro que define a máquina ponte:

$ scp-via private-server -C /tmp/file other-server:/tmp/file

O código dessas duas funções está abaixo, e está também em meu dotfiles.

_ssx_via(){
  COMMAND=${1}
  gateway=${2}
  shift; shift
  $COMMAND -o "ProxyCommand=ssh ${gateway} -W %h:%p" -o "StrictHostKeyChecking=no" $*
}

_ssh_via(){
  _ssx_via 'ssh' $*
}

_scp_via(){
  _ssx_via 'scp' $*
}