Post-auth programming notes and examples

Introduction

You can extend OpenVPN Access Server’s built-in authentication methods using a post-auth Python3 script to add additional user connection requirements, such as MFA or other custom checks. You can also use a post-auth script to implement your own custom authentication method.

This document provides information about the different post-auth options and various post-auth scripts you can download from our site and customize for your system.

What is a post-auth script?

Post-auth means post-authentication. It uses a custom Python3 script loaded into OpenVPN Access Server that can add additional checks and challenges, such as hardware address checking, custom multi-factor authentication to Access Server, or a post-auth-script-only (PAS-only) authentication method. The PAS-only method handles authentication entirely with the custom script, which allows you to implement a new authentication method that can work side-by-side with the existing built-in methods of Access Server, allowing some users to authenticate via one method and others via another method.

Note: If you’re looking to implement a simple MFA solution such as TOTP Multi-factor Authentication in Access Server, we recommend you use the existing options in the Admin Web UI for MFA, which don't require a post-auth script. 

PAS-only custom authentication script

Using a properly configured post-auth script, you can implement an entirely new custom authentication method in Access Server. The script indicates that it doesn’t need the Access Server to handle authentication by setting the AUTH_NULL parameter to true and must then manage authentication in the script. You then set this authentication method for specific users or groups or as the default authentication method. You can use the custom authentication method against any credential or authentication backend that Access Server currently doesn’t support out-of-the-box.

For more information, refer to Creating a custom authentication system with a Python script.

Hardware address checking script

OpenVPN Access Server asks OpenVPN clients to send a parameter at connection time containing the MAC address of the primary network interface or a UUID (depending on the client software and platform used). You can load a post-auth script that requires the correct hardware address at connection time. 

By default, it supports automatic registration, where hardware addresses for first-time connections on a user account are recorded and checked on subsequent connections. It can also run in a manual mode where you must manually add approved hardware addresses on the server for users and their devices.

For more information, refer to the OpenVPN Access Server post-auth hardware address checking script.

LDAP group mapping script

You can assign users to Access Server groups so they inherit specific access rules and settings, a task you can automate using our LDAP group mapping script.

Suppose you assign users in your LDAP directory server to groups using the group membership property memberOf. In that case, you can use the memberOf property in a post-auth script when a user signs in to Access Server.

Based on rules you must define in the post-auth script, you can automatically have users in specified LDAP groups assigned to specified Access Server groups when they authenticate.

For more information, refer to the OpenVPN Access Server post-auth LDAP group mapping script.

RADIUS group mapping script

You can assign users to Access Server groups so they inherit specific access rules and settings, a task you can automate with the RADIUS group mapping script. You can also include other settings in the script based on particular RADIUS properties.

Users in a RADIUS server can be part of groups with specific properties set. The post-auth script reads and uses these properties to automatically apply group membership and other settings.

The post-auth script is primarily designed for Windows Server and mapping users to Access Server groups but may also work on other RADIUS solutions.

You can use this post-auth script to translate Active Directory groups into Access Server groups so that scripts, permissions, and IP assignments can correlate to a specific AD group, assign a static IP address to a particular user given their AD profile, and you can have AD user/group specific controls for the AS ‘admin’, ‘autologin’, ‘lzo’, ‘reroute_gw’, and ‘deny-web’ user properties. Next time they log in at the Access Server, they are automatically assigned these specific settings.

For more information, refer to OpenVPN Access Server post-auth RADIUS group mapping script.

SAML Group Mapping Script

You can assign users to Access Server groups with specific access rules and other settings inherited from the group, a task you can automate with our SAML group mapping script.

Users in the SAML IdP can be part of groups. You must configure this property on the SAML IdP; we provide guides for configuring SAML, and this property is usually the groups property you can use in the post-auth script when a user logs in at the Access Server.

Based on the rules you define in the post-auth script, you can automatically assign users in specified SAML groups to specified Access Server groups when they log in at the Access Server.

For more information, refer to OpenVPN Access Server post-auth SAML group mapping script.

Script for Duo two-factor authentication

You can integrate OpenVPN Access Server with Duo and add two-factor authentication options such as SMS, push notifications, Yubikey, etc. 

If you use PAM, RADIUS, LDAP, or local authentication, you can use the provided post-auth script to integrate Duo Security into OpenVPN Access Server.

Note: If you use SAML authentication, you must integrate Duo Security into your SAML solution rather than using the custom script.

To set this up, you download the Duo Security post-auth script for OpenVPN Access Server from GitHub, add an API key obtained from Duo Security into the script in the appropriate section, and load the script into the Access Server.

For more information, refer to Duo two-factor authentication to OpenVPN Access Server.

Installing a post-auth script

To install a post-auth script, follow this general process:

  1. Download or create the script.
  2. Modify the script as needed for your configuration.
  3. Put it somewhere on your Access Server's filesystem.
  4. Install it on Access Server (signed in with root privileges):
    cd /usr/local/openvpn_as/scripts
    ./sacli --key "auth.module.post_auth_script" --value_file="<POST_AUTH_SCRIPT_PATH_AND_FILENAME>" ConfigPut
    ./sacli start
  5. Ensure you replace <POST_AUTH_SCRIPT_PATH_AND_FILENAME> with the full path and filename where you saved the post-auth script.

We recommend you keep a copy of the script for any needed updates. It’s complicated to change it in Access Server’s configuration database. We recommend you edit the original file and load it again with the above command.

Removing an active post-auth script

You may need to remove the post-auth script from your Access Server, especially when debugging.

To remove a post-auth script, follow this general process:

  1. Sign in to your Access Server console with root privileges.
  2. Run these commands:
    cd /usr/local/openvpn_as/scripts
    ./sacli --key "auth.module.post_auth_script" ConfigDel
    ./sacli start

Will OpenVPN Inc. build a custom post-auth script for us?

No. We provide you with example post-auth scripts, as do some third parties. We don’t offer custom post-auth scripts. Use the example scripts and documentation provided to develop or modify the post-auth script using the Python3 programming language.

Post-auth documentation

Post-auth scripts allow you to manage part of the authentication programmatically. Using a post-auth script does the following:

  • Uses a script written in the Python3 programming language.
  • If AUTH_NULL is set to true, the script handles authentication (PAS-only authentication method).
  • Otherwise, Access Server handles authentication initially and then hands off to the “post_auth” function in the post-auth script for additional checks and conditions.
  • You can only load one post-auth script at a time.
  • You can combine multiple authentication methods into one post-auth script.
  • On Access Server 2.9 and older, the post-auth script doesn’t execute for bootstrap accounts.

Post-auth looks for this function:

def post_auth(authcred, attributes, authret, info):
    ...

Note: For SAML, RADIUS, PAM, LDAP, and local authentication methods, Access Server handles authentication first. The post-auth script can afterward veto the authentication. If you implement PAS-only authentication, the post-auth script handles authentication by itself.

Refer to the following sections for documentation about script parameters.

Authret — script input parameters

The parameters passed to the post-auth() script call include:

  • authcred : A dictionary containing the following items:
    • username (string) — User name of VPN client provided by end user.
    • client_ip_addr (string) — Real IP address of VPN client.
    • client_hw_addr (string, not always available) — MAC address of default gateway interface or UUID of the VPN client.
    • static_response (string, optional) — A string entered by the user in response to a custom challenge question.
  • attributes : A dictionary that can contain client-reported values:
    • client_info — A dictionary of strings provided by the client, including:
      • UV_ASCLI_VER — Version number of connecting Access Server client.
      • IV_PLAT — Client platform ('win', 'mac', or 'linux', 'ios', or 'android').
      • UV_PLAT_REL — Specific version of client platform.
      • UV_APPVER_<APP_NAME> — Version number of APP_NAME installed on client.
      • IV_HWADDR — Primary network interface MAC address or UUID.
    • vpn_auth (boolean) — True if this is a VPN authentication, false if it is another type of authentication (such as web server access).
    • reauth (boolean) — True if this is a VPN mid-session reauthentication, false if it is an initial VPN authentication, and absent for non-VPN authentications.
  • authret : a dictionary containing the authentication status, and may be modified and returned by the script.

Note: If you make authentication decisions based on username, use authret[‘user’], as that is the normalized version after going through the authentication process. The value authcred[‘username’] is what the user enters unchanged.

Authret — script output parameters

If the script returns authret unmodified, there’s no effect on the authentication process, i.e., authentication proceeds as if the script isn’t present. However, by modifying authret, the script can affect these changes in the authentication process:

  1. Failing authentication setting 'status' item to FAIL.
  2. Generate failure strings to the log file ('reason' item) or pushed to the client for display to the end user ('client_reason' item) when authentication fails.
  3. Set or change the properties of the client instance object on the server, including group, IP address, and other properties.

