108

How can I maintain local LAN access while connected to Cisco VPN?

When connecting using Cisco VPN, the server has to ability to instruct the client to prevent local LAN access.

Assuming this server-side option cannot be turned off, how can allow local LAN access while connected with a Cisco VPN client?


I used to think it was simply a matter of routes being added that capture LAN traffic with a higher metric, for example:

  Network 
Destination      Netmask        Gateway       Interface  Metric
   10.0.0.0  255.255.0.0       10.0.0.3        10.0.0.3      20  <--Local LAN
   10.0.0.0  255.255.0.0  192.168.199.1  192.168.199.12       1  <--VPN Link

And trying to delete the 10.0.x.x -> 192.168.199.12 route don't have any effect:

>route delete 10.0.0.0
>route delete 10.0.0.0 mask 255.255.0.0
>route delete 10.0.0.0 mask 255.255.0.0 192.168.199.1
>route delete 10.0.0.0 mask 255.255.0.0 192.168.199.1 if 192.168.199.12
>route delete 10.0.0.0 mask 255.255.0.0 192.168.199.1 if 0x3

And while it still might simply be a routing issue, attempts to add or delete routes fail.

At what level is Cisco VPN client driver doing what in the networking stack that takes overrides a local administrator's ability to administer their machine?

The Cisco VPN client cannot be employing magic. It's still software running on my computer. What mechanism is it using to interfere with my machine's network? What happens when an IP/ICMP packet arrives on the network? Where in the networking stack is the packet getting eaten?

See also


Edit: Things I've not yet tried:

>route delete 10.0.*

Update: Since Cisco has abandoned their old client, in favor of AnyConnect (HTTP SSL based VPN), this question, unsolved, can be left as a relic of history.

Going forward, we can try to solve the same problem with their new client.

5

11 Answers 11

64

The problem with Anyconnect is that it first modifies the routing table, then babysits it and fixes it up should you modify it manually. I found a workaround for this. Works with version 3.1.00495, 3.1.05152, 3.1.05170, and probably anything else in the 3.1 family. May work with other versions, at least similar idea should work assuming the code does not get rewritten. Fortunately for us Cisco has put the babysitter "baby is awake" call into a shared library. So the idea is that we prevent action by vpnagentd via LD_PRELOAD.

  1. First we create a file hack.c:

    #include <sys/socket.h>
    #include <linux/netlink.h>
    
    int _ZN27CInterfaceRouteMonitorLinux20routeCallbackHandlerEv()
    {
      int fd=50;          // max fd to try
      char buf[8192];
      struct sockaddr_nl sa;
      socklen_t len = sizeof(sa);
    
      while (fd) {
         if (!getsockname(fd, (struct sockaddr *)&sa, &len)) {
            if (sa.nl_family == AF_NETLINK) {
               ssize_t n = recv(fd, buf, sizeof(buf), MSG_DONTWAIT);
            }
         }
         fd--;
      }
      return 0;
    }
    

Note: This code works only with Linux. For applying this solution to a macOS machine, see the macOS adapted version.

  1. Then compile it like this:

    gcc -o libhack.so -shared -fPIC hack.c
    
  2. Install libhack.so into the Cisco library path:

    sudo cp libhack.so  /opt/cisco/anyconnect/lib/
    
  3. Bring down the agent:

    /etc/init.d/vpnagentd stop
    
  4. Make sure it really is down

    ps auxw | grep vpnagentd
    

    If not, kill -9 just to be sure.

  5. If you have /etc/init.d/vpnagentd, then fix it up by adding LD_PRELOAD=/opt/cisco/anyconnect/lib/libhack.so where the underlying executable is being invoked so it looks like this:

    LD_PRELOAD=/opt/cisco/anyconnect/lib/libhack.so /opt/cisco/anyconnect/bin/vpnagentd
    

    More modern AnyConnect installations eschew /etc/init.d/vpnagentd in favor of /lib/systemd/system/vpnagentd.service, in which case you'll want something like:

    sudo mv /opt/cisco/anyconnect/bin/vpnagentd /opt/cisco/anyconnect/bin/vpnagentd.orig &&
    { echo '#!/bin/bash' &&
      echo "LD_PRELOAD=$HOME/vpn/libhack.so exec /opt/cisco/anyconnect/bin/vpnagentd.orig"
    } | sudo tee /opt/cisco/anyconnect/bin/vpnagentd &&
    sudo chmod +x /opt/cisco/anyconnect/bin/vpnagentd
    
  6. Now start the agent:

    /etc/init.d/vpnagentd start
    
  7. Fix up iptables, because AnyConnect messes with them:

    iptables-save | grep -v DROP | iptables-restore
    

    You may want to do something more advanced here to allow access only to certain LAN hosts.

  8. Now fix up the routes as you please, for example:

    route add -net 192.168.1.0 netmask 255.255.255.0 dev wlan0
    
  9. Check to see if they are really there:

    route -n

