OpenConnect

From ArchWiki

OpenConnect is free open-source software for client-to-site VPNs. It allows you to connect to various commercial so-called SSL VPN servers/gateways/concentrators, namely:

  • Cisco AnyConnect anyconnect
  • Palo Alto Networks (PAN) GlobalProtect gp
  • Junos/Ivanti Pulse Secure pulse, see also Pulse Connect Secure
  • Juniper Network Connect nc
  • Fortinet fortinet
  • F5 f5
  • Array Networks array

Installation

For integration with NetworkManager which you probably use if you have a desktop environment like GNOME or KDE, install the networkmanager-openconnect package. It will pull in the openconnect package as a dependency. If you do not need the integration, install only the openconnect package.

Some VPNs are set up for split routing and therefore split DNS. To cater for that and in general, it's recommended to use systemd-resolved and the resolv.conf stub mode. To verify that this is the case, run resolvectl and look for the line "resolv.conf mode: stub" in the Global section. Understanding the full output can be very useful so it's probably a good idea to study it. When connected to a VPN, the output will look roughly like this:

$ resolvectl
Global
       Protocols: -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
resolv.conf mode: stub

Link 2 (enp1s0)
    Current Scopes: DNS
         Protocols: +DefaultRoute +LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
Current DNS Server: 192.168.0.1
       DNS Servers: 192.168.0.1
        DNS Domain: ~home.box

Link 3 (tun0)
    Current Scopes: DNS
         Protocols: -DefaultRoute +LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
Current DNS Server: 10.10.10.10
       DNS Servers: 10.10.10.10
        DNS Domain: ~example.org ~int.example.net

Usage

Plain OpenConnect

OpenConnect has many options, see openconnect(8). In the simplest case, you are using a Cisco AnyConnect VPN, thus you only have to provide the address, then enter your username and password when prompted:

$ sudo openconnect vpnserviceaddr

If you use a VPN type other than Cisco AnyConnect, add the "--protocol" option specifying either nc, gp, pulse, f5, fortinet, or array:

$ sudo openconnect --protocol=vpntype vpnserviceaddr

The username can be provided too:

$ sudo openconnect --protocol=vpntype -u user vpnserviceaddr

Some VPNs offer different authentication groups for different access configurations like for example for a full tunnel or split tunnel connection. To show the different offered auth-groups and to get more information about the service in general, use:

$ sudo openconnect --protocol=vpntype --authenticate vpnserviceaddr

Some VPNs require gathering information about your computer when connecting. In that case the "--csd-wrapper" option can help. Scripts to use and adapt can be found under "/usr/lib/openconnect/".

$ sudo openconnect --protocol=vpntype --csd-wrapper=script vpnserviceaddr

Some VPNs delegate authentication to an identity provider (IdP) like Google, Microsoft AzureActiveDirectory, or Okta. This scenario is often called single sign-on (SSO) and usually employs the SAML 2.0 standard or the OIDC standard. It goes like this: In a browser or webview, you open the VPN's SSO address which redirects to the IdP which sends you back to the VPN page where you end up with a cookie. The cookie must be passed to openconnect using the option "-C,--cookie" or "--cookie-on-stdin". To copy the cookie from your browser, use its developer tools.

$ sudo openconnect --protocol=vpntype --cookie-on-stdin vpnserviceaddr

For Fortinet SSO VPNs, you can use openfortivpn-webview-qtAUR to fetch the cookie instead of fiddling with your browser's developer tools:

$ sudo openconnect --protocol=fortinet -C "$(openfortivpn-webview vpnserviceaddr)" vpnserviceaddr

OpenConnect passes a couple of environment variables to a script in order to configure IP routing and DNS routing. By default that's "/etc/vpnc/vpnc-script". It supports a few environment variables that you can set if needed, e.g. "CISCO_SPLIT_DNS" to add additional DNS domains to the network interface besides the ones pushed by the VPN:

$ sudo CISCO_SPLIT_DNS=~internal.example.com,~10.in-addr.arpa openconnect --protocol=vpntype vpnserviceaddr

You can use your own script instead of the default one using the option "-s,--script":

$ sudo openconnect --protocol=vpntype -s script vpnserviceaddr

You can also team up OpenConnect with a proxy like ocproxy-gitAUR in order to do SSH-style port-forwarding. E.g. a connection to localhost:13389 using rdesktop gets forwarded through the VPN to the RDS service rds.int.example.net:3389 if you run:

$ openconnect --protocol=vpntype --script-tun -s "ocproxy -L 13389:rds.int.example.net:3389" vpnserviceaddr
$ rdesktop localhost:13389

With NetworkManager

