Skip to content

Instantly share code, notes, and snippets.

@justmiles
Last active August 19, 2020 10:24
Show Gist options
  • Save justmiles/deba3fc680e35719cf98c7533591853e to your computer and use it in GitHub Desktop.
Save justmiles/deba3fc680e35719cf98c7533591853e to your computer and use it in GitHub Desktop.
OpenVPN post_auth MAC address check via JumpCloud
# OpenVPN Access Server MAC address checking post_auth script.
# Johan Draaisma
#
# This script can be used with LOCAL, PAM, LDAP, and RADIUS authentication.
# It adds an additional check when authentication is done through the VPN connection.
# It applies to all 3 connection profiles types (server-locked, user-locked, auto-login).
# Windows, Linux, and macOS will be reporting MAC addresses. However, Android and iOS
# devices will not, due to technical reasons. They will instead by reporting UUID. For
# simplicity and legacy reasons we will just call it MAC address from here on in, but
# it can be in theory any unique hardware-based string that the client reports to us.
#
# Please note: RADIUS/LDAP case insensitivity may lead to the system recognizing
# Billy.Bob and billy.bob as 2 separate accounts, when using RADIUS/LDAP.
#
#
# Full documentation and explanation can be found here:
# https://openvpn.net/vpn-server-resources/access-server-post-auth-script-host-checking/
#
# Script last updated in January 2020
import re
import subprocess
import os
from pyovpn.plugin import *
# Optionally set this string to a known public IP address (such as the
# public IP address of machines connecting from a trusted location, such
# as the corporate LAN). If set, all users must do the first login from this
# IP address, so the machine's hardware (MAC/UUID) address can be recorded.
#
# If this is empty, which it is by default, then registrations for the first
# MAC address for a user account will be done automatically on first login.
#
# If this is set to "NONE" or "DISABLED" then the server administrator must
# always manually register each MAC/UUID address by hand on the command line.
# For that, we refer you to our documentation.
first_login_ip_addr = ""
# If False or undefined, AS will call us asynchronously in a worker thread.
# If True, AS will call us synchronously (server will block during call),
# however we can assume asynchronous behavior by returning a Twisted
# Deferred object.
SYNCHRONOUS = False
# this function is called by the Access Server after normal VPN or web authentication
def post_auth(authcred, attributes, authret, info):
print("********** POST_AUTH %s %s %s %s" %
(authcred, attributes, authret, info))
# get user's property list, or create it if absent
proplist = authret.setdefault('proplist', {})
# user properties to save - we will use this to pass the hw_addr_save property to be
# saved in the user property database.
proplist_save = {}
error = ""
# If a VPN client authentication attempt is made, do these steps:
# Check if there is a known MAC address for this client
# If not, register it
# If yes, check it
#
# An additional optional requirement is that first time registration must occur
# from a specific IP address, as specified in the first_login_ip_addr set above
#
# The 'error' text goes to the VPN client and is shown to the user.
# The 'print' lines go to the log file at /var/log/openvpnas.log (by default).
# only do this for VPN authentication
if attributes.get('vpn_auth'):
# MAC address reported by the VPN client
hw_addr = authcred.get('client_hw_addr')
# User name of the VPN client login attempt
username = authcred.get('username')
# IP address of VPN client login attempt
clientip = authcred.get('client_ip_addr')
if hw_addr:
hw_addr_save = proplist.get('pvt_hw_addr') # saved MAC address
# saved MAC address (secondary)
hw_addr_save2 = proplist.get('pvt_hw_addr2')
# Check JumpCloud first
if authorizedInJumpcloud(username, hwaddr):
print("***** POST_AUTH MAC CHECK: account user name : %s" % username)
print("***** POST_AUTH MAC CHECK: client IP address : %s" % clientip)
print("***** POST_AUTH MAC CHECK: client MAC/UUID address : %s" % hw_addr)
print("***** POST_AUTH MAC CHECK: MAC/UUID authorized via : JumpCloud")
print("***** POST_AUTH MAC CHECK: connection attempt : SUCCESS")
return authret, proplist_save
if hw_addr_save:
if not hw_addr == hw_addr_save and not hw_addr == hw_addr_save2:
error = "The hardware MAC/UUID address reported by this VPN client does not match the registered address."
print("***** POST_AUTH MAC CHECK: account user name : %s" % username)
print("***** POST_AUTH MAC CHECK: client IP address : %s" % clientip)
print("***** POST_AUTH MAC CHECK: client MAC/UUID address : %s" % hw_addr)
if hw_addr_save2:
print("***** POST_AUTH MAC CHECK: expected MAC/UUID address : %s or %s" %
(hw_addr_save, hw_addr_save2))
else:
print("***** POST_AUTH MAC CHECK: expected MAC/UUID address : %s" % hw_addr_save)
print("***** POST_AUTH MAC CHECK: connection attempt : FAILED")
else:
print("***** POST_AUTH MAC CHECK: account user name : %s" % username)
print("***** POST_AUTH MAC CHECK: client IP address : %s" % clientip)
print("***** POST_AUTH MAC CHECK: client MAC/UUID address : %s" % hw_addr)
if hw_addr_save2:
print("***** POST_AUTH MAC CHECK: expected MAC/UUID address : %s or %s" %
(hw_addr_save, hw_addr_save2))
else:
print("***** POST_AUTH MAC CHECK: expected MAC/UUID address : %s" % hw_addr_save)
print("***** POST_AUTH MAC CHECK: connection attempt : SUCCESS")
else:
# First login by this user, save MAC addr.
if not first_login_ip_addr or first_login_ip_addr == clientip:
proplist_save['pvt_hw_addr'] = hw_addr
print("***** POST_AUTH MAC CHECK: account user name : %s" % username)
print("***** POST_AUTH MAC CHECK: client IP address : %s" % clientip)
print("***** POST_AUTH MAC CHECK: client MAC/UUID address : %s" % hw_addr)
print("***** POST_AUTH MAC CHECK: action taken : MAC address learned and locked.")
print("***** POST_AUTH MAC CHECK: connection attempt : SUCCESS")
else:
error = "Your attempt to login from an address not approved for MAC/UUID address registration has been denied."
print("***** POST_AUTH MAC CHECK: account user name : %s" % username)
print("***** POST_AUTH MAC CHECK: client IP address : %s" % clientip)
print("***** POST_AUTH MAC CHECK: action taken : attempt to register client MAC/UUID address from restricted address denied.")
print("***** POST_AUTH MAC CHECK: connection attempt : FAILED")
else:
error = "VPN client is not reporting a MAC/UUID address. Please verify that a suitable OpenVPN client is being used."
print("***** POST_AUTH MAC CHECK: account user name : %s" % username)
print("***** POST_AUTH MAC CHECK: client IP address : %s" % clientip)
print("***** POST_AUTH MAC CHECK: client MAC/UUID address : NONE REPORTED")
print("***** POST_AUTH MAC CHECK: action taken : VPN connection denied with a suitable error message.")
print("***** POST_AUTH MAC CHECK: connection attempt : FAILED")
# process error, if one occurred
if error:
authret['status'] = FAIL
# this error string is written to the server log file
authret['reason'] = error
# this error string is reported to the client user
authret['client_reason'] = error
return authret, proplist_save
def authorizedInJumpcloud(username, hwaddr):
completedProc = subprocess.run(
'jc user attribute-matches --username %s --key hwaddr --value %s' % (username, hwaddr), shell=True)
print(completedProc.returncode)
if completedProc.returncode == 0:
return True
else:
return False
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment