Skip to content

Commit

Permalink
Configuring LDAP Authentication in OpenSearch blog post
Browse files Browse the repository at this point in the history
From time to time, a question about LDAP setup appear on Slack.  We
setup LDAP some time ago and the process was a bit tedious, but in the
process we learned to check step by step the configuration.

Add a blog post that drive users into configuring LDAP authc / authz,
give pointers about what is going on at each step and show how to check
that each step is successful.

Signed-off-by: Romain Tartière <[email protected]>
  • Loading branch information
smortex committed Dec 14, 2023
1 parent 28305da commit 196bb4d
Show file tree
Hide file tree
Showing 4 changed files with 248 additions and 0 deletions.
248 changes: 248 additions & 0 deletions _posts/2023-12-14-configure-ldap-authentication.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
---
layout: post
title: Configuring LDAP Authentication in OpenSearch
authors:
- smortex
date: 2023-12-14 14:20:00 -1000
category:
- technical-posts
meta_keywords: ldap, authentication, authorization
meta_description: Learn how to set up OpenSearch for LDAP authentication
---

The OpenSearch Security plugin has support multiple authentication backends.
In this article, we dive into the LDAP backend to authenticate and authorize users form an external directory such as OpenLDAP or Active Directory.


## Manage configuration

The LDAP configuration of OpenSearch is stored in its indexes.
It is seeded from the `opensearch-security/config.yml` configuration file on first start, but it is not updated automatically when the file is changed later on.
In this guide we will use the `securityadmin.sh` script to update the configuration in OpenSearch, using the demo certificates.
Please exercise caution when updating, doing a backup first is advised.

> **NOTE:**
> The `securityadmin.sh` script is currently deprecated, but at the time of writing its successor is not available.
> For this reason, this guide rely on this legacy script.

### Backup configuration

Check failure on line 29 in _posts/2023-12-14-configure-ldap-authentication.md

View workflow job for this annotation

GitHub Actions / style-job

[vale] reported by reviewdog 🐶 [OpenSearch.HeadingCapitalization] 'Backup configuration' is a heading and should be in sentence case. Raw Output: {"message": "[OpenSearch.HeadingCapitalization] 'Backup configuration' is a heading and should be in sentence case.", "location": {"path": "_posts/2023-12-14-configure-ldap-authentication.md", "range": {"start": {"line": 29, "column": 5}}}, "severity": "ERROR"}

Backup the currently active configuration to the `/tmp/config` directory with this command (assuming an installation on a Debian system):

```
OPENSEARCH_JAVA_HOME=/usr/share/opensearch/jdk /usr/share/opensearch/plugins/opensearch-security/tools/securityadmin.sh \

Check failure on line 34 in _posts/2023-12-14-configure-ldap-authentication.md

View workflow job for this annotation

GitHub Actions / style-job

[vale] reported by reviewdog 🐶 [Vale.Terms] Use 'OpenSearch' instead of 'opensearch'. Raw Output: {"message": "[Vale.Terms] Use 'OpenSearch' instead of 'opensearch'.", "location": {"path": "_posts/2023-12-14-configure-ldap-authentication.md", "range": {"start": {"line": 34, "column": 59}}}, "severity": "ERROR"}
-cacert /etc/opensearch/root-ca.pem \
-cert /etc/opensearch/kirk.pem \
-key /etc/opensearch/kirk-key.pem \
-backup /tmp/config
```

### Update configuration

After editing the `opensearch-security/config.yml` configuration file, it must be injected into OpenSearch using the `securityadmin.sh` script:

```
OPENSEARCH_JAVA_HOME=/usr/share/opensearch/jdk /usr/share/opensearch/plugins/opensearch-security/tools/securityadmin.sh \
-cacert /etc/opensearch/root-ca.pem \
-cert /etc/opensearch/kirk.pem \
-key /etc/opensearch/kirk-key.pem \
-cd /etc/opensearch/opensearch-security
```

## Setup users in LDAP

Check failure on line 53 in _posts/2023-12-14-configure-ldap-authentication.md

View workflow job for this annotation

GitHub Actions / style-job

