How to connect to a corporate VPN in Linux using openconnect and vpn-slice

Want to use Linux at work, but the corporate VPN won’t allow it? This article might help, although I'm not entirely sure. I want to warn you in advance that I have a limited understanding of network administration, so it's possible that I did everything wrong. On the other hand, I might be able to write a guide that is easily understood by regular people, so I recommend giving it a try.

The article contains a lot of unnecessary information, but without this knowledge, I wouldn't have been able to solve the unexpected issues I faced while setting up the VPN. I believe that anyone trying to apply this guide will encounter problems that I didn't have, and I hope that this extra information will help them resolve those issues on their own.

Most of the commands used in the guide need to be executed through sudo, which I've omitted for brevity. Please keep that in mind.

Most of the IP addresses have been heavily obfuscated, so if you see an address like 435.435.435.435 — there should be a valid IP specific to your case.

I have Ubuntu 18.04, but I think with minor adjustments, the guide can also be applied to other distributions. However, in this text, Linux == Ubuntu.

Cisco Connect

Those using Windows or MacOS can connect to our corporate VPN via Cisco Connect, which requires specifying the gateway address and entering a password composed of a fixed part and a code generated by Google Authenticator each time.

In the case of Linux, I was unable to set up Cisco Connect, but I found a recommendation to use openconnect, which is designed specifically as a replacement for Cisco Connect.

Openconnect

In theory, Ubuntu has a special graphical interface for openconnect, but it didn’t work for me. Maybe that's for the best.

In Ubuntu, openconnect is installed via the package manager.

apt install openconnect

Right after installation, you can try to connect to the VPN.

openconnect --user poxvuibr vpn.evilcorp.com

vpn.evilcorp.com is the address of a fictional VPN.
poxvuibr is the name of a fictional user.

openconnect will prompt you to enter a password, which consists of a fixed part and a code from Google Authenticator, and then it will try to connect to the VPN. If it works, congratulations, you can confidently skip the troublesome middle section and go to the part about using openconnect in the background. If it doesn’t work, you can keep trying. However, if it did connect while using, for example, guest Wi-Fi at work, it might be too soon to celebrate, as you'll need to try to replicate the process from home.

Certificate

There’s a high chance that nothing will start, and the output from openconnect will look something like this:

POST https://vpn.evilcorp.com/
Connected to 777.777.777.777:443
SSL negotiation with vpn.evilcorp.com
Server certificate verify failed: signer not found

Certificate from VPN server "vpn.evilcorp.com" failed verification.
Reason: signer not found
To trust this server in future, perhaps add this to your command line:
    --servercert sha256:4444444444444444444444444444444444444444444444444444444444444444
Enter 'yes' to accept, 'no' to abort; anything else to view: fgets (stdin): Operation now in progress

On one hand, it's unpleasant because the VPN connection did not succeed, but on the other hand, it’s clear how to fix this issue.

Here, the server sent us a certificate, which can confirm that the connection is indeed being made to the server of the parent corporation and not to a malicious impersonator, but the system does not recognize this certificate. Because of this, it cannot verify whether the server is authentic or not. Therefore, it stops the process just to be safe.

In order for openconnect to successfully connect to the server, you need to explicitly tell it which certificate should be expected from the VPN server using the key --servercert.

You can find out which certificate was sent to us by the server directly from what openconnect printed. Here’s that part:

To trust this server in future, perhaps add this to your command line:
    --servercert sha256:4444444444444444444444444444444444444444444444444444444444444444
Enter 'yes' to accept, 'no' to abort; anything else to view: fgets (stdin): Operation now in progress

You can try to connect again with this command.

openconnect --servercert sha256:4444444444444444444444444444444444444444444444444444444444444444 --user poxvuibr vpn.evilcorp.com

Perhaps it has now succeeded, then you can move on to the end. But personally, Ubuntu showed me a thumbs down in this form.

POST https://vpn.evilcorp.com/
Connected to 777.777.777.777:443
SSL negotiation with vpn.evilcorp.com
Server certificate verify failed: signer not found
Connected to HTTPS on vpn.evilcorp.com
XML POST enabled
Please enter your username and password.
POST https://vpn.evilcorp.com/
Got CONNECT response: HTTP/1.1 200 OK
CSTP connected. DPD 300, Keepalive 30
Set up DTLS failed; using SSL instead
Connected as 192.168.333.222, using SSL
NOSSSSSHHHHHHHDDDDD
3
NOSSSSSHHHHHHHDDDDD
3
RTNETLINK answers: File exists
/etc/resolvconf/update.d/libc: Warning: /etc/resolv.conf is not a symbolic link to /run/resolvconf/resolv.conf

