Problema
Muchos entornos híbridos usan Azure Point‑to‑Site (P2S) VPN para dar acceso a máquinas on‑premise o a laptops. Cuando la puerta de enlace está configurada para autenticarse con Microsoft Entra ID (antes Azure AD), la experiencia en Windows es fluida, pero en Linux aparecen dos obstáculos recurrentes:
- El cliente oficial Azure VPN Client para Linux quedó en preview, solo soporta Ubuntu 20.04/22.04 y fue retirado sin recibir parches.
- Los clientes genéricos (OpenVPN, strongSwan) esperan autenticación basada en certificados o RADIUS; no saben manejar un token JWT que Entra ID entrega mediante el flujo device‑code.
El síntoma típico es que la conexión falla inmediatamente después de la fase de autenticación, con mensajes como “username/password authentication failed” o “TLS channel buffer overflow”. El problema no es exclusivo de una distribución; ocurre siempre que el token supera los buffers internos de OpenVPN.
Causa
1. Retiro del cliente oficial
Microsoft dejó de publicar el paquete en su repositorio Linux, por lo que los usuarios quedan sin una herramienta que ya implementa el flujo de Entra ID.
2. Tamaño del token
Un JWT de Entra ID suele ocupar entre 1 KB y 2 KB. OpenVPN reserva USER_PASS_LEN = 128 bytes para el campo de usuario/contraseña y TLS_CHANNEL_BUF_SIZE = 2048 bytes para el mensaje TLS que lleva esas credenciales. Cuando el token se inserta como “username”, el buffer se desborda y la conexión se corta.
3. Falta de integración nativa
OpenVPN no incluye un módulo para ejecutar el flujo device‑code ni para refrescar automáticamente el token. Sin una capa que obtenga y entregue el token al proceso de autenticación, la VPN nunca llega a la fase de establecimiento del túnel.
4. Expectativas de TLS del cliente oficial
Algunas implementaciones de Azure VPN añaden cabeceras específicas (OCC string, peer‑info). En la práctica, la puerta de enlace pública de Azure acepta una conexión OpenVPN estándar siempre que el token llegue completo, por lo que esos ajustes son opcionales.
Solución
La estrategia consiste en usar OpenVPN con dos parches de buffer y un wrapper que gestione el flujo device‑code. El flujo completo es reutilizable en cualquier Linux que pueda compilar OpenVPN.
Paso 1 – Preparar OpenVPN con buffers ampliados
git clone https://github.com/OpenVPN/openvpn.git
cd openvpn
sed -i 's/#define USER_PASS_LEN 128/#define USER_PASS_LEN 4096/' src/openvpn.h
sed -i 's/#define TLS_CHANNEL_BUF_SIZE 2048/#define TLS_CHANNEL_BUF_SIZE 8192/' src/openvpn.h
./configure && make -j$(nproc) && sudo make install
Los cambios aumentan la zona donde OpenVPN almacena el nombre de usuario y el mensaje TLS, evitando el overflow del token.
Paso 2 – Obtener el token Entra ID
Se puede usar la CLI de Azure (az login --use-device-code) o una pequeña rutina Python basada en msal. El script siguiente guarda el token en un archivo temporal:
#!/usr/bin/env bash
# get_entratoken.sh – devuelve un archivo con el JWT listo para OpenVPN
TMPFILE=$(mktemp)
az login --use-device-code >/dev/null 2>&1
az account get-access-token --resource https://vpn.azure.com --query accessToken -o tsv >"$TMPFILE"
echo "$TMPFILE"
El archivo contiene únicamente el token; OpenVPN lo leerá como “username”. No se escribe la contraseña porque no se necesita.
Paso 3 – Convertir el perfil XML de Azure a .ovpn
Azure exporta la configuración como XML. Un script de Python (o la herramienta xmlstarlet) extrae los parámetros relevantes (remote, cipher, auth, etc.) y genera un archivo .ovpn sin la sección auth-user-pass. Guardar el resultado como azurevpn.ovpn.
Paso 4 – Lanzar OpenVPN con el token
TOKEN_FILE=$(./get_entratoken.sh)
sudo openvpn --config azurevpn.ovpn --auth-user-pass "$TOKEN_FILE" --daemon
OpenVPN leerá el token, establecerá el túnel y, al ejecutarse como daemon, liberará la terminal.
Paso 5 – Aplicar rutas y DNS split
Azure suele enviar rutas y servidores DNS mediante push "route …" y push "dhcp-option DNS …". Después de que el túnel esté activo, ejecuta:
# rutas push
while read -r line; do
sudo ip route add $line
done < <(grep '^push "route' azurevpn.ovpn | cut -d'"' -f2)
# DNS split con systemd-resolved
while read -r dns; do
sudo resolvectl dns tun0 $dns
done < <(grep '^push "dhcp-option DNS' azurevpn.ovpn | cut -d'"' -f2)
tun0 es la interfaz creada por OpenVPN; systemd‑resolved se encarga de la resolución solo para esa interfaz.
Paso 6 – Desconexión y limpieza
sudo killall openvpn
sudo rm -f "$TOKEN_FILE"
sudo ip link delete tun0
Eliminar el archivo temporal evita que el JWT quede expuesto en el disco.
Alternativas
- strongSwan: solo viable si la puerta de enlace permite IKEv2 con certificado o RADIUS. No soporta Entra ID directamente.
- Azure VPN Client (snap): funciona en Ubuntu 20.04/22.04, pero está obsoleto y usa el ID de aplicación antiguo, lo que genera rechazos.
Cuándo aplicar esta solución
- Entorno Linux donde el cliente oficial no está disponible o está descontinuado.
- Puerta de enlace Azure P2S configurada con Entra ID y sin certificados alternativos.
- Token JWT que supera los buffers predeterminados de OpenVPN (casi siempre).
- Necesidad de split‑DNS gestionado por systemd‑resolved o similar.
No es necesario cuando la VPN usa exclusivamente certificados, RADIUS o cuando la distribución ya incluye una versión de OpenVPN con buffers ampliados (poco frecuente).
Código
#!/usr/bin/env bash
# openp2s.sh – wrapper completo para Azure P2S + Entra ID
set -euo pipefail
# 1. Compilar OpenVPN con buffers ampliados (ejecutar una sola vez)
# Omitir si ya está instalado con los parches.
# 2. Obtener token
TOKEN_FILE=$(mktemp)
az login --use-device-code >/dev/null 2>&1
az account get-access-token --resource https://vpn.azure.com --query accessToken -o tsv >"$TOKEN_FILE"
# 3. Convertir XML a OVPN (asume script xml2ovpn.py)
./xml2ovpn.py azurevpnconfig.xml >azurevpn.ovpn
# 4. Lanzar OpenVPN en background
sudo openvpn --config azurevpn.ovpn --auth-user-pass "$TOKEN_FILE" --daemon
# 5. Esperar a que tun0 aparezca
while ! ip link show tun0 >/dev/null 2>&1; do sleep 1; done
# 6. Aplicar rutas push
grep '^push "route' azurevpn.ovpn | cut -d'"' -f2 | while read -r r; do
sudo ip route add $r
done
# 7. Configurar DNS split
grep '^push "dhcp-option DNS' azurevpn.ovpn | cut -d'"' -f2 | while read -r d; do
sudo resolvectl dns tun0 $d
done
echo "Conexión establecida. Para desconectar: sudo killall openvpn && sudo rm -f $TOKEN_FILE"
Verificación
- Estado del túnel
sudo systemctl status openvpn@azurevpn sudo ip -br addr show tun0