# 4.3.4.2 VPN Recipes

We advise that OpenCRVS be hosted behind a government VPN and that all staff that access OpenCRVS use a VPN Client to connect before browsing to OpenCRVS. This keeps OpenCRVS from being on the public internet. Mobile device management (MDM) solutions can be used to deploy and configure VPN clients on staff devices to mitigate this challenge at scale. The Digital Public Good, "DHIS2" has this excellent documentation on [MDM](https://docs.dhis2.org/en/full/implement/mobile-device-management-guidelines.html).

{% hint style="info" %}
VPN is government wide networking security infrastructure, and its selection and configuration is outside the scope of the OpenCRVS platform.

This page documents "recipes". Code snippets that we have used in the past to **connect** to different kinds of VPN programmatically from Github Actions - where the environment **"provision"** user is a virtual staff member with VPN client access details. Your networking team may be required to assist you debug the provision user's VPN connection depending on your unique government network requirements.
{% endhint %}

{% hint style="warning" %}
OUTBOUND

When connected to your VPN, users should have outbound internet access. But in some cases, governments restrict this. Refer to the list of required domains that must be whitelisted in section [4.3.1 Verify servers](https://documentation.opencrvs.org/setup/3.-installation/3.3-set-up-a-server-hosted-environment/3.3.1-provision-your-server-nodes-with-ssh-access)
{% endhint %}

{% hint style="warning" %}
INBOUND

OpenCRVS uses Github Actions to connect through your VPN. Github publishes the following IP addresses which you should ensure can access as a VPN Client.

<https://api.github.com/meta>

The URL to your country logo which appears in HTML emails should be accessible from the public internet.

```
https://countryconfig.<production-domain>/content/country-logo
https://countryconfig.<staging-domain>/content/country-logo
```

{% endhint %}

The following Github Action [workflows](https://github.com/opencrvs/opencrvs-countryconfig/tree/master/.github/workflows) require to be amended for the "provision" user to connect to your VPN:

.github/workflows/clear-environment.yml

.github/workflows/deploy-prod.yml

.github/workflows/deploy.yml

.github/workflows/provision.yml

.github/workflows/reset-2fa.yml

.github/workflows/seed-data.yml

Place your required snippets for the steps you need at the beginning of the workflows, before any connection attempt is made, such as immediately after the repository is cloned. E.G. [here](https://github.com/opencrvs/opencrvs-countryconfig/blob/cde5c29849189d9e9fdef7b3a93f393e9af05150/.github/workflows/deploy.yml#L56).

### Openconnect compatible VPN

The following VPN providers are compatible with [openconnect](https://www.infradead.org/openconnect/) which allows you to programatically connect in the Github Action.

{% hint style="info" %}
openconnect compatible VPNs are:

* [Cisco AnyConnect](https://www.infradead.org/openconnect/anyconnect.html) (--protocol=anyconnect)
* [Array Networks SSL VPN](https://www.infradead.org/openconnect/array.html) (--protocol=array)
* [Juniper SSL VPN](https://www.infradead.org/openconnect/juniper.html) (--protocol=nc)
* [Pulse Connect Secure](https://www.infradead.org/openconnect/pulse.html) (--protocol=pulse)
* [Palo Alto Networks GlobalProtect SSL VPN](https://www.infradead.org/openconnect/globalprotect.html) (--protocol=gp)
* [F5 Big-IP SSL VPN](https://www.infradead.org/openconnect/f5.html) (--protocol=f5)
* [Fortinet Fortigate SSL VPN](https://www.infradead.org/openconnect/fortinet.html) (--protocol=fortinet)
  {% endhint %}

The snippet below is a basic example of how to use openconnect to access a compatible VPN.

To use this snippet, you must also manually create 2 additional environment secrets in Github that are not automatically generated by our Create Github Environment script as they are custom to this approach.

<table><thead><tr><th>Secret name</th><th>Notes</th></tr></thead><tbody><tr><td><pre><code>VPN_PROTOCOL
</code></pre></td><td></td></tr><tr><td></td><td>A string compatible with openconnect to understand which VPN provider you are using. E.G. <code>"fortinet"</code></td></tr><tr><td><pre><code>VPN_SERVERCERT
</code></pre></td><td></td></tr><tr><td></td><td>Refer to note below the snippet for an explanation regarding this secret.</td></tr></tbody></table>

Github Action workflow snippet:

```

- name: Update apt
  run: sudo apt update

- name: Install openconnect
  run: sudo apt install -y openconnect

- name: Connect to VPN
  run: |
    echo "${{ secrets.VPN_PWD }}" | sudo openconnect --syslog -u ${{ secrets.VPN_USER }} --passwd-on-stdin --protocol=${{ secrets.VPN_PROTOCOL }} ${{ secrets.VPN_HOST }}:${{ secrets.VPN_PORT }} --servercert ${{ secrets.VPN_SERVERCERT }} --background -v
    sleep 3

- name: Test if IP is reachable
  run: |
    ping -c4 ${{ vars.SSH_HOST }}

```

```
VPN_SERVERCERT
```

When using `openconnect`, you will need to retrieve the server's certificate (often referred to as `--servercert`) to ensure that you're connecting to the correct VPN server.

To retrieve the existing `--servercert` (the server's SSL certificate) as an OpenConnect client, you can follow these steps:

#### 1. **Use OpenConnect with Verbose Output**

Run `openconnect` with the `--verbose` flag to display detailed information about the connection process, including the server's certificate.

```bash
sudo openconnect --user=$VPN_USER --passwd-on-stdin --verbose $VPN_HOST_ADDRESS
```

Enter your VPN password when prompted. During the connection process, OpenConnect will display details about the server certificate, including the fingerprint.

#### 2. **Manually Extract the Server Certificate**

If you need the full certificate and not just the fingerprint, you can manually extract it using OpenSSL or `openssl s_client`.

```bash
openssl s_client -connect $VPN_HOST_ADDRESS:443 </dev/null 2>/dev/null | openssl x509 -outform PEM > servercert.pem
```

This command connects to the VPN server on port 443, retrieves the certificate, and saves it in PEM format to a file named `servercert.pem`.

#### 3. **Find the Certificate Fingerprint**

If you only need the fingerprint (which is what `--servercert` expects), you can extract it using:

```bash
openssl x509 -in servercert.pem -noout -fingerprint -sha256
```

This command will output the SHA256 fingerprint of the server certificate, which you can use directly with the `--servercert` option in OpenConnect

### Common learnings regarding IP ranges used by Github

You may encounter that the [Github IP ranges](https://docs.github.com/en/actions/using-github-hosted-runners/using-github-hosted-runners/about-github-hosted-runners#ip-addresses-of-github-hosted-runners) dynamically created for the worker conflict with internal IP ranges that you are using in your network.

Should you be unable to connect due to this issue you can make use of split-tunelling by creating a virtual machine in the Github runner that uses the range you need.

Here is a snippet using openconnect that does this:

```
- name: Update apt
  run: sudo apt update

- name: Install openconnect
  run: sudo apt install -y openconnect

- name: Install virtualenv
  run: |
    sudo apt install virtualenvwrapper
    sudo apt install virtualenv
    virtualenv vpn-slice
    source vpn-slice/bin/activate
    pip3 install https://github.com/dlenski/vpn-slice/archive/master.zip

- name: Connect to VPN
  run: |
    echo "${{ secrets.VPN_PWD }}" | sudo openconnect --syslog -u ${{ secrets.VPN_USER }} --passwd-on-stdin --protocol=${{ secrets.VPN_PROTOCOL }} ${{ secrets.VPN_HOST }}:${{ secrets.VPN_PORT }} --servercert ${{ secrets.VPN_SERVERCERT }} --background -v -s 'vpn-slice/bin/vpn-slice <ENTER YOUR IP RANGE HERE>'
    sleep 3
    ip_addr=$(ip -f inet addr show tun0 |sed -En -e 's/.*inet ([0-9.]+).*/\1/p')
    sudo ip route add <ENTER YOUR IP RANGE HERE> via $ip_addr dev tun0

- name: Test if IP is reachable
  run: |
    ping -c4 ${{ vars.SSH_HOST }}

```

Replace \<ENTER YOUR IP RANGE HERE> appropriately in the snippet above.

### Wireguard VPN

If Wireguard is your chosen VPN solution each user receives a config file to connect. The config fil contents should be added as a secret to your environment like this:

| Secret name      | Notes                               |
| ---------------- | ----------------------------------- |
| WG\_CONFIG\_FILE | Pasted content from the config file |

The following snippet accesses the config file and connects using [this](https://github.com/niklaskeerl/easy-wireguard-action) Github Action library.

```
- name: Read wireguard file from secret
  id: read-wg-file
  run: echo "##[set-output name=wg_file;]${{ secrets.WG_CONFIG_FILE }}"

- name: Set up wireguard connection
  if: ${{ steps.read-wg-file.outputs.wg_file != '' }}
  uses: niklaskeerl/easy-wireguard-action@v2
  with:
    WG_CONFIG_FILE: ${{ secrets.WG_CONFIG_FILE }}
```

### Jump / Bastion server option

An approach which is debatably less secure, is to expose a Jump or Bastion server outside of the VPN that permits SSH access from a list of static IP addresses and the Github IPs. The jump server acts as an intermediary to pass your SSH connection through to the VPN secured network.

This approach requires significant effort in security hardening the jump server and SSH connection processes.

{% hint style="info" %}
The VPN company, "Tailscale" has [this](https://tailscale.com/learn/access-remote-server-jump-host) great explanation about what a jump host is and how to safely configure one.
{% endhint %}

Please note that when using a jump server, OpenCRVS Github Actions connect to your servers like this requiring further editing of your workflows.

Using a jump server should be considered a last resort.

```
ssh -J jump@<your vpn server> provision@<your server>
```