[vale] reported by reviewdog 🐶 [OpenSearch.HeadingCapitalization] 'Setup users in LDAP' is a heading and should be in sentence case. Raw Output: {"message": "[OpenSearch.HeadingCapitalization] 'Setup users in LDAP' is a heading and should be in sentence case.", "location": {"path": "_posts/2023-12-14-configure-ldap-authentication.md", "range": {"start": {"line": 53, "column": 4}}}, "severity": "ERROR"}

The complete configuration of a directory is way beyond the scope of this article.
Here we will focus on the structure of the directory used in this example and how it was set up using OpenLDAP.
Other LDAP implementation can be used instead of OpenLDAP and they probably support similar features, so please refer to your particular LDAP software documentation to set up a directory that match your needs.

In this example, our directory has the following structure:

```
dc=example,dc=com
|-> ou=groups
| |-> cn=users
| `-> cn=admins
|-> ou=people
| |-> uid=alice
| `-> uid=bob
`-> ou=services
`-> cn=opensearch
```

Both *alice* and *bob* are in the *users* group, and only *alice* is in the *admins* group.

Check failure on line 73 in _posts/2023-12-14-configure-ldap-authentication.md

View workflow job for this annotation

GitHub Actions / style-job

[vale] reported by reviewdog 🐶 [OpenSearch.Spelling] Error: alice. If you are referencing a setting, variable, format, function, or repository, surround it with tic marks. Raw Output: {"message": "[OpenSearch.Spelling] Error: alice. If you are referencing a setting, variable, format, function, or repository, surround it with tic marks.", "location": {"path": "_posts/2023-12-14-configure-ldap-authentication.md", "range": {"start": {"line": 73, "column": 7}}}, "severity": "ERROR"}

Check failure on line 73 in _posts/2023-12-14-configure-ldap-authentication.md

View workflow job for this annotation

GitHub Actions / style-job

[vale] reported by reviewdog 🐶 [OpenSearch.Spelling] Error: alice. If you are referencing a setting, variable, format, function, or repository, surround it with tic marks. Raw Output: {"message": "[OpenSearch.Spelling] Error: alice. If you are referencing a setting, variable, format, function, or repository, surround it with tic marks.", "location": {"path": "_posts/2023-12-14-configure-ldap-authentication.md", "range": {"start": {"line": 73, "column": 60}}}, "severity": "ERROR"}
Anonymous bind is not allowed, so we also have a service account *opensearch* which will be used to find users before authentication.

## Configure OpenSearch authentication

Check failure on line 76 in _posts/2023-12-14-configure-ldap-authentication.md

View workflow job for this annotation

GitHub Actions / style-job

[vale] reported by reviewdog 🐶 [OpenSearch.HeadingCapitalization] 'Configure OpenSearch authentication' is a heading and should be in sentence case. Raw Output: {"message": "[OpenSearch.HeadingCapitalization] 'Configure OpenSearch authentication' is a heading and should be in sentence case.", "location": {"path": "_posts/2023-12-14-configure-ldap-authentication.md", "range": {"start": {"line": 76, "column": 4}}}, "severity": "ERROR"}

In order to authenticate users using our directory (verifying that users are who they say they are), we add a new entry in the `config.dynamic.authc` section of the `opensearch-security/config.yml` file:

