Skip to main content

Tutorial: Configure LDAP Filters in Access Server to Restrict VPN Access by Active Directory Group

Abstract

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:

Example environment

The examples in this tutorial use these values:

Setting

Example value

Parent group

Group0

Child group 1

Group1

Child group 2

Group2

Child group 3

Group3

LDAP user 1

brandonuser1

LDAP user 2

brandonuser2

LDAP user 3

brandonuser3

Base DN

CN=Users,DC=brandonldap,DC=net

Replace these values with the corresponding values from your environment.

Placeholder values

The commands in this tutorial use the following placeholders:

Placeholder

Description

<BASE_DN>

The starting point in the LDAP directory tree where searches begin.

<FULL_DN>

The distinguished name (DN) that uniquely identifies a specific LDAP user or group.

<PASSWORD>

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,

  • brandonuser1 is a member of Group1.

  • brandonuser2 is a member of Group2.

  • Only members of Group1 should 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.

  1. Connect to the console and get root privileges.

  2. 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
      }
  3. 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
      }
  4. Verify the users that have Group1 in their memberOf attribute:

    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
      }
  5. Repeat these checks for any other users or groups you plan to test.

Configure the memberOf LDAP filter in the Admin Web UI

  1. Sign in to the Admin Web UI.

  2. Navigate to Authentication → LDAP.

  3. Under LDAP filter, enter:

    memberOf=<FULL_DN>
    • For this example:

      memberOf=CN=Group1,CN=Users,DC=brandonldap,DC=net
  4. 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.

  1. Connect to the console and get root privileges.

  2. 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
  3. 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.

  1. Verify that brandonuser1, a member of Group1, 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
  2. Verify that brandonuser2, who isn't a member of Group1, 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
  3. 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:

  • brandonuser1 is a member of Group1.

  • brandonuser2 is a member of Group2.

  • brandonuser3 is a member of Group3.

  • Members of Group1 and Group2 should be allowed.

  • Members of Group3 should be denied.

Verify the users and memberOf group membership

Use LDAP Explorer as described in Step 1 to confirm that Access Server can locate:

  • brandonuser1

  • brandonuser2

  • brandonuser3

  • Group1

  • Group2

  • Group3

  1. 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
  2. Confirm with each user and group.

Configure the multi-group memberOf filter in the Admin Web UI

  1. Sign in to the Admin Web UI.

  2. Navigate to Authentication → LDAP.

  3. 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)
  4. Select Save and Restart.

Configure the multi-group memberOf filter with sacli

  1. Connect to the console and get root privileges.

  2. Configure the LDAP filter:

    sacli --key "auth.ldap.0.add_req" --value "|(<FULL_DN>)(<FULL_DN>)" ConfigPut
  3. Restart Access Server services:

    sacli start

Verify the multiple-group memberOf filter with authcli

  1. 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>
  2. 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:

  • Group0 is the parent group.

  • Group1 and Group2 are child groups nested under Group0.

  • brandonuser1 is a direct member of Group1.

  • brandonuser2 is a direct member of Group2.

  • 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

  1. Verify that Access Server can locate brandonuser1 and Group1:

    • 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
      }
  2. 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
      }
  3. Verify that Group1 and Group2 are members of Group0:

    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
      }
  4. Confirm with each user and group.

Configure nested memberOf with LDAP_MATCHING_RULE_IN_CHAIN in the Admin Web UI

  1. Sign in to the Admin Web UI.

  2. Navigate to Authentication → LDAP.

  3. Under LDAP filter, enter:

    memberOf:1.2.840.113556.1.4.1941:=<FULL_DN>
  4. 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
  5. Select Save and Restart.

Configure nested memberOf with sacli

  1. Connect to the console and get root privileges.

  2. Configure the filter:

    sacli --key "auth.ldap.0.add_req" --value "memberOf:1.2.840.113556.1.4.1941:=<FULL_DN>" ConfigPut
  3. 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
  4. Restart the Access Server services:

    sacli start

Verify nested group membership with authcli

  1. 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>
  2. 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>
  3. 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:

  • Group0 is the parent group.

  • Group1 and Group2 are nested child groups.

  • brandonuser1 and brandonuser2 belong to Group1.

  • brandonuser3 belongs to Group2.

  • Both child groups belong to Group0.

  • Members of Group1 should be allowed.

  • Members of Group2 should 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:

  • Group1 and Group2 are members of Group0.

  • brandonuser1 and brandonuser2 are members of Group1.

  • brandonuser3 is a member of Group2.

  1. 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
      }
  2. 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
      }
  3. Verify Access Server can see Group1 members:

    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
      }
  4. Verify Access Server can see Group0 members:

    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
      }
  5. Repeat with other users and groups.

Configure the nested-group memberOf filter in the Admin Web UI

  1. Sign in to the Admin Web UI.

  2. Navigate to Authentication → LDAP.

  3. 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 (|).

  4. 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)))
  5. Select Save and Restart.

Configure the nested-group memberOf filter with sacli

  1. Connect to the console and get root privileges.

  2. 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 (|).

  3. Restart the Access Server services:

    sacli start

Verify the nested-group memberOf filter with authcli

Verify the expected behavior:

User

Membership

Expected result

brandonuser1

Group1 → Group0

SUCCEED

brandonuser2

Group1 → Group0

SUCCEED

brandonuser3

Group2 → Group0

FAIL

  1. Test each user with:

    /usr/local/openvpn_as/scripts/authcli --user <USERNAME> --pass <PASSWORD>
  2. After confirming the expected results with authcli, test authentication through a VPN connection.

See also