Setting up an OpenVPN server with Okta Single Sign-on Web Authentication and Viscosity
After setting up your own OpenVPN server, you may want to enhance it's security. One way to do that and streamline your authentication process is to use Single Sign-in or Single Sign-On (SSO), also sometimes referred to as SAML (This is an SSO protocol). This adds another security measure to prevent unwanted users connecting to your server while at the same time integrating with your existing user, identity or client management system or authentication process.
This guide provides an example of how this might work with Okta, a popular cloud-based identity platform. This example's basics translate to most Identity Management Systems (IDMS) with just changes to how you communicate with your IDMS of choice.
Please note this example is designed to show the basics of how an IDM or SSO system can integrate with OpenVPN on the server side, it is not designed to be used on it's own as-is.
Preparation
For this guide, we assume:
- You have an Okta account (this guide will work with Okta's free trial)
- You have already installed the latest LTS version of Ubuntu (22.04 at time of writing)
- You have root access to this installation
- You have public access to this System on port 80 and 443 for HTTP & HTTPS access, and a DNS A-NAME pointing to it (for example, openvpnsso.server.com)
- You already have a copy of Viscosity installed on your client device and already setup for this server
Documentation for Okta can be found at https://help.okta.com/en-us/content/index.htm
Further Okta example can be found on their GitHub page at https://github.com/okta
This guide should only be used as an example for setting up SSO on your server. The provided Python Flask website is designed as an example only and is not intended for production use.
If you are starting from scratch with a fresh Ubuntu 22.04 install, this process from here to finishing should only take about 20 minutes.
Okta Setup
First, we need to configure Okta for the new website that will handle communication between OpenVPN and Okta.
- Login to Okta on your admin account
- On the left expand the menu, and go to Applications > Applications
- Click Create App Integration
- Select ODIC - OpenID Connect, then Web Application and click Next
- Give your App integration name a name you'll recognise like 'My OpenVPN Server'
- Ensure Authorization Code is the only option checked under 'Client acting on behalf of a user' under 'Grant type'
- Replacing '<yourserver.com>' in the following, set your Sign-in redirect URIs to
https://<yourserver.com>/authorization-code/callback
- Optionally, again replacing '<yourserver.com>' in the following, set your Sign-out redirect URIs to
https://<yourserver.com>/logout
- Set Controlled access to your choice, for testing purposes if this is a trial account, just select Allow everyone in your organization to access
- Click Save
After saving, the page will reload. Either keep this page open, or take a note now of the Client ID, Client secret, and Okta domain, we will need these later.
Server Setup
Next, we need to setup the server. In summary, we need an OpenVPN server ready to go, to install nginx as a proxy for the Python Flask application, make some small firewall changes, install an SSL certificate, configure the flask application and make some small changes to OpenVPN.
Server Preparation
First, login to your server via SSH or open a Terminal, and run the following to make sure everything is up to date
sudo apt update
sudo apt -y upgrade
OpenVPN Server Setup
First we will need an OpenVPN Server ready to go. If you don't already have one on this server, follow the Setting up an OpenVPN server with Ubuntu and Viscosity.
Once setup, ensure that you can connect.
Next, we need to add a few lines to the OpenVPN Server config:
- Edit the configuration
sudo nano /etc/openvpn/server.conf
- At the end of the file, add the following:
management 127.0.0.1 50123 auth-user-pass-optional management-client-auth
- Press Ctrl+X, to exit, Y to save, then Enter to confirm the path
- Restart the server with
sudo systemctl restart openvpn@server
If you try to connect now, the connection should eventually fail with an authentication failed message.
Notes:
If you have an existing server that has any authentication scripts or plugins, these will need to be removed as SSO will replace them. Okta can be configured with 2FA options though we won't cover them in this guide, consult Okta's documentation.
Web Server Setup
Firewall
If you followed the Setting up an OpenVPN server with Ubuntu and Viscosity guide, first we need to open the firewall to allow HTTP & HTTPS traffic, run the following:
-
sudo ufw allow http
-
sudo ufw allow https
-
sudo ufw reload
Install ningx
Next we need to install nginx. nginx is a HTTP and reverse proxy server which will serve components of our web application out and enable the use of features like TLS/SSL. Do the following on your Ubuntu Server:
- Run
sudo apt -y install nginx
- Edit
sudo nano /etc/nginx/sites-enabled/default
- Scroll down to the line
server_name _;
, and replace the underscore (_) with your server DNS name, for example myserver.com, so it looks like this -server_name myserver.com;
- Press Ctrl+X, to exit, Y to save, then Enter to confirm the path
- Reload nginx with
sudo nginx -s reload
Setup Let's Encrypt
If you already have an SSL certificate for this section, you can skip to the next section. Otherwise, Let's Encrypt and Certbot make getting an SSL certificate simple.
- Run
sudo apt install -y certbot python3-certbot-nginx
- Run the following, replacing myserver.com with your FQDN, and follow the prompts
sudo certbot --nginx -d myserver.com
. You can specify extra domains if you require them, for examplesudo certbot --nginx -d myserver.com -d www.myserver.com -d sso.myserver.com
. - It may take a few minutes for certbot to respond after answering prompts
Setup nginx
Now you have an SSL certificate, we can finish the nginx setup.
- Edit
sudo nano /etc/nginx/sites-enabled/default
- Remove the following section:
location / { # First attempt to serve request as file, then # as directory, then fall back to displaying a 404. try_files $uri $uri/ =404; }
- Scroll down to after the lines
# managed by Certbot
, but before the}
, and paste in the following:
real_ip_header X-Real-IP; real_ip_recursive on; proxy_connect_timeout 600; proxy_send_timeout 600; proxy_read_timeout 600; send_timeout 600; location / { try_files $uri @proxy; } location @proxy { proxy_pass http://127.0.0.1:8080; proxy_pass_header Server; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_pass_header Server; proxy_connect_timeout 3s; proxy_read_timeout 10s; }
- Press Ctrl+X, to exit, Y to save, then Enter to confirm the path
- Reload nginx with
sudo nginx -s reload
Web App Install
Now we can install the webapp and start it. We'll create a user to run the web application as it creates a bit more security. Run the following:
-
sudo apt install -y python3-pip
Install python pip so we can install Flask dependencies -
sudo adduser --system --no-create-home --group ssoappuser
Create a user to run the webapp as -
cd /home
-
sudo mkdir ssoapp
-
sudo chown ssoappuser:ssoappuser ssoapp
-
cd ssoapp
-
sudo git clone https://github.com/thesparklabs/openvpn-okta-sso-example.git
Download the web application from GitHub -
cd openvpn-okta-sso-example
-
sudo python3 -m pip install -r requirements.txt
Install dependencies for the web application -
sudo cp ssoapp.service /lib/systemd/system/ssoapp.service
Install the service file -
sudo chown root:root /lib/systemd/system/ssoapp.service
-
sudo systemctl daemon-reload
Web App Setup
Finally, we just need some quick setup work for the web application to work and talk to Okta. This is where you will need Client ID, Client secret, and Okta domain we gathered setting up Okta earlier.
- Make a copy of the secrets template with
sudo cp client_secrets.json.dist client_secrets.json
- Create a random secret with the following and copy it
openssl rand -hex 20
- Edit the config with
sudo nano client_secrets.json
- Replace {{THIS_IS_A_SECRET}} with the random string we generated two steps ago
- Replace {{OKTA_DOMAIN}} in auth_uri, issuer, token_uri and userinfo_uri with your Okta domain
- Replace {{CLIENT_ID}} with your Client ID
- Replace {{CLIENT_SECRET]] with your Client secret
- Replace {{YOUR_DOMAIN}} with your server address, e.g. myserver.com
- Press Ctrl+X, to exit, Y to save, then Enter to confirm the path
- Reload nginx with
sudo nginx -s reload
We can now enable and start the application service with
- Enable the service to start when the system does
sudo systemctl enable ssoapp
- Start the service
sudo systemctl start ssoapp
Client Setup
If you have followed this guide through including using our Setting up an OpenVPN server with Ubuntu and Viscosity guide, there is nothing you need to do, simply connect with Viscosity.
If you have modified an existing server, the only change you will need to make is to turn off user/password authentication if it is on. To do this, edit the connection, go to Authentication and uncheck "User Username/Password authentication", Save the connection and connect.
Auth Tokens
OpenVPN auth-gen-token
The example application includes support for auth-gen-token. As we are not using username/password via OpenVPN, this needs to be handled via the management interface. To enable it, simply add auth-gen-token 0 external-auth
to your server configuration.
Okta Token Refresh
The above example can be extended to use Okta tokens instead of OpenVPN's auth-tokens for reauthentication. We will not provide an example of this as OpenVPN's built in support will cover the vast majority of setups.
However, if you do wish to use Okta instead, here are the main things to keep in mind:
- You will need to modify the clientAllow function reply to push your auth-token.
- You will need to modify the clientReauth function to take the password from the client environment that is passed via the management interface, follow Oktas documentation to refresh this token, then send a client-auth reply similar to clientAllow including the refreshed token.
- You will need to modify the server and client configuration to ensure that the reneg-sec is less than the expiry of the Okta tokens