OpenVPN
Last modified by Sebastian Marsching on 2022/05/29 13:52
MTU issues with OpenVPN
When packages that are travelling through an OpenVPN tunnel seem to disappear or connections (seem to not react any more (e.g. a webpage keeps loading and loading), this issues might be related to problems with the MTU settings.
fragment 1300
mssfix
mssfix
You might have to change the fragment value, but 1300 should be okay for a first try.
See also: Path MTU Discovery issues
Authenticating with Active Directory
The following script can be used with OpenVPNs auth-user-pass-verify directive. The script reads the username and password from a file, so the via-file method should be specified. This script needs a fairly modern version of the ldap3 module (it has been tested with version 1.3.1).
#!/usr/bin/python
# Authenticates an OpenVPN user against an Active Directory using LDAP.
# The authentication is successful when the specified credentials can be used to
# successfully connect with the LDAP service and the user is a member of the
# specified group. The membership check includes transitive memberships.
# The username is expected to be the SAM account name (without the domain).
# The password is expected to be the raw (unhashed) password.
# The username and password are read from the file specified as the only
# parameter to this script so that do not have to be passed through
# potentially unsafe environment variables.
import io
import ldap3
import sys
# Find the principal (LDAP object) for the specified SAM account name.
# The SAM account name must not include the domain.
def find_principal_by_sam_account_name(ldap_connection, base_dn, sam_account_name):
if not ldap_connection.search(search_base = base_dn, search_filter = ('(sAMAccountName=' + ldap3.utils.conv.escape_filter_chars(sam_account_name) + ')')):
return None
if len(ldap_connection.entries) == 0:
return None
return ldap_connection.entries[0]
# This method is useful if we need all groups that the user is a member of.
def find_all_groups_principal_is_member_of(ldap_connection, base_dn, principal_dn):
if not ldap_connection.search(search_base = base_dn, search_filter = ('(member:1.2.840.113556.1.4.1941:=' + ldap3.utils.dn.safe_dn(principal_dn) + ')'), attributes = ldap3.ALL_ATTRIBUTES):
return None
return ldap_connection.entries
# Checks whether the specified principal (identified by its DN) is a member of
# the specified group (identified by its SID).
def is_principal_member_of_group(ldap_connection, base_dn, principal_dn, group_sid):
if not ldap_connection.search(search_base = base_dn, search_filter = ('(&(objectSid=' + ldap3.utils.conv.escape_filter_chars(group_sid) + ')(member:1.2.840.113556.1.4.1941:=' + ldap3.utils.dn.safe_dn(principal_dn) + '))')):
return False
return len(ldap_connection.entries) != 0
# We use short timeouts so that we can failover to another domain controller
# when the first one fails.
connect_timeout = 1.0
receive_timeout = 1.0
base_dn = 'dc=ad,dc=example,dc=com'
# We can restrict the tree in which the principal must reside. If no
# restrictions are desired, we can simply use the base DN.
principal_search_base_dn = base_dn
domain = 'WINDOMAIN'
group_sid = 'S-1-5-21-1234567890-1234567890-1234567890-1234'
ldap_servers = [ 'dc-1.ad.example.com', 'dc-2.ad.example.com' ]
if len(sys.argv) != 2:
sys.exit(1)
user_pass_filename = sys.argv[1]
try:
user_pass_file = io.open(user_pass_filename)
username = user_pass_file.readline().strip()
password = user_pass_file.readline().strip()
user_pass_file.close()
except:
sys.exit(1)
ldap_username = domain + '\\' + username
connection = None
server = None
for ldap_server in ldap_servers:
try:
server = ldap3.Server(ldap_server, port = 636, use_ssl = True, connect_timeout = connect_timeout)
connection = ldap3.Connection(server, user = ldap_username, password = password, auto_bind = True, receive_timeout = receive_timeout)
break
except:
server = None
connection = None
if connection == None:
sys.exit(1)
try:
user_entry = find_principal_by_sam_account_name(connection, principal_search_base_dn, username)
if user_entry == None:
sys.exit(1)
user_dn = user_entry.entry_get_dn()
is_group_member = is_principal_member_of_group(connection, base_dn, user_dn, group_sid)
except:
sys.exit(1)
if is_group_member:
sys.exit(0)
else:
sys.exit(1)
# Authenticates an OpenVPN user against an Active Directory using LDAP.
# The authentication is successful when the specified credentials can be used to
# successfully connect with the LDAP service and the user is a member of the
# specified group. The membership check includes transitive memberships.
# The username is expected to be the SAM account name (without the domain).
# The password is expected to be the raw (unhashed) password.
# The username and password are read from the file specified as the only
# parameter to this script so that do not have to be passed through
# potentially unsafe environment variables.
import io
import ldap3
import sys
# Find the principal (LDAP object) for the specified SAM account name.
# The SAM account name must not include the domain.
def find_principal_by_sam_account_name(ldap_connection, base_dn, sam_account_name):
if not ldap_connection.search(search_base = base_dn, search_filter = ('(sAMAccountName=' + ldap3.utils.conv.escape_filter_chars(sam_account_name) + ')')):
return None
if len(ldap_connection.entries) == 0:
return None
return ldap_connection.entries[0]
# This method is useful if we need all groups that the user is a member of.
def find_all_groups_principal_is_member_of(ldap_connection, base_dn, principal_dn):
if not ldap_connection.search(search_base = base_dn, search_filter = ('(member:1.2.840.113556.1.4.1941:=' + ldap3.utils.dn.safe_dn(principal_dn) + ')'), attributes = ldap3.ALL_ATTRIBUTES):
return None
return ldap_connection.entries
# Checks whether the specified principal (identified by its DN) is a member of
# the specified group (identified by its SID).
def is_principal_member_of_group(ldap_connection, base_dn, principal_dn, group_sid):
if not ldap_connection.search(search_base = base_dn, search_filter = ('(&(objectSid=' + ldap3.utils.conv.escape_filter_chars(group_sid) + ')(member:1.2.840.113556.1.4.1941:=' + ldap3.utils.dn.safe_dn(principal_dn) + '))')):
return False
return len(ldap_connection.entries) != 0
# We use short timeouts so that we can failover to another domain controller
# when the first one fails.
connect_timeout = 1.0
receive_timeout = 1.0
base_dn = 'dc=ad,dc=example,dc=com'
# We can restrict the tree in which the principal must reside. If no
# restrictions are desired, we can simply use the base DN.
principal_search_base_dn = base_dn
domain = 'WINDOMAIN'
group_sid = 'S-1-5-21-1234567890-1234567890-1234567890-1234'
ldap_servers = [ 'dc-1.ad.example.com', 'dc-2.ad.example.com' ]
if len(sys.argv) != 2:
sys.exit(1)
user_pass_filename = sys.argv[1]
try:
user_pass_file = io.open(user_pass_filename)
username = user_pass_file.readline().strip()
password = user_pass_file.readline().strip()
user_pass_file.close()
except:
sys.exit(1)
ldap_username = domain + '\\' + username
connection = None
server = None
for ldap_server in ldap_servers:
try:
server = ldap3.Server(ldap_server, port = 636, use_ssl = True, connect_timeout = connect_timeout)
connection = ldap3.Connection(server, user = ldap_username, password = password, auto_bind = True, receive_timeout = receive_timeout)
break
except:
server = None
connection = None
if connection == None:
sys.exit(1)
try:
user_entry = find_principal_by_sam_account_name(connection, principal_search_base_dn, username)
if user_entry == None:
sys.exit(1)
user_dn = user_entry.entry_get_dn()
is_group_member = is_principal_member_of_group(connection, base_dn, user_dn, group_sid)
except:
sys.exit(1)
if is_group_member:
sys.exit(0)
else:
sys.exit(1)