Skip to main content

Post-Authentication Python Script Guide

Post-authentication (post-auth) scripts enable programmatic management of VPN client authentication after the initial process. Using the parameters documented here, you can write custom Python scripts to enhance Access Server's authentication workflows.

Important

OpenVPN Inc. provides examples of post-auth scripts, but we don't offer custom ones. Use the example scripts and documentation provided to develop or modify the post-auth script using the Python3 programming language.

Overview

Caution

If you’re looking to implement a simple MFA solution such as TOTP MFA 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.

The post-auth function

The post_auth function is the core of the post-authentication script and receives the following parameters:

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

Where the parameters are:

  • authcred: Contains the client's authentication details.

  • attributes: Holds client-reported information.

  • authret: The status and result of authentication, which the script can modify.

  • info: Additional context provided based on the authentication method.

authret — authentication return value

authret is a dictionary passed to and from the post_auth function in the post-authentication script. It contains the authentication result and can be modified to control the outcome of the authentication process. The values within authret can be adjusted by your script to allow or reject the user, set user properties, or modify other aspects of the authentication flow.

When the script is executed, authret contains the authentication status and various parameters that the script can modify based on the needs of your authentication logic. If you change any of these values, it directly influences the authentication flow.

How authret controls authentication

The authret dictionary is key in managing the authentication process within the post-auth script. It's passed to the script as part of the authentication process and contains important information about the current user’s authentication state. By modifying the values within authret, you can control the outcome of the authentication process, set user properties, and enforce specific conditions.

Use the script to determine whether authentication succeeds or fails by modifying the status field within authret. You can also modify other fields to manage user-specific configurations like group assignments, static IP addresses, or permission settings.

This is a summary of how it works:

  1. Authentication Status (status): The status field within authret determines whether the authentication attempt will succeed or fail. By setting this value to 0 (SUCCEED), the authentication will pass, allowing the user to connect. Setting it to 1 (FAIL) will reject the authentication attempt. If you don’t modify the status, the authentication process proceeds as usual.

  2. Group Assignment (conn_group): The conn_group field designates the user to a specific group. Typically, user properties inherited from the group are retrieved before the post-auth script runs. If you change the user's group by modifying conn_group within the script, those previously retrieved properties may be outdated. After the script completes, you can optionally set GROUP_SELECT = True to instruct Access Server to look up the group-inherited properties. Note that any user properties set in the script will still take precedence.

  3. IP Assignment (conn_ip): The conn_ip field assigns a static IP address to the user. If you're assigning a static IP, ensure the IP falls within a group subnet. Access Server will try to assign the IP from the global static pool if no group is assigned. This ensures that the user receives a consistent IP address for their session.

  4. Failure Reason (reason and client_reason): The reason field is used to log the failure reason in the server logs. If authentication fails, you can use the client_reason field to send an error message back to the client, which will be shown to the user in the client application. These fields help diagnose why the authentication failed.

  5. Persisting Changes (proplist): The proplist dictionary holds various user properties that can be set or modified during authentication. You can customize the user's permissions and settings by changing values in this dictionary, such as prop_superuser, prop_autogenerate, or prop_autologin. These properties can be returned in authret to affect the current session.

Post-auth script parameters (input)

authcred

A dictionary containing the following details about the authentication request:

username

(string) User's username as provided by the end user.

client_ip_addr

(string) Real IP address of the VPN client.

client_hw_addr

(string, optional) MAC address of the VPN client.

static_response

(string, optional) User's response to a custom challenge.

attributes

A dictionary that contains client-reported values, including:

client_info

A dictionary of strings provided by the client, including:

UV_ASCLI_VER

(string) The version number of the connecting Access Server client.

IV_PLAT

(string) Client platform (winmaclinuxiosandroid).

UV_PLAT_REL

(string) Specific version of client platform.

UV_APPVER_<APP_NAME>

(string) Version number of a specified app installed on the client.

IV_HWADDR

(string) Primary network interface MAC address.

UV_UUID

(string) Unique identifier for the VPN client device.

vpn_auth

(boolean) True if this is VPN authentication, False if non-VPN (e.g., web access).

reauth

(boolean) True if this is a reauthentication, False for initial VPN authentication.

authret

A dictionary that contains the authentication status and may be modified to influence the authentication process.

Authret output parameters

status

(int, required) Defines the authentication result: 0 = success, 1 = failure, 5 = MFA required.

user

(string, required) The canonical username of the user after authentication. This may differ from the authcred['username'].

reason

(string, optional) Message output to the log file when authentication fails.

client_reason