```yaml
config:
# [...]
dynamic:
# [...]
authc:
# [...]
example_ldap:
description: "Authenticate using example.com LDAP"
http_enabled: true
transport_enabled: false
order: 4
http_authenticator:
type: basic
challenge: false
authentication_backend:
type: ldap
config:
# enable ldaps
enable_ssl: false
# enable start tls, enable_ssl should be false
enable_start_tls: true
# send client certificate
enable_ssl_client_auth: false
# verify ldap hostname
verify_hostnames: true
hosts:
- ldap.example.com
# Anonymous binding is not allowed, bind as opensearch
bind_dn: cn=opensearch,ou=services,dc=example,dc=com
password: secret
# Base DN to search for users
userbase: 'ou=people,dc=example,dc=com'
# Filter to search for users (currently in the whole subtree beneath userbase)
# {0} is substituted with the username
usersearch: '(uid={0})'
# Use this attribute from the user as username (if not set then DN is used)
username_attribute: cn
```
Refer to the [Active Directory and LDAP](https://opensearch.org/docs/latest/security/authentication-backends/ldap/) page of the Security plugin for details about the configuration.
Here we connect to a directory using STARTTLS, bind as a specific user with a password, and find users by their *uid* and use their *cn* attribute as username.

Check failure on line 121 in _posts/2023-12-14-configure-ldap-authentication.md

View workflow job for this annotation

GitHub Actions / style-job

[vale] reported by reviewdog 🐶 [OpenSearch.Spelling] Error: uid. If you are referencing a setting, variable, format, function, or repository, surround it with tic marks. Raw Output: {"message": "[OpenSearch.Spelling] Error: uid. If you are referencing a setting, variable, format, function, or repository, surround it with tic marks.", "location": {"path": "_posts/2023-12-14-configure-ldap-authentication.md", "range": {"start": {"line": 121, "column": 114}}}, "severity": "ERROR"}

Check failure on line 121 in _posts/2023-12-14-configure-ldap-authentication.md

View workflow job for this annotation

GitHub Actions / style-job

[vale] reported by reviewdog 🐶 [OpenSearch.Spelling] Error: cn. If you are referencing a setting, variable, format, function, or repository, surround it with tic marks. Raw Output: {"message": "[OpenSearch.Spelling] Error: cn. If you are referencing a setting, variable, format, function, or repository, surround it with tic marks.", "location": {"path": "_posts/2023-12-14-configure-ldap-authentication.md", "range": {"start": {"line": 121, "column": 134}}}, "severity": "ERROR"}
After updating the configuration with `securityadmin.sh` (see above), you should be able to sign-in with any user of the LDAP directory.

For now, once signed-in, almost any action will result in a *permission denied* error because we have not configured permissions yet.
If you cannot sign-in, check the OpenSearch logs for errors, update the configuration and retry.
There is no point in continuing if you cannot sign-in at this stage.

## Inspect current permissions

When signed-in, you can check your roles and backend roles by clicking on your avatar in the top-right corner, and clicking on *View roles and identities*.
A pop-up display *roles* and *backend roles*.

![Screenshot showing the "View roles and identities" link](/assets/media/blog-images/2023-12-14-configure-ldap-authentication/view_roles_and_identities.png){:class="img-centered"}

![Screenshot of the pop-up displaying roles (own\_index) and backend roles (none)](/assets/media/blog-images/2023-12-14-configure-ldap-authentication/roles_backend_roles.png){:class="img-centered"}

*Roles* give actual permissions.
These permissions are managed using the Security plugin under Management, Security, Roles.
There, *roles* are also mapped to *backend roles*, that is users belonging to a *backend role* belong to the mapped *role* and have the associated permissions.

*Backend roles* correspond to LDAP groups.

For now, we have not configured authorization in the Security plugin, as a consequence the user does not belong to any *backend role* and only belong to a single role: *own_index* which is automatically added to all users in the default configuration.

Check failure on line 144 in _posts/2023-12-14-configure-ldap-authentication.md

View workflow job for this annotation

GitHub Actions / style-job

[vale] reported by reviewdog 🐶 [OpenSearch.Spelling] Error: own_index. If you are referencing a setting, variable, format, function, or repository, surround it with tic marks. Raw Output: {"message": "[OpenSearch.Spelling] Error: own_index. If you are referencing a setting, variable, format, function, or repository, surround it with tic marks.", "location": {"path": "_posts/2023-12-14-configure-ldap-authentication.md", "range": {"start": {"line": 144, "column": 170}}}, "severity": "ERROR"}

## Link roles and backend roles

We have two groups in our directory, *users* and *admins*.

Check warning on line 148 in _posts/2023-12-14-configure-ldap-authentication.md

View workflow job for this annotation

GitHub Actions / style-job

[vale] reported by reviewdog 🐶 [OpenSearch.OxfordComma] Add an Oxford comma in 'directory, users and admins.'. Raw Output: {"message": "[OpenSearch.OxfordComma] Add an Oxford comma in 'directory, users and admins.'.", "location": {"path": "_posts/2023-12-14-configure-ldap-authentication.md", "range": {"start": {"line": 148, "column": 1}}}, "severity": "WARNING"}
All users are members of the *users* group, and administrators are also members of the *admins* group.
For this simple example, we want all authenticated users to have a read-only access to everything, and administrators to have a read-write access to everything.

In order to do this, we must map the *users* backend role to the *readall* and *kibana\_users* roles, and the *admins* backend role to the *all_access* role.

While this can be conveniently done using the OpenSearch Dashboards user interface, changes will be lost if you update the configuration with `securityadmin.sh` in the future (what we will be doing in a next step).
We will therefore edit `opensearch-security/roles_mapping.yml` to update the mappings to fit our needs:

```yaml
all_access:
reserved: false
backend_roles:
- "admin"
- "admins" # <--- added
description: "Maps admin to all_access"
kibana_user:
reserved: false
backend_roles:
- "kibanauser"
- "users" # <--- added
description: "Maps kibanauser to kibana_user"
readall:
reserved: false
backend_roles:
- "readall"
- "users" # <--- added
```

## Configure OpenSearch authorization

The last step consist in configuring OpenSearch authorization (verifying that users are permitted to do what they are trying to do).

In OpenSearch, this is done by looking-up the *backend roles* of users from the LDAP groups they belong to.
For *authentication* we added a `config.dynamic.authc` entry above, now for *authorization* we will add a `config.dynamic.authz` entry into `opensearch-security/config.yml`.

The `config.dynamic.authc` and `config.dynamic.authz` entries have a lot in common (we use the same directory, so the connection settings are the same), and only differ in how the groups of an user must be gathered:

```yaml
config:
# [...]
dynamic:
# [...]
authz:
# [...]
roles_from_exapmle_ldap:
description: "Authorize using example.com LDAP"
http_enabled: true
transport_enabled: false
authorization_backend:
type: ldap
config:
# enable ldaps
enable_ssl: false
# enable start tls, enable_ssl should be false
enable_start_tls: true
# send client certificate
enable_ssl_client_auth: false
# verify ldap hostname
verify_hostnames: true
hosts:
- ldap.example.com
# Anonymous binding is not allowed, bind as opensearch
bind_dn: cn=opensearch,ou=services,dc=example,dc=com
password: secret
# Base DN to search for roles
rolebase: 'ou=groups,dc=example,dc=com'
# Filter to search for roles (currently in the whole subtree beneath rolebase)
# {0} is substituted with the DN of the user
# {1} is substituted with the username
# {2} is substituted with an attribute value from user's directory entry, of the authenticated user. Use userroleattribute to specify the name of the attribute
rolesearch: '(uniqueMember={0})'
# Specify the name of the attribute which value should be substituted with {2} above
userroleattribute: null
# Roles as an attribute of the user entry
userrolename: disabled
#userrolename: memberOf
# The attribute in a role entry containing the name of that role, Default is "name".
# Can also be "dn" to use the full DN as rolename.
rolename: cn
# Resolve nested roles transitive (roles which are members of other roles and so on ...)
resolve_nested_roles: true
# Base DN to search for users
userbase: 'ou=people,dc=example,dc=com'
# Filter to search for users (currently in the whole subtree beneath userbase)
# {0} is substituted with the username
usersearch: '(uid={0})'
```

After updating the configuration with `securityadmin.sh` (see above), signed-in users should have the expected roles and backend roles:

![Screenshot of the pop-up displaying roles (own\_index, kibana\_user, readall) and backend roles (users)](/assets/media/blog-images/2023-12-14-configure-ldap-authentication/roles_backend_roles2.png){:class="img-centered"}


## Conclusion

In this article, we saw how to proceed to configure step by step LDAP authentication and authorization, checking it is working at each stage.

Now that this basic configuration is in place, you can set up your own roles with permissions that match your site policies, and map them to your own LDAP groups through role backends.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 196bb4d

Please sign in to comment.