In detail, the authret dictionary contains the following items:

  • status (int, required) — Should be set to SUCCEED or FAIL (you can import these symbols with this statement: from pyovpn.plugin import *).
  • user (string, required) — The canonical username of the user. In some cases, this username may differ from the username in authcred. (For example, with LDAP case-insensitive matching.) When you use LDAP authentication, this is the username reported by the LDAP server, while authcred['username'] is the username entered by the user.
  • reason (string, optional) — On auth failure, this string will be output to the log file for diagnostic purposes.
  • client_reason (string, optional) — On auth failure, this string will be sent to the VPN client and will be shown to the user in an error dialog box.
  • proplist (dictionary, optional) — A list of user properties for the connecting user. In most cases, you only need to set the conn_group member, since the group can define all other properties.
  • Conn_group (string) — Designate this user as a member of the given group. Note: When setting conn_group in the script, you should generally include: GROUP_SELECT = True in the top-level, global part of your script. This tells Access Server to do late user properties lookup so that the user properties are taken from the group chosen by the post-auth script. Additionally, any user properties returned by the script in authret['proplist'] overrides those read from the user properties database.
  • Conn_ip (string: IP address) — Dynamic IP address that should be assigned to the user. Ensure this IP address exists within a group subnet; if conn_group isn’t specified, Access Server tries to derive the group by looking at the set of all groups, and finding the group for which this IP address is contained within group_subnets (only in Layer 3 mode).
  • prop_superuser (boolean) — Designate as an Access Server administrator.
  • prop_autogenerate (boolean) — Allow standard userlogin profiles.
  • prop_autologin (boolean) — Allow autologin profiles.
  • prop_deny_web (boolean) — Deny access to the client web server and XML/REST web services (but still allow VPN access).
  • prop_lzo (boolean) — Enable lzo compression (deprecated).
  • prop_reroute_gw_override (string):
    • disable — Disable reroute_gw for this client.
    • dns_only — Disable reroute_gw for this client, but still route DNS.
    • global — Use global reroute_gw setting (default).
    • prop_expire (int) — Maximum duration of non-autologin sessions (in seconds) before reauth required, 0=infinite.
    • prop_expire_halt (bool) — If true, VPN client is halted on prop_expire expiration rather than being allowed to reauth.

The info dictionary contains the following members, depending on the current auth_method:

  • auth_method (string) — Contains the auth method (local, pam, ldap, radius, saml, pas_only) and may contain special auth methods such as 'autologin' (certificate-only auth).

LDAP-specific

  • ldap_context (object) — This is a Python LDAP context object that you can use to perform additional LDAP queries. We use this in our LDAP group mapping script.
  • user_dn (string) — The LDAP distinguished name of the user that is authenticating. Access Server uses this to normalize the username to what the LDAP server reports.

RADIUS-specific

  • radius_reply (dictionary) — Attributes received from the RADIUS server as part of the successful authentication reply. We use this in our RADIUS group mapping script.

SAML-specific

  • saml_attr (dictionary) — Attributes received from the SAML server as part of the successful authentication reply. 

Return Value

The post-auth function must return either authret or (authret, proplist_save).

Returning only authret affects only the current authentication session and doesn’t store data permanently. Returning an optional proplist_save dictionary with key/value pairs allows storing data in the user properties database for later use. We use this in our hardware-address checking script.

Exceptions

If the post-auth function throws a Python exception, authentication fails, and the reason string is set to the Python error message.

Testing

To test the post-auth script, go to the /usr/local/openvpn_as/scripts directory and use the authcli tool. Load the post-auth script and then run authcli to test authentication. For example, we will test using the username 'test' on the sample post-auth script pas.py:

$ ./authcli -u test
API METHOD: authenticate
Password: <non-echo password entry>
AUTH_RETURN
  status : SUCCEED
  reason : LDAP auth succeeded on ldap://...
  user : test
  proplist : {'prop_autogenerate': 'true', 'prop_deny': 'false',
              'prop_autologin': 'true', 'conn_group': 'default',
              'type': 'user_connect', 'prop_superuser': 'false'}

Note how actions of the script set conn_group to default.

Host-checker query file