/etc/resolv.conf

# Generated by NetworkManager
search gst.evilcorpguest.com
nameserver 127.0.0.53

/run/resolvconf/resolv.conf

# Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8)
#     DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN
# 127.0.0.53 is the systemd-resolved stub resolver.
# run "systemd-resolve --status" to see details about the actual nameservers.

nameserver 192.168.430.534
nameserver 127.0.0.53
search evilcorp.com gst.publicevilcorp.com

habr.com will resolve, but access will be denied. Addresses like jira.evilcorp.com do not resolve at all.

What happened here is unclear to me. But the experiment shows that by adding the line to /etc/resolv.conf

nameserver 192.168.430.534

the addresses within the VPN will start to magically resolve, allowing access. This means that the DNS resolver looks specifically at /etc/resolv.conf, not elsewhere.

It is possible to verify that the VPN connection exists and is working without modifying /etc/resolv.conf; simply enter the IP address instead of a symbolic resource name from the VPN in your browser.

Ultimately, there are two problems.

  • When connecting to the VPN, its DNS is not picked up.
  • All traffic goes through the VPN, which does not allow internet access.

I'll explain what to do, but first a bit about automation.

Automatic input of the fixed part of the password.

By now, you’ve likely entered the password at least five times, and this process has already worn you out. Firstly, because the password is long, and secondly, because you need to fit this within a fixed time frame.

The final solution to the problem was not included in the article, but we can make it so that the fixed part of the password doesn't need to be entered multiple times.

Let’s assume the fixed part of the password is fixedPassword, and the part from Google Authenticator is 567 987. The entire password can be passed to openconnect through standard input using the --passwd-on-stdin argument.

echo "fixedPassword567987" | openconnect --servercert sha256:4444444444444444444444444444444444444444444444444444444444444444 --user poxvuibr vpn.evilcorp.com --passwd-on-stdin

Now you can keep returning to the last entered command and only change the part from Google Authenticator.

Corporate VPN does not allow internet access.

It's really inconvenient to have to use a separate computer just to access habr. The inability to copy-paste from Stack Overflow can severely hinder your work, which means action must be taken.

It needs to be organized in such a way that when accessing a resource from the internal network, Linux uses the VPN, and when accessing Habr, it goes through the internet.

OpenConnect, after launching and establishing a connection to the VPN, executes a special script located in /usr/share/vpnc-scripts/vpnc-script. Some variables are passed to the script, and it configures the VPN. Unfortunately, I couldn't figure out how to split the traffic streams between the corporate VPN and the rest of the internet with the native script.

It seems that a utility called vpn-slice was specifically developed for people like me, which allows routing traffic through two channels without having to jump through hoops. Well, there will still be some hoops, but you don’t need to be a shaman.

Traffic splitting with vpn-slice

First, you need to install vpn-slice, and you'll have to figure this out on your own. If there are questions in the comments, I will write a separate post about it. But it's a regular program in Python, so there shouldn't be any difficulties. I installed it using virtualenv.

Next, you need to apply the utility, using the --script key to specify openconnect, so that instead of the standard script, vpn-slice is used.

echo "fixedPassword567987" | openconnect --servercert sha256:4444444444444444444444444444444444444444444444444444444444444444 --user poxvuibr --passwd-on-stdin 
--script ".\/bin\/vpn-slice 192.168.430.0\/24  " vpn.evilcorp.com 

In --script, a string with the command to call instead of the script is passed. .\/bin\/vpn-slice is the path to the executable file, and 192.168.430.0\/24 is the address mask for which VPN should be used. This means that if an address starts with 192.168.430, then the resource with this address should be accessed through the VPN.

Now the situation should be almost normal. Almost. You can now access Habr and access the internal corporate resource by IP, but you can't access the internal corporate resource by its symbolic name. If you specify the correspondence between the symbolic name and the address in hosts, it should work. And it will work until the IP changes. Linux can now access the internet or the internal corporate network depending on the IP. However, a non-corporate DNS is still used for address resolution.

The problem may also manifest in such a way — everything works fine at work, but at home you can access corporate resources only by IP. This is because when you are connected to the corporate Wi-Fi, the corporate DNS is used as well, and it resolves symbolic addresses from the VPN, even though it's still impossible to access such addresses without using the VPN.

Automatic modification of the hosts file

If vpn-slice is politely asked, it might, after raising the VPN, check its DNS, find the IP addresses of the required resources by their symbolic names, and write them in the hosts file. After the VPN is turned off, these addresses will be removed from the hosts. To do this, symbolic names need to be passed to vpn-slice as arguments. Like this.

echo "fixedPassword567987" | openconnect --servercert sha256:4444444444444444444444444444444444444444444444444444444444444444 --user poxvuibr --passwd-on-stdin
--script ".\/bin\/vpn-slice 192.168.430.0\/24  jira.vpn.evilcorp.com git.vpn.evilcorp.com " vpn.evilcorp.com 

Now everything should work both in the office and at the beach.

Search for the addresses of all subdomains in the DNS provided by the VPN

If there are not many addresses within the network, the approach of automatically modifying the hosts file works quite well. But if there are many resources in the network, you will constantly need to add lines like zoidberg.test.evilcorp.com to the script, as zoidberg is the name of one of the test stands.

But now that we understand a bit better, this need can be eliminated.

If you look in \/etc\/hosts after raising the VPN, you can see a line like this

192.168.430.534 dns0.tun0 # vpn-slice-tun0 AUTOCREATED

Also, a new line was added in resolv.conf. In short, vpn-slice somehow determined where the DNS server for the VPN is located.

Now we need to make it so that to find out the IP address of a domain name ending in evilcorp.com, Linux queries the corporate DNS, and for anything else, it defaults to the default DNS.

I googled for quite a while and found that this functionality is available in Ubuntu out of the box. This refers to the ability to use a local DNS server dnsmasq for name resolution.

So it is possible to set it up so that Linux always queries the local DNS server for IP addresses, which in turn will look for the IP on the appropriate external DNS server based on the domain name.

To manage everything related to networks and network connections in Ubuntu, NetworkManager is used, and the graphical interface for selecting, for example, a Wi-Fi connection is simply a front end to it.

We'll need to explore its configurations.

  1. Create a file in /etc/NetworkManager/dnsmasq.d/evilcorp

address=/.evilcorp.com/192.168.430.534

Note the dot before evilcorp. It signals dnsmasq that all subdomains of evilcorp.com should be resolved in the corporate DNS.

  1. Tell NetworkManager to use dnsmasq for name resolution.

The configuration of the network manager is located in /etc/NetworkManager/NetworkManager.conf. We need to add:

[main]
dns=dnsmasq

  1. Restart NetworkManager.

service network-manager restart

Now, after connecting to the VPN using the openconnect and vpn-slice combination, the IP will be correctly determined, even without adding symbolic addresses to the arguments for vpnslice.

How to access individual services through VPN

After I managed to connect to the VPN, I was very happy for a couple of days, but then I found out that if I connect to the VPN not from the office network, the email doesn't work. Familiar symptom, isn't it?

Our email is located at mail.publicevilcorp.com, which means it doesn't fall under the rule in dnsmasq, and the address of the mail server is looked up through public DNS.

However, in the office, DNS is still used, which has this address. At least, that’s what I thought. In reality, after adding the line to dnsmasq:

address=/mail.publicevilcorp.com/192.168.430.534

the situation did not change at all. The IP remained the same. I had to go to work.

Only later, when I delved into the situation and figured out the problem a bit, one clever person advised me on how to solve it. I needed to connect to the mail server not just casually, but through VPN.

I use vpn-slice to route through VPN to addresses that start with 192.168.430. And the mail server not only does not have a symbolic address that is a subdomain of evilcorp, but its IP address also does not start with 192.168.430. And, of course, it doesn’t allow anyone from the public network.

To enable Linux to route through VPN and to the mail server, it needs to be added to vpn-slice as well. Let’s say the mail server's address is 555.555.555.555.

echo "fixedPassword567987" | openconnect --servercert sha256:4444444444444444444444444444444444444444444444444444444444444444 --user poxvuibr --passwd-on-stdin
--script "./bin/vpn-slice 555.555.555.555 192.168.430.0/24" vpn.evilcorp.com 

