OpenVPN + SSH Proxy — Implementação Passo a Passo

OpenVPN + SSH Proxy — Implementação Passo a Passo

No post anterior expliquei o problema que me levou a criar o openvpn-ssh-proxy: a necessidade de acessar múltiplas redes VPN simultaneamente, sem conflito de rotas, sem instalar clientes pesados na máquina host. Agora vamos ao que interessa — colocar tudo pra funcionar.

Pré-requisitos

Antes de começar, você vai precisar de:

  • Docker instalado na máquina host (versão 20.10 ou superior)
  • Um ou mais arquivos .ovpn válidos (configuração de cliente OpenVPN fornecida pelo seu provedor de VPN ou administrador de rede)
  • Conhecimento básico de redes — saber o que é uma rota, uma interface TUN e um proxy SOCKS já basta

Não há nada para compilar ou instalar além do Docker. A imagem mauricioj/openvpn-ssh-proxy:latest já vem com OpenVPN e o servidor SSH embutidos.

Quick Start com docker run

O jeito mais rápido de testar é com um único docker run. Vou dissecar cada flag:

docker run -d \
  --device /dev/net/tun \
  --cap-add NET_ADMIN \
  -v /caminho/para/config.ovpn:/vpn/config.ovpn:ro \
  -e OVPN_FILE_PATH=/vpn/config.ovpn \
  -e OVPN_AUTH_USER=meu_usuario \
  -e OVPN_AUTH_PASS=minha_senha \
  -e SSH_TUNNEL_PORT=2222 \
  -e SSH_TUNNEL_USER=tunnel \
  -e SSH_TUNNEL_PASS=tunnelpass \
  -p 2222:2222 \
  --name vpn-trabalho \
  mauricioj/openvpn-ssh-proxy:latest

O que cada flag faz:

  • --device /dev/net/tun — expõe o dispositivo TUN do host para o container. O OpenVPN precisa criar uma interface tun0 dentro do container, e sem acesso a esse device, o processo falha imediatamente.
  • --cap-add NET_ADMIN — concede ao container a capability de Linux necessária para manipular interfaces de rede e tabelas de roteamento. Sem ela, o OpenVPN não consegue configurar as rotas.
  • -v /caminho/para/config.ovpn:/vpn/config.ovpn:ro — monta o arquivo de configuração dentro do container em modo somente leitura. Ajuste o caminho da esquerda para o local real do seu .ovpn.
  • -e OVPN_FILE_PATH — diz ao entrypoint onde encontrar o arquivo de configuração dentro do container.
  • -e OVPN_AUTH_USER e -e OVPN_AUTH_PASS — credenciais de autenticação da VPN, caso o servidor exija usuário e senha além do certificado.
  • -e SSH_TUNNEL_PORT — porta em que o servidor SSH vai escutar dentro do container (e que você mapeia com -p).
  • -e SSH_TUNNEL_USER e -e SSH_TUNNEL_PASS — usuário e senha para autenticar no servidor SSH do container.
  • -p 2222:2222 — publica a porta SSH para que você possa conectar a partir do host.

Se o seu .ovpn usa certificado com passphrase, adicione também -e OVPN_CERT_PASSPHRASE=sua_passphrase.

Rodando Duas VPNs Simultaneamente com Docker Compose

Aqui está onde a proposta do projeto brilha de verdade. Preciso acessar a VPN do trabalho e a VPN de um cliente ao mesmo tempo? Sem problema — cada container é isolado, cada um tem seu próprio tun0 e suas próprias rotas.

services:
  vpn-trabalho:
    image: mauricioj/openvpn-ssh-proxy:latest
    container_name: vpn-trabalho
    restart: unless-stopped
    devices:
      - /dev/net/tun
    cap_add:
      - NET_ADMIN
    volumes:
      - ./configs/trabalho.ovpn:/vpn/config.ovpn:ro
    environment:
      OVPN_FILE_PATH: /vpn/config.ovpn
      OVPN_AUTH_USER: meu_usuario_trabalho
      OVPN_AUTH_PASS: minha_senha_trabalho
      SSH_TUNNEL_PORT: 2222
      SSH_TUNNEL_USER: tunnel
      SSH_TUNNEL_PASS: tunnelpass
    ports:
      - "2222:2222"

  vpn-cliente:
    image: mauricioj/openvpn-ssh-proxy:latest
    container_name: vpn-cliente
    restart: unless-stopped
    devices:
      - /dev/net/tun
    cap_add:
      - NET_ADMIN
    volumes:
      - ./configs/cliente.ovpn:/vpn/config.ovpn:ro
    environment:
      OVPN_FILE_PATH: /vpn/config.ovpn
      OVPN_AUTH_USER: meu_usuario_cliente
      OVPN_AUTH_PASS: minha_senha_cliente
      SSH_TUNNEL_PORT: 2222
      SSH_TUNNEL_USER: tunnel
      SSH_TUNNEL_PASS: tunnelpass
    ports:
      - "2223:2222"

