Introduction
The title might be a tad dramatic, but since joining a new company, I've been grappling with their corporate OpenVPN. Most communication tools like Slack and Zoom struggle to function properly when I'm connected to the VPN. I've been searching for a way to bypass the VPN for specific apps or, ideally, to use the VPN exclusively for accessing internal dashboards and SSH-ing into servers. My search for solutions in the OpenVPN realm led me to split tunneling, but that required waiting on IT. Determined to find a way on my own, I embarked on a journey.
The Way Out
In my desperation, I even considered running a Linux VM with a browser on my Mac to completely isolate the VPN connection, but that seemed too resource-intensive. This line of thinking, however, reminded me of my days running VPS, where I used SSH SOCKS proxy along with FoxyProxy to tunnel specific websites through the proxy to bypass censorship. I wondered: What if I could run the OpenVPN client in a lightweight Linux VM and then expose the SOCKS proxy to my host machine? This approach would allow me to use the VPN for internal resources while bypassing it for everything else.
Attempt 1: Orb Machines
My first attempt utilized Orb, a nifty tool for running lightweight VMs and Docker containers on macOS. I spun up a Debian VM and installed OpenVPN on it. Getting the OpenVPN client to work with my .ovpn profile was tricky due to 2FA, specifically how the two auth factors were passed in. After digging into Pritunl's source code, I discovered that they simply concat and send it as a single password: {pin}{totp}
.
With the VPN working in the VM, I needed to run a SOCKS proxy on the VM and expose it to my host machine. I opted for microsocks, running it with microsocks -i 0.0.0.0 -p 1080
. Testing from the host machine was straightforward, thanks to Orb's convenient DNS name for the VM. A few curl commands confirmed that the proxy was working as expected:
❯ curl -s --socks5 debian.orb.local:1080 'https://ipapi.co/json/' | jq .org"AMAZON-AES"~❯ curl -s 'https://ipapi.co/json/' | jq .org"Reliance Jio Infocomm Limited"
Attempt 2: Gluetun + Docker
While Orb Machines served as a quick proof of concept, I wanted a more permanent solution. Initially, I considered writing a small Go service to orchestrate OpenVPN and microsocks, but then I discovered Gluetun. This tool allows running various VPN clients in Docker containers and is primarily used for giving other containers VPN access. Crucially, it also had built-in support for exposing an HTTP proxy.
I quickly put together a docker-compose.yml
file and launched it with docker-compose up -d
:
version: "3"services:gluetun:image: qmcgaw/gluetuncap_add:- NET_ADMINdevices:- /dev/net/tun:/dev/net/tunvolumes:- ./corp.ovpn:/gluetun/custom.conf:roenvironment:- VPN_SERVICE_PROVIDER=custom- VPN_TYPE=openvpn- OPENVPN_CUSTOM_CONFIG=/gluetun/custom.conf- OPENVPN_USER=<username>- OPENVPN_PASSWORD=<pin><totp>- HTTPPROXY=on- HTTPPROXY_LOG=on- HTTPPROXY_PORT=3128ports:- 3128:3128
Note: I'm using Orb for running the container, hence the DNS name
gluetun.gluetun.orb.local
.
❯ curl -s -x http://gluetun.gluetun.orb.local:3128 'https://ipapi.co/json/' | jq .org"AMAZON-AES"
To easily toggle the proxy for specific domains inside Chrome, I found Proxy SwitchyOmega. This extension allows for automatically switching between proxies and direct connections based on domain wildcards. I configured it to use the HTTP proxy on gluetun.gluetun.orb.local:3128
, and it worked flawlessly.
For SSH, I used the command:
ssh -o 'ProxyCommand nc -X connect -x gluetun.gluetun.orb.local:3128 %h %p' user@host
This can be further refined by adding the ProxyCommand
to your ~/.ssh/config
file.
Possible Refinement
The current approach works well, but I have to manually update the docker-compose.yml
file with the TOTP pin from my phone. I'm considering writing a small script to handle the TOTP and update the docker-compose.yml
file automatically. Unfortunately, I made the dumb mistake of not storing the TOTP in 1Password, which would have allowed me to use the CLI tool to retrieve it. I might need to ask IT to reset my 2FA, or perhaps explore rooting my phone to extract the TOTP secret from the Authenticator app, stay tuned.
Conclusion
I'm quite satisfied with this setup. My browser now automatically uses the proxy for internal domains and a direct connection for everything else. I can use Slack, Zoom, and other tools without any issues.
Lessons learned: It's 2024, please use Tailscale for your corporate VPN needs. 😄