OpenSearch Security Part 6: LDAP Authorization

This post explains how to use LDAP groups in OpenSearch to assign security roles to users.

In the last article, we explained how to use LDAP for user authentication in OpenSearch. Let’s explore how we can leverage existing LDAP groups for assigning roles to users. The following steps can of course also be applied to Active Directory.

Recap: User Authentication

Last time, we configured OpenSearch to connect to an LDAP server for user authentication. Since LDAP organises users (as well as any other objects) in a tree structure, we had to tell OpenSearch:
    In which LDAP subtree the users are stored
    How to query this subtree for a specific user
The query for looking up users contains a placeholder variable which is replaced with the user name before submission. If a user record was found and the password is correct, authentication is successful. We then mapped the user to a security role by user name.

Authorization: Leveraging LDAP groups

LDAP not only stores users but also groups: An LDAP user can be a member of one or more groups, and groups can also be nested (“group of groups”). So instead of assigning users directly to security roles, why not use the already existing LDAP groups? Doing so reduces the user administration by a great deal: You can add new LDAP users, assign them to LDAP groups, and the OpenSearch security roles will take effect automatically.
So we want OpenSearch to:
    First, verify that the user credentials are correct (authentication, “authc”)
    As a second step, fetch all LDAP groups of the authenticated user (authorization, “authz”)
In our example, we have two LDAP groups, “devops” and “qa”. LDAP groups are similar to users. Usually, they are stored in an LDAP subtree and are identified by their Distinguished Name. For example:
dn: cn=devops,ou=groups,dc=example,dc=com
dn: cn=admin,ou=groups,dc=example,dc=com
Users are usually assigned to LDAP groups by the uniqueMember attributes, like:
dn: cn=devops,ou=groups,dc=example,dc=com
uniqueMember: cn=Peter Scott,ou=people,dc=example,dc=com
uniqueMember: ...

dn: cn=qa,ou=groups,dc=example,dc=com
uniqueMember: cn=Peter Scott,ou=people,dc=example,dc=com
uniqueMember: ...

Our goal is to retrieve all LDAP groups a user is a member of and then map these groups to security roles.

Enabling LDAP Authorization

LDAP authorization is very similar to authentication. We need to tell OpenSearch:
    How to connect to LDAP
    In which subtree the roles are stored
    Which LDAP query we want to use for looking up the groups for a particular user
The authz section of config.yml defines the authorization backends. Lets add an authorization backend with type ldap and enable it:
config:
  dynamic:
  ...
    authc:
      ...
    authz:
      my_ldap_authenticator:
        http_enabled: true
        authorization_backend:
          type: ldap
          config:
           ...
Next, we need to define the connection settings. The possible values are identical to the authc section discussed in our last article. Example:
   authz:
      my_ldap_authenticator:
        http_enabled: true
        authorization_backend:
          type: ldap
          config:
            hosts:
              - primary.ldap.example.com:389
              - secondary.ldap.example.com:389
            bind_dn: cn=admin,dc=example,dc=com
            password: "password"
            enable_ssl: true
            pemtrustedcas_filepath: /full/path/to/trusted_cas.pem

Configuring the Roles Subtree and the Roles Query

Similar to authentication, we now need to tell OpenSearch where our LDAP roles are stored and what query should be used to look up all user groups.
   authz:
      my_ldap_authenticator:
        ...
        authorization_backend:
          ...
          config:
          ...
            rolebase: 'ou=groups,dc=example,dc=com'
            rolesearch: '(uniqueMember={0})'
The rolebase parameter defines the LDAP groups subtree. As in the authc section, for the actual LDAP query (rolesearch) we can use placeholders: The placeholder {0} is replaced with the DN of the authenticated user.
So if this would be an SQL server instead of an LDAP server, the query would roughly look like this:
SELECT * FROM ou=groups,dc=example,dc=com WHERE uniqueMember= '<DN of the authenticated user>'
Let’s have a look again at the example above. The user “Peter Scott” is a member of the LDAP group cn=devops,ou=groups,dc=example,dc=com and cn=qa,ou=groups,dc=example,dc=com. Executing the LDAP query would thus return both the devops and the qa LDAP group records.

Mapping LDAP Groups to OpenSearch Security Roles

We can now use these LDAP groups to assign OpenSearch security roles. This is done in roles_mapping.yml. Instead of specifying the usernames directly, this time use the LDAP groups retrieved in the authorization step:
role_mapping.yml:
devops_security_role:
  backend_roles:
    - "cn=devops,ou=groups,dc=example,dc=com"

qa_security_role:
  backend_roles:
    - "cn=qa,ou=groups,dc=example,dc=com"
This assigns the security role devops_security_role to all users that are part of the cn=devops,ou=groups,dc=example,dc=com LDAP group. All users in the LDAP group cn=qa,ou=groups,dc=example,dc=com are assigned to the security role qa_security_role.
We can now manage our users and their groups in our LDAP server, and do not need to touch the OpenSearch security configuration again. Mission accomplished!

Outlook

An LDAP directory structure can be quite complex, and there are many ways users and groups may be structured. Luckily, OpenSearch security supports a wide range of LDAP use cases. For example, OpenSearch can work with multiple LDAP servers, resolve nested groups, and also retrieve roles directly from an LDAP user record. We will discuss some of these in a follow-up article. Stay tuned!
Interested? Get in touch!