(string, optional) Message shown to the user if authentication fails.

proplist

(optional) A dictionary of properties for the connecting user, including:

conn_group

(string) Group designation for the user.

conn_ip

(string) Static IP address assigned to the user.

prop_superuser

(boolean) Assigns admin privileges to the user.

prop_autogenerate

(boolean) If True, allows standard user login profiles.

prop_autologin

(boolean) If True, allows autologin profiles.

prop_deny_web

(boolean) Denies access to the web server and XML/REST web services, but still allows VPN access.

prop_lzo

(boolean) Enables LZO compression (deprecated).

prop_reroute_gw_override

(string) Overrides gateway routing settings for the user, with possible values of disable, dns_only, or global.

  • 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).

Additional information

info

A dictionary that contains the following members, depending on the current auth_method:

auth_method

(string) Specifies the authentication method, such as local, pam, ldap, radius, saml, or pas_only. May contain special authentication methods such as 'autologin' (certificate-only authentication).

LDAP specific:

ldap_context

(object) A Python LDAP context object that can be used for additional LDAP queries. We use this in our LDAP group mapping script.

user_dn

(string) The LDAP distinguished name of the user authenticating, used to normalize the username to what the LDAP server reports.

RADIUS specific:

radius_reply

(dictionary) Attributes received from the RADIUS server in the successful authentication reply. We use this in our RADIUS group mapping script.

SAML specific:

saml_attr

(dictionary) Attributes received from the SAML server in the successful authentication reply.

Writing the post-auth script

In this section, we'll walk through creating a custom post-auth script. You'll find two basic examples. The first example demonstrates assigning a user a group and static IP address. The second example shows how to limit VPN access to a user in a specified group.

Example 1

This post-auth script provides a simple example of how post-authentication actions can be used to control group assignments and IP addresses based on user identity. Below is the example script, followed by descriptions of what it does.

from pyovpn.plugin import *

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

    # This checks if the user is 'brandonopenvpn'
    if authcred['username'] == 'brandonopenvpn': 
        authret['conn_group'] = 'VPN_USERS' # Assign user to VPN_USERS group
        authret['conn_ip'] = '192.0.2.15'   # Assign a static IP address
        authret['status'] = SUCCEED

    # If the user is not 'brandonopenvpn',
    # allow the connection with no extra actions
    else:
        authret['conn_group'] = 'default_group'
    
    return authret

This script assigns the user 'brandonopenvpn' to the 'VPN_USERS' group and gives them a static IP address (192.0.2.15). All other users are assigned to the 'default_group' without specific IP assignments. The script ensures the status is set to SUCCEED for 'brandonopenvpn,' while other users proceed through normal authentication.

  • The script imports the necessary functions from the pyovpn.plugin module to interface with Access Server's authentication mechanisms.

    from pyovpn.plugin import *
  • Then it defines the post_auth function, which takes four parameters:

    • authcred: Contains the credentials and details about the authenticated user.

    • attributes: Contains client-reported values such as platform and application details.

    • authret: The dictionary used to modify the application process, such as setting the group and IP address for this example.

    • info: Contains additional information, like authentication method.

    def post_auth(authcred, attributes, authret, info):
    
  • Next, it checks if the user is 'brandonopenvpn' and, if so, takes actions to assign the user to the 'VPN_USERS' group (by modifying authcred['username']) with the static IP address, 192.0.2.15 (by modifying authret[conn_ip']). And the authentication is successful.

    if authcred['username'] == 'brandonopenvpn': 
        authret['conn_group'] = 'VPN_USERS'  # Assign user to VPN_USERS group
        authret['conn_ip'] = '192.0.2.15'    # Assign a static IP address
        authret['status'] = SUCCEED          # Mark authentication as successful
  • For all other users, after authenticating, the script assigns them to the default group, 'default_group', without setting a static IP address.

    else:
        authret['conn_group'] = 'default_group'  # Assign non-'brandonopenvpn' users to the default group
    
  • Finally, the script returns the modified authret dictionary, which Access Server uses to apply the specific actions (assign the group and static IP).

    return authret

Example 2

This second post-auth script example demonstrates a simple approach to restricting access based on group membership.

from pyovpn.plugin import *

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

    # get user's property list, or create it if absent
    proplist = authret.setdefault('proplist', {})

    # get group name for the user
    conn_group_save = proplist.get('conn_group') # saved group

    # This checks if user is member of 'VPN_USERS' group
    if conn_group_save == "VPN_USERS":
        authret['status'] = SUCCEED

    # If the user is not in the group, fail authentication
    else:
        authret['status'] = FAIL  # Fail the authentication
        authret['reason'] = 'User not in the required group'

    return authret

The script checks for the user's saved group in the proplist dictionary and grants access if they are in the 'VPN_USERS' group. Any other user can't authenticate and thus can't make a VPN connection.

  • The script imports the necessary functions from the pyovpn.plugin module to interface with Access Server's authentication mechanisms.

    from pyovpn.plugin import *
  • Then it defines the post_auth function, which takes four parameters:

    • authcred: Contains the credentials and details about the authenticated user.

    • attributes: Contains client-reported values such as platform and application details.

    • authret: The dictionary used to modify the application process, such as setting the group and IP address for this example.

    • info: Contains additional information, like authentication method.

    def post_auth(authcred, attributes, authret, info):
  • Next, it retrieves or initializes the proplist dictionary containing user properties. If it's not present, the script will create it.

    proplist = authret.setdefault('proplist', {})
    
  • After, it checks the saved group name (conn_group_save) for the user by looking up the value from the proplist dictionary.

    conn_group_save = proplist.get('conn_group')  # saved group
    
  • If that group name is 'VPN_USERS', the script sets the status to SUCCEED, allowing authentication to proceed. If they're not in the group, the script sets the status to FAIL, and a reason ('User not in the required group') is added to the authentication failure.

    if conn_group_save == "VPN_USERS":
        authret['status'] = SUCCEED  # Allow access for VPN_USERS group
    else:
        authret['status'] = FAIL  # Fail the authentication
        authret['reason'] = 'User not in the required group'  # Reason for failure
  • Finally, the script returns the modified authret dictionary, which Access Server uses to apply the specific actions (assign the group and static IP).

    return authret

To test one of these post-auth scripts, follow these steps:

  1. Ensure your script is installed properly in the appropriate Access Server script directory.

  2. Connect to your console and get root privileges.

  3. Use the authcli tool to simulate authentication:

    ./authcli -u <username>
    
  4. Enter the password when prompted.

Next, follow the steps for the example script you're testing.

When testing example 1, the expected output for 'brandonopenvpn' should include the static IP 192.0.2.15 and the conn_group set to 'VPN_USERS'.

  • For 'brandonopenvpn':

    API METHOD: authenticate
    AUTH_RETURN
      status : SUCCEED
      user : brandonopenvpn
      reason : RADIUS access accepted
      auth method : radius
      proplist : {'prop_autogenerate': 'true', 'prop_force_lzo': 'false', 'type': 'user_connect'}
      conn_group : VPN_USERS
      conn_ip : 192.0.2.15
    
  • For another user (e.g., 'laurenopenvpn'):

    API METHOD: authenticate
    AUTH_RETURN
      status : SUCCEED
      user : laurenopenvpn
      reason : RADIUS access accepted
      auth method : radius
      proplist : {'prop_autogenerate': 'true', 'prop_force_lzo': 'false', 'type': 'user_connect'}
      conn_group : default_group
    

When testing example 2, only users in the 'VPN_USERS' group can authentication successfully. All others have their authentication failed.

  • For 'brandonopenvpn' (a member of the 'VPN_USERS' group):

    API METHOD: authenticate
    AUTH_RETURN
      status : SUCCEED
      user : brandonopenvpn
      reason : RADIUS access accepted
      auth method : radius
      proplist : {'prop_autogenerate': 'true', 'prop_force_lzo': 'false', 'type': 'user_connect', 'conn_group': 'VPN_USERS'}
    
  • For a different user (e.g., 'laurenopenpn', not in the 'VPN_users' group):

    API METHOD: authenticate
    AUTH_RETURN
      status : FAIL
      user : laurenopenvpn
      reason : User not in the required group
      auth method : radius
      proplist : {'prop_autogenerate': 'true', 'prop_force_lzo': 'false', 'type': 'user_connect'}
    

Summary for these example scripts

  • Example 1: Assigns the user to a specific group ('VPN_USERS') and provides a static IP address if the user matches the condition in the script.

  • Example 2: Only allows users from the 'VPN_USERS' group to authenticate successfully. Any other users will have their authentication failed with a custom reason ("User not in the required group").

By using the authcli tool and reviewing the output, you can confirm that the post-authentication script is functioning as expected and that the correct parameters are being returned.

Return value

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

  • Returning only authret affects only the current authentication session.

  • Returning proplist_save along with authret saves the changes to the user properties database, making them persistent for future sessions. We use this in our hardware-address checking, RADIUS, LDAP, and SAML post-auth scripts.

Install the post-auth script

Refer to these tutorials: