What do you think? Discuss, post comments, or ask questions at the end of this article [More about me]

Problem

Similar to this case, I needed to maintain a (near) continuous VPN connection to a server (server 1) from my server (server 2) that was running a Tomcat web-app (on Ubuntu Server 16.04).  Server 1 was part of a network that provided secured VPN access using a FortiNet VPN gateway.

The solution below uses a bash script (which creates an external expect script) to automate the connection, and re-connection (if VPN connection drops), of the VPN connection to server 1.

Solution

Installing Forticlient and dependencies

You'll need to install an appropriate Forticlient SSLVPN package.  I don't know why, but FortiNet makes it unusually difficult to find the Linux client package with the forticlientsslvpn_cli script that is required.  In any case, I found a package (used version 4.4.23330-1) appropriate for my Ubuntu server (running 16.04) here.  Once downloaded you can install it with:

sudo apt-get install <DOWNLOAD_LOCATION>/forticlient-sslvpn_4.4.2333-1_amd64.deb

You'll also need the ppp and expect packages installed.  If you're using Ubuntu, simply do:

sudo apt-get install ppp expect

Setup

Forticlient should be installed to /opt/forticlient-sslvpn/64bit/.  Apparently you need to run the setup script first:

sudo /opt/forticlient-sslvpn/64bit/helper/setup

Scroll through the legalese and then accept (type Y).  We should have everything we need to now automate connecting.

Bash script (with embedded expect script) to execute (and maintain FortiClient VPN connection)

Please note that the below approach stores a vpn password in clear text in the script file, and as such is a potential security risk. The script should be locked down to stop users without authorisation from viewing its contents. Hence, this approach may only be appropriate for a server/system that is strictly managed or not accessed by other users.

We'll now create a bash script to handle the connection (and automatic reconnection).  The script below does several things, including creating and executing an external expect script.  This expect script automates and emulates a bit of human interaction that forticlientsslvpn_cli requires.

The brains of the embedded expect script was written by mgeeky and his original script can be found here.  Many thanks to mgeeky.

#!/bin/bash

# init only
CONNECT_PID=""
RUNNING=""

# Provide required parameters
FORTICLIENT_PATH="/opt/forticlient-sslvpn/64bit/forticlientsslvpn_cli"
VPN_HOST="<HOST:PORT>"
VPN_USER="<USER_NAME>"
VPN_PASS="<PASSWORD>"

# Checks whether vpn is connected
function checkConnect {
    ps -p $CONNECT_PID &> /dev/null
    RUNNING=$?
}

# Initiates connection
function startConnect {

    # start vpn connection and grab its pid (expect script returns spawned vpn conn pid)
    CONNECT_PID="connect"
    eval $CONNECT_PID
}

# Creates an expect script to complete automated vpn connection
function connect {

    # write expect script to tmp location
    cat <<-EOF > /tmp/expect
        #!/usr/bin/expect -f
        match_max 1000000
        set timeout -1
        spawn $FORTICLIENT_PATH --server $VPN_HOST --vpnuser $VPN_USER --keepalive
        puts [exp_pid]
        expect "Password for VPN:"
        send -- "$VPN_PASS"
        send -- "\r"

        expect "Would you like to connect to this server? (Y/N)"
        send -- "Y"
        send -- "\r"

        expect "Clean up..."
		close
		EOF
    
    #IMPORTANT!: the "EOF" just above must be preceded by a TAB character (not spaces)

    # lock down and execute expect script
    chmod 500 /tmp/expect
    /usr/bin/expect -f /tmp/expect

    # when expect script is finished (closes) clean up
    rm -f /tmp/expect
}

startConnect

# note this will not continuously loop, it will only loop if the spawned vpn connection drops
# i.e. will only hit this code when expect closes
while true
do
    # sleep a bit of time (why not, everyone needs sleep)
    sleep 1
    checkConnect
    [ $RUNNING -ne 0 ] && startConnect
done

NOTE: the "EOF" on line 47 MUST be preceded by a single TAB character (not spaces), otherwise the script will fail. If you are copy/pasting the script above into your favourite Linux text editor, please remove the preceding spaces and replace with a tab character.

Securing and executing

You'll note that the above script requires the entering of a username and password.  At the very least, let's lock down this script to root (only allow root to read/write contents):

Assume our script is called forti-vpn.sh and is located in our home folder

sudo chown root:root ~/forti-vpn.sh
sudo chmod 600 ~/forti-vpn.sh
sudo chmod +x ~/forti-vpn.sh

To execute the script, change to the folder where it resides and run

sudo ./forti-vpn.sh &

which will execute the script in the background.

Stopping the script (and killing the vpn connection)

To stop the script you'll need to find it's pid's and kill them.  If you named your script forti-vpn.sh, then you can do this easily with pkill.  For example, executing

sudo pkill forti

will kill all processes with the name "forti" in it (which include the script and spawned forticlient processes).

Alternatively, you can use htop. You should have this installed (if not, do sudo apt-get install htop).  With htop you can then locate the sudo forti-vpn.sh process and select them (with space-bar) and then hit F9 (kill) and then 9 (sigkill) and hit enter.

References

  1. https://hadler.me/linux/forticlient-sslvpn-deb-packages/
  2. https://gist.github.com/mgeeky/8afc0e32b8b97fd6f96fce6098615a93