Elasticsearch OpenSearch LDAP Authentication & Active Directory

By Opster Expert Team - Gustavo

Updated: Sep 20, 2023

| 5 min read

This article is part of a series:

Quick links:

Introduction and background

In this article, we will explore how to use Active Directory (AD) via Lightweight Directory Access Protocol (LDAP). The example files can be found here.

What is Active Directory? 

Active Directory (AD) is a Microsoft implementation service which serves the purpose of locating, securing, managing, and organizing computer and network resources. These resources include files, users, groups, peripherals and network devices.

One of the main benefits is the centralization of resource management. AD simplifies the administrator’s tasks, and provides a set of security tools for permission management. 

Lightweight directory access protocol (LDAP) is one of the protocols supported by Active Directory and allows users to locate data about the mentioned resources.

Configuring LDAP Authentication

Configuring LDAP Authentication

We are going to use docker-compose to configure a nLDAP server, an LDAP admin interface, and a single node OpenSearch cluster

The files we need to configure are: 

  • Config.yml
  • Internal_users.yml
  • Roles_mapping.yml
  • Directory.ldif

config.yml

This file contains LDAP details and credentials:

---
_meta:
 type: "config"
 config_version: 2
 
config:
 dynamic:
   http:
     anonymous_auth_enabled: false
   authc:
     internal_auth:
       order: 0
       description: "HTTP basic authentication using the internal user database"
       http_enabled: true
       transport_enabled: true
       http_authenticator:
         type: basic
         challenge: false
       authentication_backend:
         type: internal
     ldap_auth:
       order: 1
       description: "Authenticate using LDAP"
       http_enabled: true
       transport_enabled: true
       http_authenticator:
         type: basic
         challenge: false
       authentication_backend:
         type: ldap
         config:
           enable_ssl: false
           enable_start_tls: false
           enable_ssl_client_auth: false
           verify_hostnames: true
           hosts:
           - openldap:389
           bind_dn: cn=readonly,dc=example,dc=org
           password: changethistoo
           userbase: ou=People,dc=example,dc=org
           usersearch: (cn={0})
           username_attribute: cn
 
   authz:
     ldap_roles:
       description: "Authorize using LDAP"
       http_enabled: true
       transport_enabled: true
       authorization_backend:
         type: ldap
         config:
           enable_ssl: false
           enable_start_tls: false
           enable_ssl_client_auth: false
           verify_hostnames: true
           hosts:
           - openldap:389
           bind_dn: cn=readonly,dc=example,dc=org
           password: changethistoo
           userbase: ou=People,dc=example,dc=org
           usersearch: (cn={0})
           username_attribute: cn
           skip_users:
             - admin
             - kibanaserver
           rolebase: ou=Groups,dc=example,dc=org
           rolesearch: (uniqueMember={0})
           userroleattribute: null
           userrolename: disabled
           rolename: cn
           resolve_nested_roles: false