A previous, simpler version of this hack gave a function that only did "return 0;" - that poster noted that "The only side effect that I've observed so far is that vpnagentd is using 100% of CPU as reported by top, but overall CPU is only 3% user and 20% system, and the system is perfectly responsive. I straced it, it seems to be doing two selects in a loop when idle returning from both quickly, but it never reads or writes - I suppose the call that I cut out with LD_PRELOAD was supposed to read. There might be a cleaner way to do it, but it is good enough for me so far. If somebody has a better solution, please share."

The problem with the trivial hack is it caused a single cpu core to be 100% all the time, effectively reducing your hardware cpu thread count by one - whether your vpn connection was active or not. I noticed that the selects the code was doing were on a netlink socket, which sends vpnagentd data when the routing table changes. vpnagentd keeps noticing there's a new message on the netlink socket and calls the routeCallBackHandler to deal with it, but since the trivial hack doesn't clear the new message it just keeps getting called again and again. the new code provided above flushes the netlink data so the endless loop which caused the 100% cpu doesn't happen.

If something does not work, do gdb -p $(pidof vpnagentd), once attached:

b socket
c
bt

and see which call you are in. Then just guess which one you want to cut out, add it to hack.c and recompile.

19
  • 6
    This is genius. I'm trying to get it to work on OSX and have one question: how did you know that the method to override was named _ZN27CInterfaceRouteMonitorLinux20routeCallbackHandlerEv?
    – donturner
    Feb 6, 2014 at 15:40
  • 3
    @donturner Try nm /opt/cisco/anyconnect/lib/libvpnagentutilities.dylib | grep routeCallbackHandlerEv and then you'll find __ZN25CInterfaceRouteMonitorMac20routeCallbackHandlerEv
    – mckelvin
    Sep 5, 2015 at 6:48
  • 1
    "nm /opt/cisco/anyconnect/lib/libvpnagentutilities.so" gives me no symbols too but nm -D gives me 1715 of them. Feb 26, 2017 at 7:15
  • 1
    This is still working beautifully on 4.1.06020 Jul 24, 2017 at 19:21
  • 1
    Sure, @yeralin, something like sudo mv /opt/cisco/anyconnect/bin/vpnagentd /opt/cisco/anyconnect/bin/vpnagentd.orig && { echo '#!/bin/bash' && echo "LD_PRELOAD=$HOME/vpn/libhack.so exec /opt/cisco/anyconnect/bin/vpnagentd.orig"; } | sudo tee /opt/cisco/anyconnect/bin/vpnagentd. Warning: not idempotent, coding blind in a tiny comment box with no preview, ugh. Maybe I should edit it into the answer? Sep 20, 2020 at 6:33
14

This is VERY convoluted, but if you create a minimal VM using VMWare Player or similar, and run the Cisco AnyConnect VPN client in that, it might be possible to set up routing as you want using the VMWare virtual network adapters, or simply use the VM for access to whatever resources are required via the Cisco SSL VPN and "drag/drop" files to/from your actual machine.

11

For those looking to maintain control of their routing table when using a Cisco AnyConnect SSL VPN, check out OpenConnect. It both supports the Cisco AnyConnect SSL VPN and doesn't attempt to disrupt or 'secure' routing table entries. @Vadzim alludes to this in a comment above.

After trying everything but patching the AnyConnect Secure Mobility Client, I was able to successfully replace it on Windows with OpenConnect GUI. This enabled me to maintain connectivity to local resources (and update the routing table).

I use OpenConnect on Windows but it also supports Linux, BSD, and macOS (among other platforms) according to the project page.

4
8

Shrew Soft VPN software did the trick for me, also, as Ian Boyd suggested.

