I have a workstation behind a VPN at work that I like to remotely access for queuing jobs or data analysis over hosted jupyter notebooks.
Usually I just connect to using the Cisco Anyconnect client but it's caused some headaches.
I want to be able to route to these workstations using the VPN but since they throttle bandwidth use my local gateway for everything else (i.e. looking up docs, streaming spotify etc)
$ brew install openconnect
openconnect
uses a straightforward syntax for connecting. Our VPN is configured to use user/pass for login with an authgroup.
I tested it can connect with the following command
$ openconnect --user=<USERNAME> --authgroup=<GROUP> <VPNHOST> [modifiers]
Through some googling I found some cool examples of using bash functions to streamline most of this and manage staring and killing the process.
Change sudoer's so that openconnect
and kill
to not require sudo for starting and stopping the process
function vpnsetup() {
sudo sh -c 'echo "%admin ALL=(ALL) NOPASSWD: /usr/local/bin/openconnect, /bin/kill" > /etc/sudoers.d/openconnect'
}
Connect using openconnect and read in your vpn password from a local file and passing it to openconnect via stdin.
function vpnstart() {
cat ~/.vpn_pass | sudo openconnect \
--background \
--pid-file="$HOME/.openconnect.pid" \
--user=$VPNUSER \
--authgroup=$AUTHGROUP $VPNHOST \
--passwd-on-stdin
}
Storing the process pid to a file, and then killing the pid loaded from that file when you want to disconnect
function vpnstop() {
if [[ -f "$HOME/.openconnect.pid" ]]; then
sudo kill -2 $(cat "$HOME/.openconnect.pid") && rm -f "$HOME/.openconnect.pid"
else
echo "openconnect pid file does not exist, probably not running"
fi
}
Storing passwords in plaintext for loading into scripts makes me nervous so lets make an encrypted password file and load the decrypted version of that.
There's a lot of ways to do this but I chose to use gpg and this is what I did specifically.
$ touch ~/.vpn_pass
$ nano ~/.vpn_pass #Store your plaintext password
$ gpg --encrypt --armor -o ~/.vpn_pass.gpg
$ rm ~/.vpn_pass
I modified the helper functions to decrypt encrypted password files so the password can now be loaded into stdin inline using gpg
$ gpg --decrypt -a -o- ~/.vpn_pass.gpg
Create a new vpnc script that will be executed on when connecting
on MacOS these files are usually located in /usr/local/etc
$ touch /usr/local/etc/vpnc-script-split
$ sudo chmod 755 /usr/local/etc/vpnc-script-split
Modify to only route traffic to specific hosts over the VPN
# Add one IP to the list of split tunnel
add_ip ()
{
export CISCO_SPLIT_INC_${CISCO_SPLIT_INC}_ADDR=$1
export CISCO_SPLIT_INC_${CISCO_SPLIT_INC}_MASK=255.255.255.255
export CISCO_SPLIT_INC_${CISCO_SPLIT_INC}_MASKLEN=32
export CISCO_SPLIT_INC=$(($CISCO_SPLIT_INC + 1))
}
# Initialize empty split tunnel list
export CISCO_SPLIT_INC=0
# Delete DNS info provided by VPN server to use internet DNS
# Comment following line to use DNS beyond VPN tunnel
unset INTERNAL_IP4_DNS
# List of workstations beyond VPN tunnel
add_ip xxx.xxx.xxx.xxx # Workstation 1
add_ip yyy.yyy.yyy.yyy # Other workstation
add_ip 10.1.0.5 # proxy.mycom.com
# Execute default script
. /etc/vpnc/vpnc-script
# End of script
Finally, pass the --script=</path/to/vpnc-script-split>
flag in the openconnect
command to restrict
vpn traffic to just the ip addresses we denoted in vpnc-script-split
After connecting to the vpn...
$ source bash_funcs.sh
$ vpnsetup
$ vpnstart
... we can use traceroute
or route
to confirm split tunneling is working correctly
$ route -n get 140.226.100.xxx
route to: 140.226.100.xxx
destination: 140.226.100.xxx
gateway: 140.226.4.57
interface: utun0
flags: <UP,GATEWAY,HOST,DONE,WASCLONED,IFSCOPE,IFREF>
Notice it uses the remote gateway and the vpn interface utun0
But if we want to hit youtube and stream some phat 4k music videos...
$ route -n get www.youtube.com
route to: 172.217.3.14
destination: 172.217.3.14
gateway: 192.168.1.1
interface: en5
flags: <UP,GATEWAY,HOST,DONE,WASCLONED,IFSCOPE,IFREF>
recvpipe sendpipe ssthresh rtt,msec rttvar hopcount mtu expire
0 0 0 0 0 0 1500 0
It connects directly over our hardware interface en5
and our local router gateway.
Final form of bash functions attached in separate file.