Script for raising the VPN with a single argument.

Of course, this is not very convenient. Yes, you can save the text to a file and copy-paste it into the console instead of typing it out by hand, but it's still not pleasant. To ease the process, you can wrap the command in a script that will reside in your PATH. Then you'll only need to enter the code obtained from Google Authenticator.

#!/bin/sh  
echo "fixedPassword$1" | openconnect --servercert sha256:4444444444444444444444444444444444444444444444444444444444444444 --user poxvuibr --passwd-on-stdin 
--script "./bin/vpn-slice 192.168.430.0/24  jira.vpn.evilcorp.com git.vpn.evilcorp.com " vpn.evilcorp.com 

If you place the script in connect~evilcorp~, you can simply type in the console.

connect_evil_corp 567987

But now you will still have to keep the console where openconnect is running open for some reason.

Running openconnect in the background.

Fortunately, the authors of openconnect took care of us and added a special key—background—to the program, which allows the program to run in the background after launching. If you start it this way, you can close the console after launching.

#!/bin/sh  
echo "fixedPassword$1" | openconnect --servercert sha256:4444444444444444444444444444444444444444444444444444444444444444 
--user poxvuibr 
--passwd-on-stdin 
--background 
--script "./bin/vpn-slice 192.168.430.0/24  jira.vpn.evilcorp.com git.vpn.evilcorp.com " vpn.evilcorp.com  

Now it's just unclear where the logs go. We don't really need the logs, but who knows. openconnect can redirect them to syslog, where they will be kept intact and secure. You need to add the key—syslog to the command.

#!/bin/sh  
echo "fixedPassword$1" | openconnect --servercert sha256:4444444444444444444444444444444444444444444444444444444444444444 
--user poxvuibr 
--passwd-on-stdin 
--background 
--syslog 
--script "./bin/vpn-slice 192.168.430.0/24  jira.vpn.evilcorp.com git.vpn.evilcorp.com " vpn.evilcorp.com  

And so it turns out that openconnect works somewhere in the background and doesn't disturb anyone, but it's unclear how to stop it. You can of course filter the output of ps with grep and look for a process whose name contains openconnect, but that's somewhat tedious. Thanks to the authors who also thought about this. In openconnect, there is a key—pid-file, which allows you to instruct openconnect to write its process identifier to a file.

#!/bin/sh  
echo "fixedPassword$1" | openconnect --servercert sha256:4444444444444444444444444444444444444444444444444444444444444444 
--user poxvuibr 
--passwd-on-stdin 
--background  
--syslog 
--script "./bin/vpn-slice 192.168.430.0/24  jira.vpn.evilcorp.com git.vpn.evilcorp.com " vpn.evilcorp.com  
--pid-file ~/vpn-pid

Now you can always kill the process with the command.

kill $(cat ~/vpn-pid)

If the process does not exist, kill will complain but not throw an error. If the file does not exist, nothing bad will happen either, so you can safely kill the process in the first line of the script.

kill $(cat ~/vpn-pid)
#! /bin/sh  
echo "fixedPassword$1" | openconnect --servercert sha256:4444444444444444444444444444444444444444444444444444444444444444 
--user poxvuibr 
--passwd-on-stdin 
--background 
--syslog 
--script ". /bin/vpn-slice 192.168.430.0/24  jira.vpn.evilcorp.com git.vpn.evilcorp.com " vpn.evilcorp.com  
--pid-file ~/vpn-pid

Now you can turn on the computer, open the console, and run the command, passing it the code from Google Authenticator. You can then kill the console.

Without vpn-slice. Instead of an epilogue.

Understanding how to live without vpn-slice turned out to be very difficult. I had to read and Google a lot. Fortunately, after spending so much time on the problem, technical manuals and even the man page for openconnect read like captivating novels.

I found out that vpn-slice, like its native script, modifies the routing table to split networks.

Routing Table

Simply put, this is a table where the first column contains the starting address that Linux should use, and the second column shows which network adapter should be used to reach that address. There are actually more columns, but that doesn't change the essence.

To view the routing table, you need to execute the command ip route

default via 192.168.1.1 dev wlp3s0 proto dhcp metric 600 
192.168.430.0/24 dev tun0 scope link 
192.168.1.0/24 dev wlp3s0 proto kernel scope link src 192.168.1.534 metric 600 
192.168.430.534 dev tun0 scope link 