It can import Cisco VPN client profiles. I have used Cisco VPN Client version 5.0.05.0290, and after installing the Shrew VPN (version 2.1.7) and importing Cisco profile, I was able to access local LAN while connected to corporate VPN without any additional configuration of Shrew VPN connection (or software).

4
  • It would be amazing if this was available for android. Jan 22, 2014 at 15:46
  • 4
    I'm from 2019 and it works!!!!!! Apr 17, 2019 at 20:07
  • @povisenko On Windows 10?
    – Triynko
    Feb 22, 2020 at 21:11
  • @Triynko yes, server Feb 22, 2020 at 23:35
7

Thanks to Sasha Pachev for the nice hack above.

vpnagentd also messes with the resolver by overwriting the changes made to /etc/resolv.conf. I solved it by eventually winning the race against it:

#!/bin/bash

dnsfix() {
    [ -f /etc/resolv.conf.vpnbackup ] || echo "Not connected?" >&2 || return 0 # do nothing in case of failure
    while ! diff -q /etc/resolv.conf /etc/resolv.conf.vpnbackup #>/dev/null
    do
         cat /etc/resolv.conf.vpnbackup >/etc/resolv.conf
    done
    chattr +i /etc/resolv.conf
    diff -q /etc/resolv.conf /etc/resolv.conf.vpnbackup >/dev/null 
}

while ! dnsfix
do
    echo "Retrying..."
    chattr -i /etc/resolv.conf
done

Don't forget to chattr -i /etc/resolv.conf when disconnecting.

I'm trying to solve it by intercepting the callback, like for the routes method above, but can't yet find the corresponding callback or method.

Update1/2: A strace revealed that vpnagentdis using the inotify API to monitor the resolver file changes. From there onwards it was downhill. Here's the additional hack:

int _ZN18CFileSystemWatcher11AddNewWatchESsj(void *string, unsigned int integer)
{
  return 0;
}

That's a little bit overkill, granted, as it disables all file watching for the agent. But seems to work OK.

The vpn client wrapper script below integrates all the functionality(updated to include this additional hack). chattr is no longer used/needed.

Update 3: Fixed username/password settings in the script. It now uses a vpn.conf file with the format described below(and root-only permissions).

#!/bin/bash

# Change this as needed
CONF="/etc/vpnc/vpn.conf"
# vpn.conf format
#gateway <IP>
#username <username>
#password <password>
#delete_routes <"route spec"...> eg. "default gw 0.0.0.0 dev cscotun0"
#add_routes <"route spec"...> eg. "-net 192.168.10.0 netmask 255.255.255.0 dev cscotun0" "-host 10.10.10.1 dev cscotun0"

ANYCONNECT="/opt/cisco/anyconnect"

usage() {
    echo "Usage: $0 {connect|disconnect|state|stats|hack}"
    exit 1
}

CMD="$1"
[ -z "$CMD" ] && usage

ID=`id -u`

VPNC="$ANYCONNECT/bin/vpn"

dnsfix() {
    [ -f /etc/resolv.conf.vpnbackup ] || echo "Not connected?" >&2 || return 0 # do nothing in case of failure
    while ! diff -q /etc/resolv.conf /etc/resolv.conf.vpnbackup >/dev/null
    do
         cat /etc/resolv.conf.vpnbackup >/etc/resolv.conf
    done
#    chattr +i /etc/resolv.conf
    diff -q /etc/resolv.conf /etc/resolv.conf.vpnbackup >/dev/null 
}

case "$CMD" in
    "connect")
        [ $ID -ne 0 ] && echo "Needs root." && exit 1
        HOST=`grep ^gateway $CONF | awk '{print $2}'`
        USER=`grep ^user $CONF | awk '{print $2}'`
        PASS=`grep ^password $CONF | awk '{print $2}'`
        OLDIFS=$IFS
        IFS='"'
        DEL_ROUTES=(`sed -n '/^delete_routes/{s/delete_routes[ \t\"]*//;s/\"[ \t]*\"/\"/g;p}' $CONF`)
        ADD_ROUTES=(`sed -n '/^add_routes/{s/add_routes[ \t\"]*//;s/\"[ \t]*\"/\"/g;p}' $CONF`)
        IFS=$OLDIFS

        /usr/bin/expect <<EOF