NetworkManager can be controlled via command line interface nmcli, via terminal user interface nmtui, and via many desktop environments, thanks to its D-Bus API. For NetworkManager a VPN is just another connection of type "vpn". To create one based on OpenConnect named "MyOrgVPN" with the CLI, run:

$ nmcli connection add \
	 connection.id MyOrgVPN \
	 connection.type vpn \
	 vpn.service-type openconnect \
	 vpn.data cookie-flags=2,gateway=vpnserviceaddr,protocol=vpntype vpn.secrets gateway=vpnserviceaddr,gwcert=

Activating the connection using nmcli connection up MyOrgVPN (i.e. without --ask) or GNOME's menu in the top right corner or GNOME settings a.k.a control center works in most cases as one would expect: a window opens and asks for a username, password, and possibly 2FA/MFA; even SSO scenarios often work if the VPN type is anyconnect or gp. For other desktop environments you might have to install network-manager-applet, nm-connection-editor, or some other package in order to control NetworkManager connections.

Activating the connection using nmcli --ask connection up MyOrgVPN or nmtui or nmtui connect MyOrgVPN does not work the way one would expect though: instead of getting asked for a username, etc., one gets asked for "Cookie (vpn.secrets.cookie)". To provide it, you can use a Bash script like this one:

~/vpn-up.bash
#!/usr/bin/bash

connection_name=$(nmcli -t -g type,name connection \
	| grep -Po '^vpn:\K.*' \
	| sed 's/\\:/:/g' | fzf
)
eval "$(nmcli -t -g vpn.data connection show "$connection_name" \
	| grep -Po '(^|[ ,])(protocol|gateway)\ =\ [^,]*' \
	| sed 's/ //g')"
eval "$(openconnect --authenticate --protocol="$protocol" "$gateway")"
nmcli connection up "$connection_name" \
	passwd-file <(echo "vpn.secrets.cookie:$COOKIE")

To deactivate the connection, use nmcli connection down MyOrgVPN or nmtui or GNOME's menu in the top right corner or GNOME settings a.k.a control center.

Note: NetworkManager-openconnect does not handle every VPN scenario yet, so in some cases you might have to use plain OpenConnect as described above.

Single Sign-On (SSO) with WebAuth and Anyconnect

networkmanager-openconnect supports Single Sign-On (SSO) with WebAuth for Cisco Anyconnect. To use WebAuth:

  • Do not forget to install optional dependencies for networkmanager-openconnect like webkit2gtk-*
  • Add something like "AnyConnect Linux_64 4.10.07061" to "User Agent" field on the VPN tab for this connection

Activate the connection. It should bring you a window with a browser for WebAuth to complete. If it succeeded the networkmanager-openconnect will launch openconnect and pass the "webvpn" cookie for it to connect.

With netctl

A simple tuntap netctl.profile(5) can be used to integrate OpenConnect in the normal netctl workflow. For example:

/etc/netctl/vpn
Description='VPN'
Interface=vpn
Connection=tuntap
Mode=tun
#User=root
#Group=root

BindsToInterfaces=(enp0s25 wlp2s0)
IP=no

PIDFILE=/run/openconnect_${Interface}.pid
SERVER=vpn.example.net
AUTHGROUP='<AUTHGROUP>'
LOCAL_USERNAME=<USERNAME>
REMOTE_USERNAME=<VPN_USERNAME>
# Assuming the use of pass(1): 
PASSWORD_CMD="su ${LOCAL_USERNAME} -c \"pass ${REMOTE_USERNAME} | head -n 1\""

ExecUpPost="${PASSWORD_CMD} | /usr/bin/openconnect --background --pid-file=${PIDFILE} --interface='${Interface}' --authgroup='${AUTHGROUP}' --user='${REMOTE_USERNAME}' --passwd-on-stdin ${SERVER}"
ExecDownPre="kill -INT $(cat ${PIDFILE}) ; resolvconf -d ${Interface} ; ip link delete ${Interface}"

This allows execution like:

$ netctl start vpn
$ netctl restart vpn
$ netctl stop vpn

Note that this relies on LOCAL_USERNAME having a gpg-agent running, with the passphrase for the PGP key already cached.

If pass's interactive query is wanted, use the following line for PASSWORD_CMD:

DISPLAY=":0"
PASSWORD_CMD="su ${LOCAL_USERNAME} -c \"DISPLAY=${DISPLAY} pass ${REMOTE_USERNAME} | head -n 1\""

Adjust the DISPLAY variable as necessary.

Troubleshooting

Cisco AnyConnect

For Cisco AnyConnect VPNs, if you try to use 2FA/MFA but it is not prompting you for the passcode, you need to set the useragent to AnyConnect ... . This is an issue with Cisco, here is the relevant issue in the OpenConnect project.