Tutorial: Configure LDAP Filters in Access Server to Restrict VPN Access by Active Directory Group
Configure LDAP filters in Access Server to restrict VPN authentication by Active Directory group — covers single group, multiple groups, nested groups using LDAP_MATCHING_RULE_IN_CHAIN, and allowing one nested group while denying another, with Admin Web UI and CLI steps.
Overview
OpenVPN Access Server supports LDAP filter syntax to control which users can authenticate through LDAP. You can use LDAP filters to restrict VPN access to members of specific Microsoft Active Directory (AD) groups.
This tutorial covers several common group-membership scenarios using the Active Directory memberOf attribute:
Allow members of one specific group.
Allow members of multiple groups.
Allow direct and indirect members of a parent group through nested groups.
Allow members of selected nested groups while excluding others.
The examples use the Base DN, which defines the starting point in the LDAP directory tree where Access Server begins searching for users and groups.
For nested group membership, the tutorial uses LDAP_MATCHING_RULE_IN_CHAIN, identified by OID 1.2.840.113556.1.4.1941. This Active Directory matching rule enables recursive nested-group membership lookups without requiring multiple LDAP queries from the application.
You'll also use Access Server's authcli tool to test LDAP authentication directly from the command line without establishing a VPN connection.
Group-based LDAP filters can also support least-privilege access policies used in Zero Trust Network Access deployments.
Prerequisites
Before you begin, ensure you have:
An installed Access Server with LDAP authentication configured. If LDAP isn't configured yet, complete Tutorial: Set Up Access Server with Active Directory via LDAP for VPN Integration.
Admin Web UI access or console access with root privileges.
A Windows Active Directory server with LDAP configured.
Existing AD users and groups you can use to test the filters.
Example environment
The examples in this tutorial use these values:
Setting | Example value |
|---|---|
Parent group |
|
Child group 1 |
|
Child group 2 |
|
Child group 3 |
|
LDAP user 1 |
|
LDAP user 2 |
|
LDAP user 3 |
|
Base DN |
|
Replace these values with the corresponding values from your environment.
Placeholder values
The commands in this tutorial use the following placeholders:
Placeholder | Description |
|---|---|
| The starting point in the LDAP directory tree where searches begin. |
| The distinguished name (DN) that uniquely identifies a specific LDAP user or group. |
| The LDAP user's password. |
Note
In our documentation, we use example IPv4 addresses and subnets reserved for documentation, such as 192.0.2.0/24, 198.51.100.0/24, and 203.0.113.0/24.
Ensure you replace them with valid IPv4 addresses and subnets for your network(s).
Use this scenario when only members of one specific AD group should be allowed to authenticate.
In this example,
brandonuser1is a member ofGroup1.brandonuser2is a member ofGroup2.Only members of
Group1should be allowed to authenticate.
Verify the user and group with LDAP Explorer
Before applying the LDAP filter, confirm that Access Server can locate the users and groups in Active Directory.
Connect to the console and get root privileges.
Verify that Access Server can locate
brandonuser1:sacli --dn='CN=Users,DC=brandonldap,DC=net' --scope 'subnode' --key 'sAMAccountName' --value 'brandonuser1' ldapexp
Example output:
{ "reason": "LDAP query succeeded on ldap://192.0.2.10", "result": [ "CN=brandonuser1,CN=Users,DC=brandonldap,DC=net" ], "status": 0 }
Verify that Access Server can locate
Group1:sacli --dn='CN=Users,DC=brandonldap,DC=net' --scope 'subnode' --key 'sAMAccountName' --value 'Group1' ldapexp
Example output:
{ "reason": "LDAP query succeeded on ldap://192.0.2.10", "result": [ "CN=Group1,CN=Users,DC=brandonldap,DC=net" ], "status": 0 }
Verify the users that have
Group1in theirmemberOfattribute:sacli --dn 'CN=Users,DC=brandonldap,DC=net' --scope 'subnode' --key memberOf --value 'CN=Group1,CN=Users,DC=brandonldap,DC=net' ldapexp
Example output:
{ "reason": "LDAP query succeeded on ldap://192.0.2.10", "result": [ "CN=brandonuser1,CN=Users,DC=brandonldap,DC=net" ], "status": 0 }
Repeat these checks for any other users or groups you plan to test.
Configure the memberOf LDAP filter in the Admin Web UI
Sign in to the Admin Web UI.
Navigate to Authentication → LDAP.
Under LDAP filter, enter:
memberOf=<FULL_DN>
For this example:
memberOf=CN=Group1,CN=Users,DC=brandonldap,DC=net
Select Save and Restart.
Configure the memberOf LDAP filter with sacli
You can also configure the filter from the command line with the sacli command-line tool.
Connect to the console and get root privileges.
Configure the LDAP filter:
sacli --key "auth.ldap.0.add_req" --value "memberOf=<FULL_DN>" ConfigPut
For this example:
sacli --key "auth.ldap.0.add_req" --value "memberOf=CN=Group1,CN=Users,DC=brandonldap,DC=net" ConfigPut
Restart the Access Server services:
sacli start
Verify the memberOf LDAP filter with authcli
Access Server's authcli tool lets you test LDAP authentication without first establishing a VPN connection.
Verify that
brandonuser1, a member ofGroup1, can authenticate:/usr/local/openvpn_as/scripts/authcli --user brandonuser1 --pass <PASSWORD>
Example output:
AUTH_RETURN status : SUCCEED user : brandonuser1 reason : LDAP auth succeeded on ldap://192.0.2.10 auth method : ldap proplist : {'prop_autogenerate': 'true', 'type': 'user_connect', 'pvt_epoch': '17746636625031316972'} session_id : AS_H403XkX7Aexthq5LfecgPA== expire : 1787068752
Verify that
brandonuser2, who isn't a member ofGroup1, can't authenticate:/usr/local/openvpn_as/scripts/authcli --user brandonuser2 --pass <PASSWORD>
Example output:
API METHOD: authenticate AUTH_RETURN status : FAIL user : brandonuser2 reason : user not found that meets specified requirements: memberOf=CN=Group1,CN=Users,DC=brandonldap,DC=net auth method : ldap
After verifying the filter with
authcli, test authentication through a VPN connection.
Use this scenario when users from more than one AD group should be allowed to authenticate.
In this example:
brandonuser1is a member ofGroup1.brandonuser2is a member ofGroup2.brandonuser3is a member ofGroup3.Members of
Group1andGroup2should be allowed.Members of
Group3should be denied.
Verify the users and memberOf group membership
Use LDAP Explorer as described in Step 1 to confirm that Access Server can locate:
brandonuser1brandonuser2brandonuser3Group1Group2Group3
Verify that Access Server can locate the necessary users and groups. Examples:
sacli --dn='CN=Users,DC=brandonldap,DC=net' --scope 'subnode' --key 'sAMAccountName' --value 'brandonuser1' ldapexp
sacli --dn='CN=Users,DC=brandonldap,DC=net' --scope 'subnode' --key 'sAMAccountName' --value 'Group1' ldapexp
sacli --dn 'CN=Users,DC=brandonldap,DC=net' --scope 'subnode' --key memberOf --value 'CN=Group1,CN=Users,DC=brandonldap,DC=net' ldapexp
Confirm with each user and group.
Configure the multi-group memberOf filter in the Admin Web UI
Sign in to the Admin Web UI.
Navigate to Authentication → LDAP.
Under LDAP filter, enter
|(<FULL_DN>)(<FULL_DN>)
Important
Include the leading pipe (
|). It specifies the OR condition used by this Access Server filter.For this example:
|(memberOf=CN=Group1,CN=Users,DC=brandonldap,DC=net)(memberOf=CN=Group2,CN=Users,DC=brandonldap,DC=net)
Select Save and Restart.
Configure the multi-group memberOf filter with sacli
Connect to the console and get root privileges.
Configure the LDAP filter:
sacli --key "auth.ldap.0.add_req" --value "|(<FULL_DN>)(<FULL_DN>)" ConfigPut
Restart Access Server services:
sacli start
Verify the multiple-group memberOf filter with authcli
Test all three users:
Expected:
SUCCEED/usr/local/openvpn_as/scripts/authcli --user brandonuser1 --pass <PASSWORD>
Expected:
SUCCEED/usr/local/openvpn_as/scripts/authcli --user brandonuser2 --pass <PASSWORD>
Expected:
FAIL/usr/local/openvpn_as/scripts/authcli --user brandonuser3 --pass <PASSWORD>
After verifying the expected results, test authentication through a VPN connection.
Use this scenario when users belong to nested groups and you want to allow both direct and indirect members of a parent group.
In this example:
Group0is the parent group.Group1andGroup2are child groups nested underGroup0.brandonuser1is a direct member ofGroup1.brandonuser2is a direct member ofGroup2.Both users are therefore indirect members of
Group0.
A standard memberOf=... filter only evaluates direct group membership. For recursive nested-group lookups in Active Directory, use LDAP_MATCHING_RULE_IN_CHAIN.
LDAP_MATCHING_RULE_IN_CHAIN, identified by OID 1.2.840.113556.1.4.1941, allows Active Directory to evaluate the membership chain and find users who belong to the parent group indirectly through nested child groups.
Verify the nested groups with LDAP Explorer
Verify that Access Server can locate
brandonuser1andGroup1:brandonuser1:sacli --dn='CN=Users,DC=brandonldap,DC=net' --scope 'subnode' --key 'sAMAccountName' --value 'brandonuser1' ldapexp
Example output:
{ "reason": "LDAP query succeeded on ldap://192.0.2.10", "result": [ "CN=brandonuser1,CN=Users,DC=brandonldap,DC=net" ], "status": 0 }Group1:sacli --dn='CN=Users,DC=brandonldap,DC=net' --scope 'subnode' --key 'sAMAccountName' --value 'Group1' ldapexp
Example output:
{ "reason": "LDAP query succeeded on ldap://192.0.2.10", "result": [ "CN=Group1,CN=Users,DC=brandonldap,DC=net" ], "status": 0 }
Verify that Access Server can locate members of
Group1:sacli --dn 'CN=Users,DC=brandonldap,DC=net' --scope 'subnode' --key memberOf --value 'CN=Group1,CN=Users,DC=brandonldap,DC=net' ldapexp
Example output:
{ "reason": "LDAP query succeeded on ldap://192.0.2.10", "result": [ "CN=brandonuser1,CN=Users,DC=brandonldap,DC=net" ], "status": 0 }
Verify that
Group1andGroup2are members ofGroup0:sacli --dn 'CN=Users,DC=brandonldap,DC=net' --scope 'subnode' --key memberOf --value 'CN=Group0,CN=Users,DC=brandonldap,DC=net' ldapexp
Example output:
{ "reason": "LDAP query succeeded on ldap://192.0.2.10", "result": [ "CN=Group1,CN=Users,DC=brandonldap,DC=net", "CN=Group2,CN=Users,DC=brandonldap,DC=net" ], "status": 0 }
Confirm with each user and group.
Configure nested memberOf with LDAP_MATCHING_RULE_IN_CHAIN in the Admin Web UI
Sign in to the Admin Web UI.
Navigate to Authentication → LDAP.
Under LDAP filter, enter:
memberOf:1.2.840.113556.1.4.1941:=<FULL_DN>
Replace
<FULL_DN>with the full DN of the parent group. For example:memberOf:1.2.840.113556.1.4.1941:=CN=Group0,CN=Users,DC=brandonldap,DC=net
Select Save and Restart.
Configure nested memberOf with sacli
Connect to the console and get root privileges.
Configure the filter:
sacli --key "auth.ldap.0.add_req" --value "memberOf:1.2.840.113556.1.4.1941:=<FULL_DN>" ConfigPut
Replace
<FULL_DN>with the full DN of the parent group. For example:sacli --key "auth.ldap.0.add_req" --value "memberOf:1.2.840.113556.1.4.1941:=CN=Group0,CN=Users,DC=brandonldap,DC=net" ConfigPut
Restart the Access Server services:
sacli start
Verify nested group membership with authcli
Verify that users in either nested child group can authenticate:
Expected:
SUCCEED/usr/local/openvpn_as/scripts/authcli --user brandonuser1 --pass <PASSWORD>
Expected:
SUCCEED/usr/local/openvpn_as/scripts/authcli --user brandonuser2 --pass <PASSWORD>
Test a user who isn't a direct or indirect member of
Group0(for example,brandonuser3):Expected:
FAIL/usr/local/openvpn_as/scripts/authcli --user brandonuser3 --pass <PASSWORD>
After verifying the expected results, test authentication through a VPN connection.
Use this scenario when users belong to the same parent group through different nested groups, but only selected child groups should be allowed to authenticate.
In this example:
Group0is the parent group.Group1andGroup2are nested child groups.brandonuser1andbrandonuser2belong toGroup1.brandonuser3belongs toGroup2.Both child groups belong to
Group0.Members of
Group1should be allowed.Members of
Group2should be denied.
This filter combines LDAP_MATCHING_RULE_IN_CHAIN with a negative memberOf condition.
Verify the nested memberOf relationships
Use LDAP Explorer to verify:
Group1andGroup2are members ofGroup0.brandonuser1andbrandonuser2are members ofGroup1.brandonuser3is a member ofGroup2.
Verify Access Server can see
brandonuser1:sacli --dn='CN=Users,DC=brandonldap,DC=net' --scope 'subnode' --key 'sAMAccountName' --value 'brandonuser1' ldapexp
Example output:
{ "reason": "LDAP query succeeded on ldap://192.0.2.10", "result": [ "CN=brandonuser1,CN=Users,DC=brandonldap,DC=net" ], "status": 0 }
Verify Access Server can see
Group1:sacli --dn='CN=Users,DC=brandonldap,DC=net' --scope 'subnode' --key 'sAMAccountName' --value 'Group1' ldapexp
Example output:
{ "reason": "LDAP query succeeded on ldap://192.0.2.10", "result": [ "CN=Group1,CN=Users,DC=brandonldap,DC=net" ], "status": 0 }
Verify Access Server can see
Group1members:sacli --dn 'CN=Users,DC=brandonldap,DC=net' --scope 'subnode' --key memberOf --value 'CN=Group1,CN=Users,DC=brandonldap,DC=net' ldapexp
Example output:
{ "reason": "LDAP query succeeded on ldap://192.0.2.10", "result": [ "CN=brandonuser1,CN=Users,DC=brandonldap,DC=net" ], "status": 0 }
Verify Access Server can see
Group0members:sacli --dn 'CN=Users,DC=brandonldap,DC=net' --scope 'subnode' --key memberOf --value 'CN=Group0,CN=Users,DC=brandonldap,DC=net' ldapexp
Example output:
{ "reason": "LDAP query succeeded on ldap://192.0.2.10", "result": [ "CN=Group1,CN=Users,DC=brandonldap,DC=net", "CN=Group2,CN=Users,DC=brandonldap,DC=net" ], "status": 0 }
Repeat with other users and groups.
Configure the nested-group memberOf filter in the Admin Web UI
Sign in to the Admin Web UI.
Navigate to Authentication → LDAP.
Under LDAP filter, enter:
|(&(memberOf:1.2.840.113556.1.4.1941:=<FULL_DN>)(!(memberOf:1.2.840.113556.1.4.1941:=<FULL_DN>)))
Important
Include the leading pipe (
|).Use the parent group for the first DN and the excluded child group for the second. For this example:
|(&(memberOf:1.2.840.113556.1.4.1941:=CN=Group0,CN=Users,DC=brandonldap,DC=net)(!(memberOf:1.2.840.113556.1.4.1941:=CN=Group2,CN=Users,DC=brandonldap,DC=net)))
Select Save and Restart.
Configure the nested-group memberOf filter with sacli
Connect to the console and get root privileges.
Configure the filter:
sacli --key "auth.ldap.0.add_req" --value "|(&(memberOf:1.2.840.113556.1.4.1941:=<FULL_DN>)(!(memberOf:1.2.840.113556.1.4.1941:=<FULL_DN>)))" ConfigPut
Important
Include the leading pipe (
|).Restart the Access Server services:
sacli start
Verify the nested-group memberOf filter with authcli
Verify the expected behavior:
User | Membership | Expected result |
|---|---|---|
|
|
|
|
|
|
|
|
|
Test each user with:
/usr/local/openvpn_as/scripts/authcli --user <USERNAME> --pass <PASSWORD>
After confirming the expected results with
authcli, test authentication through a VPN connection.