Connect to VPN inside a pipeline (CI/CD)
Tags: devops, pipeline, cicd, circleci
Published: 2020-11-03 18:21:35

For numerous reasons, some servers are protected with a VPN, but it shouldn't hold you back from using a CI/CD. In this guide (aka my first one), I'll describe how to connect to your VPN inside your pipeline. Let's get started!

# File Structure
It is always suggested to put your pipeline files inside one folder, to keep things clean. This is a file structure for CircleCI using Forticlient VPN.

    ├── .circleci
          └── config.yml  
          └── forti-vpn.sh
          └── forticlient-sslvpn_4.4.2333-1_amd64.deb 

I suggest you put most of your files needed, inside the .circleci folder, as this can keep the structure clean and put all your pipeline files hidden away from your code. 
The 'root' of your repo inside the machine is: 
> `/home/$CIRCLE_PROJECT_REPONAME/`

I'll dive into the two Forticlient files later in this post.

# VPN Setup 

It's actually easier than you might think. The build tool (CircleCI, Jenkins, Azure, Github Workflow,..) actually just utilizes by default docker images [(read more)](https://circleci.com/docs/2.0/executor-types/). Usually, the build automation tool has some agents (Linux containers) already running. But since we need some special [TUN/TAP drivers](https://en.wikipedia.org/wiki/TUN/TAP), we'll need to spin up a Linux VM ourselves.

Don't worry, you don't need anything special for it, just 1 line of code at the top of your `config.yml`.

```bash
version: 2.1
jobs:
    build:
        machine:
            image: ubuntu-1604:201903-01
```

The machine line tells the tool to spin up a specific Linux machine, because in the standard ones, the TUN/TAP driver are disabled by default.


# VPN Clients
## FortiClient
### Environment variables

Add your FortiClient host (with port), username and password as environment variables in the UI of CircleCI
* FORTI_HOST *hostname:port* 
* FORTI_USER *forticlient username*
* FORTI_PASSWORD *forticlient password*

### Install FortiClient 

At the time of writing, I wasn't able to install forticlient inside a cmd with a curl command or otherwise, so I'm just passing the installation file inside the `.circleci` folder. You can download the file from [here](https://hadler.me/linux/forticlient-sslvpn-deb-packages/). On Azure, the following cmd did work:
```bash
sudo wget https://hadler.me/files/forticlient-sslvpn_4.4.2333-1_amd64.deb
```

Now we have the installer, but we still need to create a step which installs the tool inside our Linux.

```bash
- run:
name: Install FortiClient
command: |
    mkdir -p /home/$CIRCLE_PROJECT_REPONAME/vpn/tmp
    cd /home/$CIRCLE_PROJECT_REPONAME/vpn
    sudo apt-get update
    echo "Install Expect"
    sudo apt-get install expect
    echo "Install PPP"
    sudo apt-get install ppp expect
    echo "Install FortiClient"

    sudo cp  ~/.circleci/forticlient-sslvpn_4.4.2333-1_amd64.deb .
    sudo dpkg -x forticlient-sslvpn_4.4.2333-1_amd64.deb /home/$CIRCLE_PROJECT_REPONAME/vpn
    
    sudo truncate -s 0 opt/forticlient-sslvpn/64bit/helper/License.txt
    sudo ./opt/forticlient-sslvpn/64bit/helper/setup >/dev/null < <(echo y)
```
*you don't need to change anything in this script, the $CIRCLE_PROJECT_REPONAME is a circleci env var, that automatically grabs your repo name.

### Connect to the VPN
You only need to provide your credentials as environment variables. Create this file and paste in the following [script](https://gist.github.com/WoutB/4b0706ca99fdf7b9d7def15372beb7ab#file-forti-vpn-sh) (or you can get this file)

Then, add a step to connect to the VPN using the script.

```bash
    sudo wget https://gist.githubusercontent.com/buelenw/0887937a8cffaca9c9ec84c8b4c47a06/raw/a8575708e860b33f5f51ae834ee64558d1a57d14/forti-vpn.sh
    sudo sed -i s/"<FORTI_HOST>"/$FORTI_HOST/ forti-vpn.sh
    sudo sed -i s/"<FORTI_USERNAME>"/$FORTI_USER/ forti-vpn.sh
    sudo sed -i s/"<FORTI_PASSWORD>"/$FORTI_PASSWORD/ forti-vpn.sh
    sudo chown root:root forti-vpn.sh
    sudo chmod 600 forti-vpn.sh
    sudo chmod +x forti-vpn.sh
    nohup sudo ./forti-vpn.sh &     
    sleep 10

    HOST=$SERVER
    ip=${HOST%%:*}
    ping -c 1 $ip ;  echo $?
```
*the sed command will place your credentials into the login script


*the sleep is needed for to VPN connection to establish*
After the sleep there's a check which pings the SERVER and returns 1 if the server is unreachable. This will automatically stop the CircleCI workflow.

>**Make sure to kill the VPN connection at the end of your job**
```
sudo pkill forti
```
 
This guide is working with Forti-client, but I'm pretty sure it's the same solution for all other VPN-clients (like Cisco-anyconnect and OpenVPN. It has been tested on both CircleCi & Azure DevOps.

# References
https://discuss.circleci.com/t/vpn-connection-from-build-machine/4033
https://confluence.jaytaala.com/display/TKB/Continuous+and+automated+VPN+connection+with+FortiClient+%28CLI+only%29+using+bash+and+expect+scripting
https://serverfault.com/questions/412220/fortinet-ssl-vpn-client-setup-without-gui-on-linux-centos/484988#484988?newreg=9f12134c83234e27bb2ec21784900b36
https://gist.github.com/PieterScheffers/20583b65ef171cf94db16e6c659498f3