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.

VPN is government wide networking security infrastructure, and its selectionand 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.

OUTBOUND

When connected to your VPN, users should have outbound internet access. But in some cases, governments restrict this. The following domains must be outbound whitelisted for OpenCRVS to function:

archive.ubuntu.com
changelogs.ubuntu.com
hub.docker.com
download.docker.com
sentry.io
fonts.gstatic.com
storage.googleapis.com
fonts.googleapis.com 
github.com
acme-v02.api.letsencrypt.org
registry.npmjs.org
registry.yarnpkg.com
eu.ui-avatars.com
<any domains associated with SMTP / SMS services>

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

The following Github Action 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.

Openconnect compatible VPN

The following VPN providers are compatible with openconnect which allows you to programatically connect in the Github Action.

openconnect compatible VPNs are:

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.

Secret nameNotes
VPN_PROTOCOL

A string compatible with openconnect to understand which VPN provider you are using. E.G. "fortinet"

VPN_SERVERCERT

Refer to note below the snippet for an explanation regarding this secret.

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.

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.

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:

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 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 nameNotes

WG_CONFIG_FILE

Pasted content from the config file

The following snippet accesses the config file and connects using this 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.

The VPN company, "Tailscale" has this great explanation about what a jump host is and how to safely configure one.

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>

Last updated