set vpn_client "$VPNC";
set ip "$HOST";
set user "$USER";
set pass "$PASS";
set timeout 5
spawn \$vpn_client connect \$ip
match_max 100000
expect { 
    timeout {
        puts "timeout error\n"
        spawn killall \$vpn_client
        exit 1
    }
    ">> The VPN client is not connected." { exit 0};
    ">> state: Disconnecting" { exit 0};
    "Connect Anyway?"
}
sleep .1
send -- "y\r"
expect { 
    timeout {
        puts "timeout error\n"
        spawn killall \$vpn_client
        exit 1
    }
    "Username:"
}
sleep .1
send -- "\$user\r"
expect { 
    timeout {
        puts "timeout error\n"
        spawn killall \$vpn_client
        exit 1
    }
    "Password: "
}
send -- "\$pass\r";
expect eof
EOF
        sleep 2
        # iptables
        iptables-save | grep -v DROP | iptables-restore

        # routes
        for ROUTE in "${DEL_ROUTES[@]}"
        do
#            echo route del $ROUTE
            route del $ROUTE
        done
        for ROUTE in "${ADD_ROUTES[@]}"
        do
#            echo route add $ROUTE
            route add $ROUTE
        done

        # dns
        while ! dnsfix
        do
            echo "Try again..."
#            chattr -i /etc/resolv.conf
        done

        echo "done."
        ;;
    "disconnect")
#        [ $ID -ne 0 ] && echo "Needs root." && exit 1
        # dns
#        chattr -i /etc/resolv.conf

        $VPNC disconnect
        ;;
    "state"|"stats")
        $VPNC $CMD
        ;;
    "hack")
        [ $ID -ne 0 ] && echo "Needs root." && exit 1
        /etc/init.d/vpnagentd stop
        sleep 1
        killall -9 vpnagentd 2>/dev/null
        cat - >/tmp/hack.c <<EOF
#include <sys/socket.h>
#include <linux/netlink.h>

int _ZN27CInterfaceRouteMonitorLinux20routeCallbackHandlerEv()
{
  int fd=50;          // max fd to try
  char buf[8192];
  struct sockaddr_nl sa;
  socklen_t len = sizeof(sa);

  while (fd) {
     if (!getsockname(fd, (struct sockaddr *)&sa, &len)) {
        if (sa.nl_family == AF_NETLINK) {
           ssize_t n = recv(fd, buf, sizeof(buf), MSG_DONTWAIT);
        }
     }
     fd--;
  }
  return 0;
}

int _ZN18CFileSystemWatcher11AddNewWatchESsj(void *string, unsigned int integer)
{
  return 0;
}
EOF
        gcc -o /tmp/libhack.so -shared -fPIC /tmp/hack.c
        mv /tmp/libhack.so $ANYCONNECT
        sed -i "s+^\([ \t]*\)$ANYCONNECT/bin/vpnagentd+\1LD_PRELOAD=$ANYCONNECT/lib/libhack.so $ANYCONNECT/bin/vpnagentd+" /etc/init.d/vpnagentd
        rm -f /tmp/hack.c
        /etc/init.d/vpnagentd start
        echo "done."
        ;;
    *)
        usage
        ;;
esac
2
  • 1
    Your notify hack solved my newfound (2017-02-25) problem with my AnyConnect 3.1.14018 installation, whereby it would disconnect any time I opened a new terminal window or GNU screen. It's watching /var/run/utmp for some reason. Well, not any more, thanks! Feb 26, 2017 at 7:31
  • Nice. Sometimes "overkill" can be your friend. :-)
    – Mauro Lacy
    Feb 26, 2017 at 13:02
4

My company still uses that vpn. The vpnc client simply changes you iptables settings that way :

# iptables-save
# Generated by iptables-save v1.4.10 on Sun Jun 17 14:12:20 2012
*filter
:INPUT DROP [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT DROP [0:0]
-A INPUT -s 123.244.255.254/32 -d 192.168.0.14/32 -j ACCEPT 
-A INPUT -i tun0 -j ACCEPT 
-A INPUT -i lo0 -j ACCEPT
-A INPUT -j DROP 
-A OUTPUT -s 192.168.0.14/32 -d 123.244.255.254/32 -j ACCEPT 
-A OUTPUT -o tun0 -j ACCEPT 
-A OUTPUT -o lo0 -j ACCEPT 
-A OUTPUT -j DROP 
COMMIT

It filters all except for the vpn traffic.

Simply get the filter in a file with iptables-save, add INPUT and OUTPOUT access lines that match your needs and reapply the file with iptables-restore.

for instance to access a local network on 192.168.0

# Generated by iptables-save v1.4.10 on Sun Jun 17 14:12:20 2012
*filter
:INPUT DROP [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT DROP [0:0]
-A INPUT -s 123.244.255.254/32 -d 192.168.0.14/32 -j ACCEPT 
-A INPUT -s 192.168.0.0/24 -d 192.168.0.14/32 -j ACCEPT      #local in
-A INPUT -i tun0 -j ACCEPT 
-A INPUT -i lo0 -j ACCEPT 
-A INPUT -j DROP 
-A OUTPUT -s 192.168.0.14/32 -d 123.244.255.254/32 -j ACCEPT 
-A OUTPUT -s 192.168.0.14/32 -d 192.168.0.0/24 -j ACCEPT     #local out
-A OUTPUT -o tun0 -j ACCEPT 
-A OUTPUT -o lo0 -j ACCEPT 
-A OUTPUT -j DROP 
COMMIT
1
  • 4
    Its wrong, its not that easy to just add your route.. I tried and it didn't work.. VPN client taking control of kernel routing table which is not letting you modify
    – Satish
    Mar 30, 2016 at 20:47
3

Any news on this?

At what level is Cisco VPN client driver doing what in the networking stack that takes overrides a local administrator's ability to administer their machine?

I fully agree and was wondering about the same thing.

Anyway, it's an app that requires admin privileges to install and while it runs it may very well filter what you do...

My attempts on Windows fail too:

route change 0.0.0.0 mask 0.0.0.0 192.168.1.1 metric 1
 OK!

IPv4 Route Table
===========================================================================
Active Routes:
Network Destination        Netmask          Gateway       Interface  Metric
          0.0.0.0          0.0.0.0      192.168.1.1    192.168.1.230     21 <-- LAN
          0.0.0.0          0.0.0.0    192.168.120.1    192.168.120.3      2 <-- VPN

Haha. No metric below 20 here it seems.

2
  • As far as linux is concerned, this (petefreitag.com/item/753.cfm) seems to indicate that the firewall is involved too.
    – Marki
    Jul 23, 2011 at 19:51
  • 3
    i found ShrewSoft VPN. It can connect to a Cisco IPSec VPN server, and it ignores the VPN server administrator's demand that i be disconnected from my own network. (See superuser.com/questions/312947/… for detailed instructions) Even though it doesn't answer this question, it is a workaround. Note: ShrewSoft VPN only works for IPSec; it doesn't work with SSL VPN (i.e. newer Cisco AnyConnect VPN client)
    – Ian Boyd
    Jul 24, 2011 at 0:57
3

I don't know if I have understood it right, but I first clarify my understanding:

You have a local LAN (for example, say 10.0.0.0/16, and a remote Cisco VPN Server (for example, 64.0.0.0/16). You want to connect to the VPN server through the Cisco VPN client and yet you need to have the LAN access. In this case you want to separate the whole 10.0.x.x/16 from the VPN connection). The following route must be added in a Mac client:

/sbin/route add -net 10.0 -interface en1

where en1 is the interface through which you are connected to your LAN. I know you can add the same thing in Windows and Linux as well.

1
  • 3
    +1 for Mac client; which doesn't apply to me. And while this command might work, the Cisco client might delete it shortly after being created (the Cisco client seems to prevent anyone from changing routes)
    – Ian Boyd
    Mar 5, 2013 at 19:06
3

Since I cannot add comments, I'll post here. I'm running on Windows.

The solution using Virtual Machine and run AnyConnect inside the VM and then use VM as a mediator between your working environment and company's network won't work if your "beloved" IT department routes 0.0.0.0 through VPN thus even your local network (including this between your local PC and VM) is routed through the VPN(sic!).

I tried to apply solution posted by @Sasha Pachev but eventually I ended up patching .dll so that it returns 0 at the beginning of the function. Eventually after some fight with dynamic library, I was able to modify routing tables according to my needs but apparently that's not enough!

Even though my rules seems to be correct to achieve split tunneling, I still get General Failure. Did you come across similar problem as were able to solve it?

  • My gateway to the internet is 192.168.163.2
  • My gateway to the company's network is 10.64.202.1 (thus whole 10...* subnet I treat as "comapny's")

This is how my routing table looks like now (after manual modifications while VPN is on)

enter image description here

yet the result of ping are following

C:\Users\Mike>ping -n 1 10.64.10.11
Reply from 10.64.10.11: bytes=32 time=162ms TTL=127

C:\Users\Mike>ping -n 1 8.8.8.8
PING: transmit failed. General failure.

C:\Users\Mike>ping -n 1 192.168.163.2
General failure.

Just for the reference, below is how route table looks like when VPN is disconnected (unaltered)

enter image description here

and this is how the table looks like when VPN is connected (unaltered) in that case when I'm trying to ping 8.8.8.8 I simply get timeout (since company's firewall does not allow traffic to go outside the intranet)

enter image description here

1
  • 1
    I'm having difficulty patching the DLL, could someone provide a copy of theirs or outline a bit more detail which offsets I need to change?
    – Sean C
    Mar 5, 2016 at 6:46
1

Try remove those entries with gateway 10.64.202.13 see if ping 8.8.8.8 works then add them back one by one and identify which one is causing the trouble.

How did you patch the DLL. I can't even modify the routing table because it keeps adding the 0.0.0.0 with VPN gateway back.

2
  • 1
    If you need clarification or additional information on a question please post a comment rather than including it in your answer. Thanks. May 1, 2014 at 8:48
  • wasn't allowing me to add comments to existing questions.
    – Tony
    May 1, 2014 at 14:44
1

How to use "openconnect" (via the openconnect-sso wrapper) with SAML and Duo two-factor authentication via Okta Single-Sign-on (SSO)

Tested on Ubuntu 18.04.
I have blacked out appropriate parts of the screenshots for my security.
This answer is also in my eRCaGuy_dotfiles repo here: https://github.com/ElectricRCAircraftGuy/eRCaGuy_dotfiles/blob/master/vpn/openconnect-sso.md.

Cisco AnyConnect is an incredibly restrictive VPN client. It routes all traffic though the VPN and blocks all local connections once connected to the VPN.

My preferred way to solve this is to simply use OpenConnect instead. It is compatible with Cisco AnyConnect servers and its client allows local connections even when the VPN is connected, routing only necessary traffic through the VPN (via split tunneling) to reach endpoints which are otherwise unavailable without the VPN. Therefore, openconnect solves this problem and allows LAN access while connected to a Cisco VPN.

Example 1: Simple openconnect example with Duo Two-factor authentication

Here is an example of how to connect to the Rice University VPN using openconnect: kb.rice.edu: VPN: openconnect VPN for Linux using Duo Authentication:

# install
sudo apt update 
sudo apt install vpnc-scripts openconnect

# connect
# NB: for the **second password** field when running the commands below, type
# `pin`, `push`, `phone`, or `sms` to specify how you'd like to receive your 
# two-factor authentication request. Add a number to the end of the command if
# you have multiple registered devices. Ex: `push2`, `phone2`, `sms2`, etc.

# Option 1: runs in the background
openconnect -b --quiet --user=netID --authgroup=RiceNet connect.rice.edu
# Option 2: runs in the background
openconnect -b --quiet --no-dtls --user=netID --authgroup=RiceNet connect.rice.edu

# Option 3: runs in the foreground
openconnect --no-dtls connect.rice.edu

To disconnect, use Ctrl + C in the terminal if running the process in the foreground. Or, if running the process in the background, open any terminal and run one of the following commands to send the running process the Ctrl + C SIGINT interrupt signal safely like this:

# to cleanly kill openconnect or openconnect-sso
sudo pkill --signal SIGINT openconnect
# or (same thing)
sudo pkill -SIGINT openconnect

See more details in my answer here: How to shut down openconnect cleanly?

Example 2 [What I use]: using the openconnect-sso wrapper for SAML authentication via Okta Single-Sign-on (SSO) and Duo two-factor authentication

My case is more complex, so I can't use openconnect by itself. Instead, I must use the openconnect-sso "OpenConnect Single Sign-On (SSO)" wrapper which allows SAML 2-factor authentication via Okta, in place of the Cisco AnyConnect client.

I found installing openconnect-sso to be incredibly difficult due to some simple dependency problems, but these instructions should make it easy for you:

sudo apt update
sudo apt install vpnc-scripts openconnect   # install openconnect
sudo apt install python3

python3 -m pip install --upgrade pip
python3 -m pip install openconnect-sso      # install openconnect-sso

# install openconnect-sso dependencies, including forcing a reinstall of PyQt5

# uninstall
python3 -m pip uninstall PyQt5
python3 -m pip uninstall PyQt5-sip
python3 -m pip uninstall PyQtWebEngine
python3 -m pip uninstall keyring

# reinstall
python3 -m pip install PyQt5
python3 -m pip install PyQt5-sip
python3 -m pip install PyQtWebEngine
python3 -m pip install keyring

python3 -m pip install cffi

# Check the version
# My output is: `openconnect-sso 0.7.3`
openconnect-sso --version

Usage (note: for how to find your server address and SAML group, see below):

VPN_SERVER_ADDRESS="myvpn.whatever.com"   # example server address to connect to
VPN_SAML_GROUP="whatever-saml-whatever"   # example SAML group name
VPN_USER="my.username@something.com"      # example username
# or perhaps just this for the username:
# VPN_USER="my.username"

# connect via `openconnect-sso`
# The first time ever, you must specify everything
openconnect-sso --server "${VPN_SERVER_ADDRESS}/${VPN_SAML_GROUP}" --user "${VPN_USER}"
# Subsequent connection attempts can be done with just this, since apparently
# the server address, SAML group, and username are cached after the first usage
openconnect-sso

Screenshots and sequence of events during connecting:

Once I run the openconnect-sso command above, this is what happens:

  1. openconnect-sso opens up a web-page which is "Powered by Okta" (as stated at the bottom of it--see screenshot below) and which is requesting my Username and Password for Duo SSO (single sign-on) two-factor authentication. My username and password are already filled in--probably since I've done this before. It says, "We found some errors. Please review the form and make corrections." Ignore that error. I think this is just because the username and password were automatically typed in, and it doesn't detect them yet. To make it detect them, I just have to click in the username box on my already-typed-in username and press Tab twice. That interaction with the input boxes causes the form to detect that my username and password are present. It then automatically validates my username and password since they are already typed in, and then it loads a new web page. enter image description here

  2. On the new page, I make sure my correct phone number or device is selected in the "Device" box, then I click the "Send me a Push" button, and it sends a Duo two-factor authentication push request to my phone. I authenticate on my phone in the Duo app, then the webpage window automatically closes. enter image description here

    Note that in the screenshot above, it says my "computer software is out of date" simply because it wants me to update my version of the Chrome browser to the latest. If I don't do that at least every 10 days or so, the VPN server won't let me log in.

  3. Next, in the terminal, openconnect-sso prints some statements that it has exited the browser (shown just below), then it requests my sudo password (also shown just below) for my Linux Ubuntu username so it can run as root to do the final VPN connection as root. I type that in and press Enter. It then finishes connecting to the VPN. The last several lines it prints out, starting with where it closed the browser window and then asked for my Linux sudo password, look as follows (note that I have changed my IP addresses in the output for my security):

    [info     ] Terminate requested.           [webengine] 
    [info     ] Exiting browser                [webengine] 
    [info     ] Browser exited                 [openconnect_sso.browser.browser] 
    [info     ] Response received              [openconnect_sso.authenticator] id=success message=
    [sudo] password for gabriel: 
    Connected to xxx.xxx.xxx.x:443
    SSL negotiation with myvpn.whatever.com
    Server certificate verify failed: signer not found
    Connected to HTTPS on myvpn.whatever.com
    Got CONNECT response: HTTP/1.1 200 OK
    CSTP connected. DPD 30, Keepalive 20
    Connected as xx.xxx.x.xxx + aaaa:bbbb:cccc::ddd/64, using SSL
    Established DTLS connection (using GnuTLS). Ciphersuite (DTLS0.9)-(DHE-RSA-4294967237)-(AES-256-CBC)-(SHA1).
    
  4. Success! I am now fully connected to the VPN, yet I still have full access to my local LAN and can ssh into my local embedded-Linux boards!

Again, to disconnect, use Ctrl + C in the terminal running the process in the foreground. Or, open any terminal and run one of the following commands to send the running process the Ctrl + C SIGINT interrupt signal safely like this:

# to cleanly kill openconnect or openconnect-sso
sudo pkill --signal SIGINT openconnect
# or (same thing)
sudo pkill -SIGINT openconnect

See more details in my answer here: How to shut down openconnect cleanly?:

If you use sudo pkill openconnect instead, it sends the default SIGTERM termination signal instead, which force-kills it and does not kill it cleanly. If you make this simple mistake, simply turn your WiFi card OFF then back ON again by toggling it with Fn + F8 or equivalent (look for the wifi beacon icon) on your laptop keyboard. This resets your internet connection so your internet will work again.

How to find your VPN server address and SAML group

Tested with Cisco AnyConnect Secure Mobility Client version 4.10.05085 on Linux Ubuntu 18.04:

enter image description here

  1. Open the Cisco AnyConnect client and click the "VPN" tab. It will look like this. Your VPN server address is what is shown in the "Connect to" box. enter image description here
  2. Click the "Connect" button and it will open up a "Powered by Okta" "Duo SSO" window in a new browser window.
    1. That browser window looks like this: enter image description here
    2. The Cisco AnyConnect window now shows a "Group" box which shows your SAML Group: enter image description here
  3. Use that VPN server address and SAML Group name in the openconnect-sso command above.

Example 2 troubleshooting

If you can't get the PyQt5 or other dependencies to work with plain Python3, then it may be because your Python3 version is too old. Try forcefully installing and using a later version of Python3 like this. For example, if I wanted to use Python3.8 it would look like this:

sudo apt update
sudo apt install vpnc-scripts openconnect   # install openconnect
sudo apt install python3.8

python3.8 -m pip install --upgrade pip
python3.8 -m pip install openconnect-sso      # install openconnect-sso

# install openconnect-sso dependencies, including forcing a reinstall of PyQt5

# uninstall
python3.8 -m pip uninstall PyQt5
python3.8 -m pip uninstall PyQt5-sip
python3.8 -m pip uninstall PyQtWebEngine
python3.8 -m pip uninstall keyring

# reinstall
python3.8 -m pip install PyQt5
python3.8 -m pip install PyQt5-sip
python3.8 -m pip install PyQtWebEngine
python3.8 -m pip install keyring

python3.8 -m pip install cffi

# Check the version
# My output is: `openconnect-sso 0.7.3`
openconnect-sso --version

Other tips

You can view various info. about your VPN server like this (source: https://gitlab.com/openconnect/openconnect/-/issues/84):

openconnect --dump -vvvv myvpn.whatever.com

References

Here are most of the additional references I had to look at to figure out some of the dependency and related info. above.

  1. https://kb.rice.edu/page.php?id=113148
  2. https://github.com/dlenski/openconnect/issues/116
  3. *****Python 3.7.0 No module named 'PyQt5.QtWebEngineWidgets'
  4. No module named _cffi_backend
  5. https://github.com/Nike-Inc/gimme-aws-creds/issues/158
  6. https://bobbyhadz.com/blog/python-no-module-named-pyqt5
  7. [my answer] How to install PyQt5 in Python3 and here
  8. ***** The openconnect-sso link at the very top of this thread is how I first learned of openconnect-sso!: https://gitlab.com/openconnect/openconnect/-/issues/84
  9. [my Q&A] Disable VPN for certain local devices, such as an embedded Linux board I need to ssh into (Allow local (LAN) access when using VPN)
  10. [my answer] How to shut down openconnect cleanly?
  11. https://github.com/vlaci/openconnect-sso
    1. All issues I opened: https://github.com/vlaci/openconnect-sso/issues?q=is%3Aissue+author%3AElectricRCAircraftGuy+
    2. [my issue] ModuleNotFoundError: No module named 'PyQt5' [solved; please update installation instructions with this info]
  12. https://github.com/dlenski/openconnect/issues/143
  13. https://gitlab.com/openconnect/openconnect/-/issues/84
  14. Google search for "open connect with duo two factor authentication"
  15. ***** Google search for "openconnect" with duo two factor authentication and "okta"
  16. https://www.reddit.com/r/archlinux/comments/8wclaz/openconnect_and_two_factor_auth/
  17. https://duo.com/docs/okta
  18. [my answer] Cisco Anyconnect not working on Ubuntu 18.04 with two-factor authentication
  19. official openconnect repo--I think!: https://gitlab.com/openconnect/openconnect
  20. https://docs.python-guide.org/starting/install3/linux/
  21. Dealing with multiple Python versions and PIP?

See also

  1. [my answer] Ask Ubuntu: How to install openconnect-sso on Ubuntu 21.04

Not the answer you're looking for? Browse other questions tagged or ask your own question.