Note que as portas do host são diferentes (2222 e 2223), mas dentro de cada container o SSH escuta em 2222. Isso é justamente a vantagem do isolamento — cada container vive em seu próprio namespace de rede.

Suba tudo com:

docker compose up -d

Referência das Variáveis de Ambiente

VariávelObrigatóriaDescrição
OVPN_FILE_PATHSimCaminho do arquivo .ovpn dentro do container
OVPN_CERT_PASSPHRASENãoPassphrase do certificado cliente, se houver
OVPN_AUTH_USERNãoUsuário para autenticação via auth-user-pass
OVPN_AUTH_PASSNãoSenha para autenticação via auth-user-pass
OVPN_OPTSNãoFlags adicionais passadas diretamente ao OpenVPN (ex: --verb 4)
SSH_TUNNEL_PORTSimPorta em que o servidor SSH vai escutar
SSH_TUNNEL_USERSimUsuário SSH para autenticação no container
SSH_TUNNEL_PASSSimSenha SSH para autenticação no container

Testando os Túneis

Com os containers rodando, há dois casos de uso principais.

Proxy SOCKS5 para navegação e acesso geral

O SSH suporta criar um proxy SOCKS5 com a flag -D. Todo tráfego roteado por esse proxy vai sair pelo IP da VPN:

ssh -D 1080 \
    -p 2222 \
    -o StrictHostKeyChecking=no \
    tunnel@localhost \
    -N

A flag -N diz ao SSH para não executar nenhum comando remoto — só abrir o túnel. Com o proxy SOCKS5 ativo na porta 1080 local, você pode testar com curl:

curl --socks5 localhost:1080 https://ifconfig.me

O IP retornado deve ser o IP de saída da VPN, não o IP real da sua máquina.

Para a segunda VPN, basta mudar a porta de conexão SSH e a porta local do SOCKS:

ssh -D 1081 \
    -p 2223 \
    -o StrictHostKeyChecking=no \
    tunnel@localhost \
    -N

Port Forwarding para acessar um serviço interno

Se preciso acessar um serviço específico dentro da rede VPN — digamos, um servidor de banco de dados em 10.8.0.50:5432 — uso redirecionamento de porta com -L:

ssh -L 5432:10.8.0.50:5432 \
    -p 2222 \
    -o StrictHostKeyChecking=no \
    tunnel@localhost \
    -N

Agora localhost:5432 no meu host aponta direto para o Postgres dentro da rede VPN. Posso conectar qualquer cliente de banco de dados normalmente.

No Windows, o mesmo comando funciona no PowerShell ou no terminal do VS Code com OpenSSH instalado:

ssh -L 5432:10.8.0.50:5432 `
    -p 2222 `
    -o StrictHostKeyChecking=no `
    tunnel@localhost `
    -N

Debugging

Coisas não funcionando? Aqui está o meu processo de diagnóstico.

Ver os logs do container em tempo real:

docker logs -f vpn-trabalho

Procure por linhas com Initialization Sequence Completed — isso confirma que o OpenVPN conectou com sucesso. Erros de autenticação ou de certificado aparecerão aqui claramente.

Entrar no container para inspecionar o estado:

docker exec -it vpn-trabalho sh

De dentro do container, verifico se a interface TUN foi criada e se as rotas estão corretas:

ip addr show tun0
ip route show

Se tun0 não aparece, o OpenVPN não subiu — os logs vão indicar o motivo. Se tun0 existe mas o tráfego não flui, verifique se as rotas da rede interna da VPN foram adicionadas corretamente.

Ativar logs verbosos do OpenVPN sem recriar o container, basta usar a variável OVPN_OPTS:

environment:
  OVPN_OPTS: "--verb 4"

Com --verb 4 você vê o handshake TLS completo e os pacotes de controle, o que ajuda a identificar problemas de certificado ou de compatibilidade de cifras.

Proximo Passo

A implementação está funcionando, mas antes de usar isso em produção ou em um ambiente de trabalho real, há alguns pontos importantes sobre segurança que vale considerar — credenciais em variáveis de ambiente, superfície de ataque do SSH exposto, e como minimizar os riscos.

Cubro tudo isso no terceiro post da série, junto com as lições aprendidas durante o desenvolvimento do projeto.