OpenVPN Connect for Windows and macOS support checking certain programs’ presence and reporting their version numbers to the server. Other OpenVPN clients do not support this feature.

To use this feature, you must first construct a host-checker query file containing information on which applications to check and load into OpenVPN Access Server. Then, when you generate connection profiles, they contain the necessary embedded instructions for the host-checking function in OpenVPN Connect. The values reported by OpenVPN Connect can then be checked in a post-auth script on the OpenVPN Access Server to determine whether the connection is allowed.

Host-checker query file format:

[PLATFORM1|'all']
NAME1=REGEX1
NAME2=REGEX2
[PLATFORM2|'all']
NAME1=REGEX1
NAME2=REGEX2

Explanation of the terms:

  • PLATFORM - This applies to either win, mac, or all (optional).
  • NAME - A short user-defined name for the application
    (only alphanumeric and underscore characters allowed).
  • REGEX - A case-insensitive regular expression matched against the full application name.

If a REGEX matches an application name, its version number is returned to the Access Server and accessible to the post-auth script via the attributes['client_info'] dictionary. For each application NAME, the version of the application is returned as UV_APPVER_<NAME> (string) in the attributes['client_info'] dictionary. If an error occurs, the UV_APPVER_<NAME> value gets set to one of the following:

  • ERR_NOT_FOUND - The application was not found.
  • ERR_NO_VERSION - The application had no version number to parse.
  • ERR_MANY_FOUND - More than one application matched.
  • ERR_REGEX - The regular expression couldn’t be parsed.

For example, the following one-line host-checker query file would return the version number of Mozilla Firefox installed on the client to the post-auth function:

FIREFOX=^mozilla firefox

For our example, we assume that the above line saves in a file called appver.txt on the OpenVPN Access Server. To load the file into the Access Server, use the following commands from /usr/local/openvpn_as/scripts. Load the host_checker file into Access Server:

./sacli --key "vpn.client.app_verify" --value_file="appver.txt" ConfigPut
./sacli start

The above commands embed appver.txt as metadata in all client profiles generated from the Access Server after this point. When a user makes a connection with these profiles, the version number of specified applications is passed to the post-auth script. In particular, if the Firefox host-checker query file shown above was used, the Firefox version number (or error string) is accessible as:

attributes['client_info']['UV_APPVER_FIREFOX']

Challenge/response in OpenVPN protocol

Authentication occurs with certificates or credentials. On top of that, you can show an additional challenge to the user when they log in at the Access Server web interface or with an OpenVPN client. The typical use case for this is multi-factor authentication. In the post-auth script, you can implement a custom challenge, such as asking for a hardware token code or a question that only an authorized user can answer.

Note: The TOTP Multi-factor Authentication functionality for a simple MFA solution is built into the Access Server. If you want a custom solution, you can do so in a post-auth script.

Dynamic protocol details

In dynamic challenge/response, the VPN client isn’t initially aware of the additional authentication requirement but learns this later. The process is as follows:

  1. The client authenticates to the server with a certificate and/or credentials.
  2. The server (soft)fails the authentication with a challenge.
  3. The client displays the challenge to the user.
  4. The user answers the challenge.
  5. The client then authenticates and sends the user response along.
  6. If the response was correct, the client is now authenticated.

You can use the dynamic protocol when you want to use a different challenge text for each login session (although the dynamic protocol works fine for static challenge text as well). It is also useful when your VPN clients are already installed and provisioned with connection profiles, and you wish to implement a challenge/response afterward.

Note: On command-line clients such as on Linux, you may need to add the auth-retry-interact directive to have the client prompt the user to enter the response to a dynamic challenge.

Static protocol details

In static challenge/response, the VPN client is aware of the additional authentication requirement because the connection profile has the static-challenge directive embedded. The process is as follows:

  1. The client displays the challenge to the user.
  2. The user answers the challenge.
  3. The client then authenticates and sends the user response along.
  4. If the response was correct, the client is now authenticated.

Use the static protocol when the challenge text is constant for all login sessions. The static protocol is generally only supported for VPN login, and other types of sessions (such as web sessions) support only the dynamic protocol. For this reason, post-auth scripts that support the static protocol must be able to fall back to the dynamic protocol if authcred['static_response'] is undefined. The pascrs.py script demonstrates this. The static protocol is more efficient than the dynamic protocol because the authentication credentials and challenge-response information can be gathered in a single step and then delivered to the server in one transaction.