LDAP supports both authentication (user and password validation) and authorization (map LDAP groups to OpenSearch roles.

The config blocks are almost the same for both authc and authz. You just need to provide the bind_dn, and the user/roles base.

internal_users.yml

Here we override the initial users to only have admin and kibanaserver users available, and take all the rest of the users from the LDAP server.

---
# This is the internal user database
# The hash value is a bcrypt hash and can be generated with plugin/tools/hash.sh
 
_meta:
 type: "internalusers"
 config_version: 2
 
admin:
 hash: "$2a$12$VcCDgh2NDk07JGN0rjGbM.Ad41qVR/YFJcgHp0UGns5JDymv..TOG"
 reserved: true
 backend_roles:
 - "admin"
 description: "Demo admin user"
 
kibanaserver:
 hash: "$2a$12$4AcgAt3xwOWadA5s5blL6ev39OXDNhmOesEoo33eZtrq2N0YrU3H."
 reserved: true
 description: "Demo kibanaserver user"

roles_mapping.yml

This file contains the instructions to assign OpenSearch roles to the LDAP users depending on the group.

---
 
_meta:
 type: "rolesmapping"
 config_version: 2
 
all_access:
 reserved: false
 backend_roles:
 - "admin"
 - "Administrator"
 description: "Maps admin to all_access"
 
own_index:
 reserved: false
 users:
 - "*"
 description: "Allow full access to an index named like the username"
 
kibana_user:
 reserved: false
 backend_roles:
 - "kibanauser"
 - "Developers"
 description: "Maps kibanauser to kibana_user"
 
readall:
 reserved: false
 backend_roles:
 - "readall"
 - "Developers"
 
manage_snapshots:
 reserved: false
 backend_roles:
 - "snapshotrestore"
 - "Developers"
 
kibana_server:
 reserved: true
 users:
 - "kibanaserver"

For example, the “Developers” group will have read access to all the documents, and administrators will have access to do anything.

directory.ldif

This file contains the LDAP components (users, groups) and will be useful to fill our users’ database.

We will add the following:

Users:

Gllermaly -> Administrator
Jsmith -> Developer

# — OUs ————————————-

dn: ou=Groups,dc=example,dc=org
objectClass: organizationalunit
objectClass: top
ou: Groups

dn: ou=People,dc=example,dc=org
objectClass: organizationalunit
objectClass: top
ou: People

# — People ———————————-

dn: cn=gllermaly,ou=People,dc=example,dc=org
objectClass: person
objectClass: inetOrgPerson
objectClass: organizationalPerson
objectClass: top
cn: gllermaly
userpassword: password
givenname: Gustavo
sn: Llermaly
mail: gustavo@llermaly.com
uid: 1001

dn: cn=jsmith,ou=People,dc=example,dc=org
objectClass: person
objectClass: inetOrgPerson
objectClass: organizationalPerson
objectClass: top
cn: jsmith
userpassword: password
givenname: John
sn: Smith
mail: john@smith.com
uid: 1002

# — Groups ———————————-

dn: cn=Administrator,ou=Groups,dc=example,dc=org
objectClass: groupofuniquenames
objectClass: top
ou: Groups
cn: Administrator
uniquemember: cn=gllermaly, ou=People, dc=example,dc=org

dn: cn=Developers,ou=Groups,dc=example,dc=org
objectClass: groupofuniquenames
objectClass: top
ou: Groups
cn: Developers
uniquemember: cn=gllermaly, ou=People, dc=example,dc=org
uniquemember: cn=jsmith, ou=People, dc=example,dc=org

Putting it all together

Now you can create the docker-compose file that will use all these files. Put everything in the same folder:

version: '3'
services:
 opensearch-ldap-node1:
   image: opensearchproject/opensearch:2.2.0
   container_name: opensearch-ldap-node1
   environment:
     - cluster.name=opensearch-ldap-cluster
     - node.name=opensearch-ldap-node1
     - discovery.seed_hosts=opensearch-ldap-node1
     - cluster.initial_master_nodes=opensearch-ldap-node1
     - bootstrap.memory_lock=true # along with the memlock settings below, disables swapping
     - "OPENSEARCH_JAVA_OPTS=-Xms512m -Xmx512m" # minimum and maximum Java heap size, recommend setting both to 50% of system RAM
   ulimits:
     memlock:
       soft: -1
       hard: -1
     nofile:
       soft: 65536 # maximum number of open files for the OpenSearch user, set to at least 65536 on modern systems
       hard: 65536
   volumes:
     - opensearch-ldap-data1:/usr/share/opensearch/data
     - ./config.yml:/usr/share/opensearch/plugins/opensearch-security/securityconfig/config.yml
     - ./internal_users.yml:/usr/share/opensearch/plugins/opensearch-security/securityconfig/internal_users.yml
     - ./roles_mapping.yml:/usr/share/opensearch/plugins/opensearch-security/securityconfig/roles_mapping.yml
   ports:
     - 9200:9200
     - 9600:9600 # required for Performance Analyzer
   networks:
     - opensearch-net
 openldap:
   image: osixia/openldap
   container_name: openldap
   command: --copy-service # seemingly required to load directory.ldif
   ports:
     - 389:389
     - 636:636
   environment:
     - LDAP_ADMIN_PASSWORD=changethis
     - LDAP_READONLY_USER=true
     - LDAP_READONLY_USER_PASSWORD=changethistoo
   volumes:
     - ./directory.ldif:/container/service/slapd/assets/config/bootstrap/ldif/custom/directory.ldif
   networks:
     - opensearch-net
 openldap-admin:
   image: osixia/phpldapadmin
   container_name: openldap-admin
   ports:
     - 6444:443
   environment:
     - PHPLDAPADMIN_LDAP_HOSTS=openldap
   networks:
     - opensearch-net
 
volumes:
 opensearch-ldap-data1:
 
networks:
 opensearch-net:

Now run docker-compose up and wait for the containers to be ready.

The last step is to apply the changes in the security settings running the built in script. SSH Into the node and run the following commands:

1

"/usr/share/opensearch/plugins/opensearch-security/tools/securityadmin.sh" -f "/usr/share/opensearch/plugins/opensearch-security/securityconfig/config.yml" -icl -key "/usr/share/opensearch/config/kirk-key.pem" -cert "/usr/share/opensearch/config/kirk.pem" -cacert "/usr/share/opensearch/config/root-ca.pem" -nhnv

2

"/usr/share/opensearch/plugins/opensearch-security/tools/securityadmin.sh" -f "/usr/share/opensearch/plugins/opensearch-security/securityconfig/internal_users.yml" -icl -key "/usr/share/opensearch/config/kirk-key.pem" -cert "/usr/share/opensearch/config/kirk.pem" -cacert "/usr/share/opensearch/config/root-ca.pem" -nhnv

3

"/usr/share/opensearch/plugins/opensearch-security/tools/securityadmin.sh" -f "/usr/share/opensearch/plugins/opensearch-security/securityconfig/roles_mapping.yml" -icl -key "/usr/share/opensearch/config/kirk-key.pem" -cert "/usr/share/opensearch/config/kirk.pem" -cacert "/usr/share/opensearch/config/root-ca.pem" -nhnv

Now you can run queries authenticating with the LDAP users.

This query should succeed as gllermaly user is an Administrator

curl -XPUT 'https://localhost:9200/new-index/_doc/1' -H 'Content-Type: application/json' -d '{"title": "My document"}' -u 'gllermaly:password' -k

This one will fail because the jsmith user does not have write permissions:

curl -XPUT 'https://localhost:9200/new-index/_doc/1' -H 'Content-Type: application/json' -d '{"title": "My document"}' -u 'jsmith:password' -k

You can go to localhost:6444 to see a Web administrator of the LDAP server. 

To dive deeper into user roles, please see Access Control – Users, Roles and Permissions.

Conclusion

In just a few steps you can add LDAP authentication to OpenSearch and integrate your existing users. Using role mappings you can easily align some of the LDAP users properties to OpenSearch cluster roles.

This configuration applies both for LDAP and Active directory servers.