Here, each line specifies where to go in order to send a message to a certain address. The first part describes the starting point of the address. To understand how to determine that 192.168.0.0/16 means the address should start with 192.168, you need to google what an IP address mask is. After 'dev' is the name of the adapter to which the message should be sent.

For VPN, Linux created a virtual adapter — tun0. The line responsible for routing all traffic for addresses starting with 192.168 through it is

192.168.0.0/16 dev tun0 scope link 

You can also check the current state of the routing table using the command route -n (IP addresses cleverly anonymized) This command produces output in a different format and is generally deprecated, but its output is often encountered in manuals online, so you need to know how to read it.

The starting point of the IP address for the route can be understood from the combination of the Destination and Genmask columns. The parts of the IP address that correspond to 255 in the Genmask are taken into account, while those that correspond to 0 are not. For example, a combination of Destination 192.168.0.0 and Genmask 255.255.255.0 means that if the address starts with 192.168.0, the request will go through this route. If the Destination is 192.168.0.0 but the Genmask is 255.255.0.0, then requests to addresses starting with 192.168 will use this route.

To understand what vpn-slice actually does, I decided to look at the states of the tables before and after

Before enabling the VPN, it was like this

route -n 

Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         222.222.222.1   0.0.0.0         UG    600    0        0 wlp3s0
222.222.222.0   0.0.0.0         255.255.255.0   U     600    0        0 wlp3s0
333.333.333.333 222.222.222.1   255.255.255.255 UGH   0      0        0 wlp3s0

After calling openconnect without vpn-slice, it became this

route -n

Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         0.0.0.0         0.0.0.0         U     0      0        0 tun0
0.0.0.0         222.222.222.1   0.0.0.0         UG    600    0        0 wlp3s0
222.222.222.0   0.0.0.0         255.255.255.0   U     600    0        0 wlp3s0
333.333.333.333 222.222.222.1   255.255.255.255 UGH   0      0        0 wlp3s0
192.168.430.0   0.0.0.0         255.255.255.0   U     0      0        0 tun0
192.168.430.534 0.0.0.0         255.255.255.255 UH    0      0        0 tun0

And after invoking openconnect in combination with vpn-slice like this

Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         222.222.222.1   0.0.0.0         UG    600    0        0 wlp3s0
222.222.222.0   0.0.0.0         255.255.255.0   U     600    0        0 wlp3s0
333.333.333.333 222.222.222.1   255.255.255.255 UGH   0      0        0 wlp3s0
192.168.430.0   0.0.0.0         255.255.255.0   U     0      0        0 tun0
192.168.430.534 0.0.0.0         255.255.255.255 UH    0      0        0 tun0

It is clear that if vpn-slice is not used, openconnect explicitly states that for all addresses except specifically mentioned, traffic must go through the VPN.

Here:

0.0.0.0         0.0.0.0         0.0.0.0         U     0      0        0 tun0

Next to it, another route is immediately indicated, which needs to be used if the address that Linux is trying to access does not match any mask in the table.

0.0.0.0         222.222.222.1   0.0.0.0         UG    600    0        0 wlp3s0

It is already stated here that in such cases, traffic must route through the standard Wi-Fi adapter.

I suppose that the path for the VPN is used because it is the first one in the routing table.

Theoretically, if this default route is removed from the routing table, then in conjunction with dnsmasq, openconnect should provide proper operation.

I tried

route del default

And everything started working.

Routing requests to the mail server without vpn-slice

But I still have a mail server at address 555.555.555.555, which I also need to access through the VPN. The route to it also needs to be added manually.

ip route add 555.555.555.555 via dev tun0

And now everything is fine. So it is indeed possible to do without vpn-slice, but one has to know exactly what they are doing. I am now thinking about whether to add the removal of the default route and the addition of the mail route to the last line of the original openconnect script after connecting to the VPN, just to reduce the moving parts in my setup.

Perhaps for someone, this postscript would be enough to understand how to set up a VPN. However, while I was trying to figure out what I should do, I read quite a few such guides that worked for the author but for some reason did not work for me. I decided to add all the bits and pieces I found here. I would have been very happy to have something like this.

Source: habr.com

Buy reliable website hosting with DDoS protection, VPS VDS servers đŸ”„ Buy reliable website hosting with DDoS protection, VPS VDS servers | ProHoster