Tag: openvpn

  • Problema: timeout em sites atrás do proxy reverso via OpenVPN

    Um dia o nginx que configurei como proxy reverso aqui passou a dar timeout ao tentar carregar o Nextcloud. Ao investigar, verifiquei que mesmo ao fazer a solicitação ao servidor Nextcloud a partir do próprio servidor que hospeda o nginx/OpenVPN, através do comando abaixo, a solicitação não completava.

    $ curl -v http://10.8.0.3

    As solicitações feitas dentro da LAN funcionavam, indicando que o problema acontecia apenas dentro da VPN. No entanto, dentro da VPN o ping funcionava, solicitações para outras páginas do Nextcloud também funcionavam, como:

    $ curl -v http://10.8.0.3/status.php

    Tentei aprofundar a investigação mas eu não sou nenhum especialista em redes, só um entusiasta que se mete profissionalmente. Uma explicação que consegui foi que o problema podia ser relacionado ao MTU (maximum transmission unit) da interface virtual de rede do OpenVPN.

    Tanto a interface virtual do OpenVPN (tun0) quanto a interface de rede da máquina (eth0) possuíam o mesmo MTU: 1500 bytes. Isso pode ser verificado com:

    $ ip a

    Diminui o MTU do tun0 para 1380, tanto no servidor quanto no cliente, que foi uma sugestão que encontrei e funcionou. Esqueci o problema até acontecer com outra instalação do Nextcloud que mantenho. Mesmo problema, mesma solução. Eventualmente retornei o MTU pra 1500 e continuou funcionando.

    O blog não existia na época da primeira ocorrência com o Nextcloud. Agora, tive o mesmo problema com o WordPress e o novo Nextcloud. E o problema parece inconsistente. Tudo funciona, até que de repende para. E em momentos diferentes, sem nenhum gatilho claro, mas claramente alguma tentativa de enviar um pacote maior que não poderia ser fragmentado falhou.

    Dessa vez parei pra investigar um pouco melhor. Já que o MTU da tun0 é 1500, um ping com tamanho de 1472 bytes (-20 do cabeçalho IP -8 do cabeçalho ICMP) deve passar sem ser fragmentado. Não passou. 100% dos pacotes perdidos. Fui baixando até encontrar o ponto onde os pings tem resposta: 1396 bytes. MTU de 1424 bytes.

    Testei então: ping sem opções definidas e com tamanhos 1396 e 1397 bytes, monitorando também com o tcpdump tanto no servidor quanto no cliente:

    $ ping 10.8.0.3
    $ ping -M do -s 1396 10.8.0.3
    $ ping -M do -s 1397 10.8.0.3

    Para o tcpdump:

    # tcpdump -i tun0 icmp

    Resultados:

    • “ping 10.8.0.3” e “ping -M do -s 1396 10.8.0.3”:
      • pra cada 10 pings, 0% de perda de pacotes.
      • tcpdump reporta 20 pacotes capturados tanto no servidor quanto no cliente: solicitação e resposta

    Ótimo. Agora:

    • “ping -M do -s 1397 10.8.0.3” e valores acima:
      • pra cada 10 pings, 100% de perda de pacotes
      • tcpdump reporta 10 pacotes capturados no servidor, 20 no cliente: o pacote sai do servidor e chega ao cliente, que responde, mas as respostas do cliente não chegam ao servidor.

    O mesmo foi feito pingando do cliente para o servidor. Mesmo sucesso para pacotes de 1396 bytes e menores. Já com

    • ping -M do -s 1397 10.8.0.1:
      • pra cada 10 pings, 100% de perda de pacotes
      • tcpdump reporta 10 pacotes capturados no cliente, 0 no servidor.

    Ou seja, o problema está no envio de pacotes dos clientes para o servidor através do túnel.

    O ping não alerta que o pacote é grande demais nem dá qualquer erro específico, apenas para pings com tamanho acima de 1472 (que resultariam num pacote acima do MTU). Em vez disso, apenas falha silenciosamente.

    Adicionalmente ao ping, fiz testes com requisições às páginas que resultavam em timeout usando curl e monitorando com tcpdump.

    Solução: baixar o valor do MTU da interface tun0 do servidor e clientes da VPN para 1424 bytes: tamanho total do maior pacote do ping que funcionou (1396 de payload, 20 do cabeçalho IP e 8 do cabeçalho ICMP).

    Isso pode ser feito de forma não persistente com:

    # ip link set dev tun0 mtu 1424

    O MTU vai voltar para 1500 quando o serviço do OpenVPN for reiniciado. Para configurar de forma persistente, inclua essa linha nos arquivos de configuração do OpenVPN do servidor e dos clientes:

    tun-mtu 1424

    Eu ainda não entendi 100% o que causa esse problema e ele não parece afetar todos os clientes igualmente ou ao mesmo tempo. O servidor recebia resposta do ping com payload de 1472 de alguns clientes sem problemas, que exibiam a página e operavam com a MTU padrão de 1500 bytes no tun0.

    Até então, após a mudança para o MTU de 1424, o problema não reincidiu. Sendo mais conservador, eu colocaria um valor mais baixo, pra deixar uma margem pra possíveis alterações que possam vir a acontecer no OpenVPN ou nos outros servidores, masss eu tô aqui pra ver o que acontece.

  • Configuração de servidor OpenVPN

    OpenVPN é um sistema de rede virtual privada (VPN), usado pra criar conexões seguras entre dispositivos, protegendo a troca de informações mesmo em trajetos não confiáveis, como a internet. Implementa tanto aplicação de servidor quanto de cliente e é distribuído sob a licença GPL-2.0.

    O sistema funciona com base em infraestrutura de chave pública (PKI). Pra configurar a autenticação de forma segura é necessário gerar certificados SSL e chaves para uma autoridade certificadora (CA), para o servidor e para os clientes da VPN.

    Felizmente, não precisamos tocar diretamente no OpenSSL, que é bem complexo. Em vez disso, a gente usa o EasyRSA, uma ferramenta que nos ajuda a gerar as chaves e certificados que a gente precisa.

    Considerando uma instalação do Debian ou Ubuntu server:

    # apt install openvpn easy-rsa

    Pra facilitar e deixar a configuração organizada, copie a pasta do EasyRSA para dentro da pasta de configuração do OpenVPN.

    # cp -r /usr/share/easy-rsa /etc/openvpn/

    Vá para a pasta /etc/openvpn/easy-rsa, edite copie o arquivo vars.example como var e descomente as linhas que quiser editar de acordo com a sua preferência. Configurações sugeridas:

    set_var EASYRSA_KEY_SIZE        4096
    set_var EASYRSA_CERT_EXPIRE     731
    set_var EASYRSA_CRL_DAYS        366
    set_var  EASYRSA_FIX_OFFSET 30

    Dessa forma, os certificados serão válidos por 2 anos, a lista de revogação de certificados (CRL) por 1 ano, e todos os certificados vão ser emitidos com validade fixa a partir de 30 de Janeiro.

    Após configurado, podemos iniciar nossa PKI e gerar o CA:

    # bash easyrsa init-pki
    # bash easyrsa build-ca

    Ao gerar a chave do CA, uma senha será pedida pra criptografar a chave do CA.

    Deve-se também gerar a chave de Diffie-Hellman, que é usada para gerar a chave simétrica da conexão de VPN a ser estabelecida.

    # bash easyrsa gen-dh

    Opcionalmente, para maior segurança, pode-se gerar uma chave de autenticação para que o servidor exija uma assinatura válida nos pacotes recebidos dos clientes.

    # openvpn --genkey secret pki/tls-auth.key

    Ao gerar o par de chaves do servidor, é uma boa ideia gerar a chave sem senha pra que o serviço possa ser iniciado junto com o sistema:

    # bash easyrsa build-server-full nome_do_servidor nopass

    A senha da chave do CA ainda será pedida para assinar o certificado do servidor.

    Assim como para o servidor, você pode escolher deixar as chaves dos clientes com senha ou não passando o parâmetro opcional nopass.

    # bash easyrsa build-client-full nome_do_cliente0 nopass

    Agora, a configuração do servidor e dos clientes vai depender do propósito da VPN: pode ser um gateway, pode ser apenas para conectar um cliente ao outro, pode ser pra conectar clientes à uma LAN.

    Crie um arquivo com extensão .conf na pasta /etc/openvpn server, como por exemplo config_do_servidor.conf com esse conteúdo de exemplo – adapte-o para a sua necessidade:

    # Configurações da conexão
    # Descomente a linha abaixo e coloque o IP do seu servidor caso ele esteja dentro de uma LAN
    ;local 192.168.0.10
    port 1194
    proto udp4
    dev tun0
    keepalive 10 120
    persist-key
    persist-tun
    
    # As linhas abaixo determinam a rede privada que seu servidor vai criar
    server 10.8.0.0 255.255.255.0
    topology subnet
    push "route 10.8.0.0 255.255.255.0"
    
    # Descomente a linha abaixo e substitua a LAN pela sua caso queira que os clientes tenham uma rota para a LAN do servidor
    ;push "route 192.168.0.0 255.255.255.0"
    
    # Descomente a linha abaixo caso queira que o cliente use o servidor de VPN como gateway
    ;push "redirect-gateway def1 bypass-dhcp"
    
    # Configura resolvedores de DNS alternativos
    ;push "dhcp-option DNS 208.67.222.222"
    ;push "dhcp-option DNS 208.67.220.220"
    
    # Permite que clientes da VPN possam alcançar outros
    ;client-to-client
    
    # Limita o número de clientes na VPN
    ;max-clients 5
    
    # Configurações do processo
    verb 3
    explicit-exit-notify 1
    status /var/log/openvpn/status-conext00.log
    # Reduz o privilégio de execução após inicialização para o usuário e grupo abaixo - em distribuições da família Fedora e Red Hat, existe um usuário openvpn próprio. O grupo nogroup não existe.
    user nobody
    group nogroup
    # Mantém clientes com o mesmo IP na VPN
    ;ifconfig-pool-persist /var/log/openvpn/ipp.txt
    
    # Certificados e chaves necessários gerados através do easy-rsa
    ca ../easy-rsa/pki/ca.crt
    cert ../easy-rsa/pki/issued/nome_do_servidor.crt
    key ../easy-rsa/pki/private/nome_do_servidor.key
    dh ../easy-rsa/pki/dh.pem
    
    # Lista opcional de revogação de certificados
    ;crl-verify ../easy-rsa/pki/crl.pem
    
    # Chave TLS opcional para maior segurança, exigindo que pacotes recebidos tenham uma assinatura válida
    ;tls-auth ../easy-rsa/pki/tls-auth.key 0
    

    Observe que as linhas do tipo

    push "algum parâmetro"

    são configurações que também podem ser especificadas no arquivo de configuração do cliente em vez do servidor.

    Precisa-se também configurar as permissões de firewall e habilitar no kernel o encaminhamento em IPv4.

    Para habilitar o encaminhamento no kernel, adicione um arquivo /etc/sysctl.d/98-ip-fwd.conf com a seguinte linha:

    net.ipv4.ip_forward=1

    Reinicie para que tenha efeito ou

    # sysctl -w net.ipv4.ip_forward=1

    No firewall, para permitir conexão de clientes:

    # ufw allow 1194/udp

    Caso use o servidor de VPN como um gateway ou estabeleça rota do cliente para a LAN em que o servidor está:

    # ufw route allow in on tun0 out on eth0
    # ufw route allow in on eth0 out on tun0

    E configure o protocolo NAT adicionando este trecho no topo do arquivo /etc/ufw/before.rules

    ### START OF MANUAL NAT CONFIG
    # NAT table rules
    *nat
    :POSTROUTING ACCEPT [0:0]
    
    # Forward traffic through eth0 - Change to match you out-interface
    -A POSTROUTING -s 10.8.0.0/24 -o tun0 -j MASQUERADE
    
    # don't delete the 'COMMIT' line or these nat table rules won't
    # be processed
    COMMIT
    ### END OF MANUAL NAT CONFIG

    Lembre-se de substituir a interface física e a rede pelas do seu servidor.

    Alternativamente ao NAT, no caso da existência de uma LAN, você pode configurar no seu roteador uma rota para 10.8.0.0/24 pelo IP privado do seu servidor.

    Agora basta habilitar e iniciar o serviço:

    # systemctl enable --now openvpn-server@config_do_servidor.service

    É isso, o servidor deve estar funcionando.

    Agora precisamos que clientes se conectem. Pra isso, eles precisam de um arquivo de configuração do mesmo tipo que foi feito para o servidor. O easy-rsa não gera esses arquivos, então eu fiz um pequeno script pra resolver esse problema, que deixo em /etc/openvpn/easy-rsa/build-client-config.sh

    Conteúdo do script:

    #!/bin/bash
    
    system=$1
    client=$2
    clients_dir=./clients
    client_template_win=./clients/client.ovpn
    client_template_lnx=./clients/client.conf
    
    
    init() {
            mkdir $clients_dir
    cat <<'EOF' > $client_template_win
    client
    dev tun
    proto udp4
    remote servidor.exemplo.com 1194
    resolv-retry infinite
    nobind
    persist-key
    persist-tun
    remote-cert-tls server
    ;key-direction 1
    verb 3
    EOF
    
    cat <<'EOF' > $client_template_lnx
    client
    dev tun
    proto udp4
    remote servidor.exemplo.com 1194
    resolv-retry infinite
    nobind
    persist-key
    persist-tun
    remote-cert-tls server
    ;key-direction 1
    verb 3
    user nobody
    group nogroup
    EOF
    
            if ! [[ -f ./pki/ca.crt ]]; then
                    echo "CA não encontrado. Encerrando."
                    exit 1
            else
                    echo "" | tee -a $client_template_lnx $client_template_win
                    echo "<ca>" | tee -a $client_template_lnx $client_template_win
                    cat pki/ca.crt | tee -a $client_template_lnx $client_template_win
                    echo "</ca>" | tee -a $client_template_lnx $client_template_win
            fi
    
            if [[ -f ./pki/tls-auth.key ]]; then
                    sed -i 's/;//g' $client_template_lnx $client_template_win
                    echo "" | tee -a $client_template_lnx $client_template_win
                    echo "<tls-auth>" | tee -a $client_template_lnx $client_template_win
                    cat pki/tls-auth.key | tee -a $client_template_lnx $client_template_win
                    echo  "</tls-auth>" | tee -a $client_template_lnx $client_template_win
            else
                    echo "tls-auth.key não encontrada, prosseguindo sem"
            fi
    }
    
    if ! [[ -d $clients_dir ]]; then
            init
            exit
    fi
    
    if ! [[ -f ./pki/issued/$client.crt ]]; then
            echo "Cliente não existe"
            exit 1
    fi
    
    if [[ $system == "-l" || $system == "--linux" ]]; then
            fileExt=conf
    elif [[ $system == "-o" || $system == "--outro" ]]; then
            fileExt=ovpn
    else
            echo "Sistema não especificado"
    fi
    
    targetFile=clients/$client.$fileExt
    
    cp clients/client.$fileExt $targetFile
    
    echo "" | tee -a $targetFile
    echo "<cert>" | tee -a $targetFile
    cat pki/issued/$client.crt | tee -a $targetFile
    echo "</cert>" | tee -a $targetFile
    
    echo "" | tee -a $targetFile
    echo "<key>" | tee -a $targetFile
    cat pki/private/$client.key | tee -a $targetFile
    echo "</key>" | tee -a $targetFile

    Pra inicializar, basta executar o script sem nenhum argumento

    # bash build-client-config.sh

    Ele vai criar uma pasta clients, com um template para Linux, client.conf, e outro para Windows, client.ovpn. Edite-os conforme necessário.

    Feito isso, é possível criar o arquivo de configuração para um cliente existente com

    # bash build-client-config.sh --linux nome_do_cliente0

    ou para Windows, macOS, iOS ou Android

    # bash build-client-config.sh --outro nome_do_cliente0