Building an Enterprise LDAP Directory with OpenLDAP



Series: Centralized DB Authentication in the Wild | Part 2 of 9

Introduction

  • In Part 1, we looked at why local database authentication becomes difficult to manage as environments grow. Every database maintains its own users, passwords and roles, making onboarding, offboarding and access changes repetitive across multiple systems.

  • We'll deploy OpenLDAP using Docker, create an enterprise-style directory with users, service accounts and groups, and verify that the directory is ready for database integration. The same identity platform will be reused throughout the rest of this series.
  • Before connecting any database to a centralized authentication platform, we first need a directory service that every database can trust. That is what this article builds.

Note

  • This demo environment runs on a dedicated Linux host:  iam.dbacraft.com OpenLDAP and phpLDAPadmin both run in Docker on this host.
  • Always test in a non-production environment before deploying identity infrastructure changes to a live system.

Demo environment

Component Host Detail
OpenLDAP iam.dbacraft.com osixia/openldap:1.5.0, port 389
phpLDAPadmin iam.dbacraft.com osixia/phpldapadmin:0.9.0, port 8081
Base DN iam.dbacraft.com dc=dbacraft,dc=com
Database hosts db1.dbacraft.com PostgreSQL, MongoDB, MySQL, MariaDB 



Step 1: Deploy OpenLDAP and phpLDAPadmin

OpenLDAP provides the centralized directory service used throughout this series. phpLDAPadmin provides a web interface for browsing and managing the directory.

Deploy both services using the following Docker Compose file.

# Path: /opt/auth/ldap/docker-compose.yml
# Run on: iam.dbacraft.com
version: "3.8"
services:
  openldap:
    image: osixia/openldap:1.5.0
    container_name: dbacraft_openldap_389
    hostname: iam.dbacraft.com
    restart: unless-stopped
    environment:
      LDAP_ORGANISATION: "DBACraft Auth"
      LDAP_DOMAIN: "dbacraft.com"
      LDAP_BASE_DN: "dc=dbacraft,dc=com"
      LDAP_ADMIN_PASSWORD: "********"
      LDAP_CONFIG_PASSWORD: "********"
      LDAP_TLS: "false"  
    ports:
      - "389:389"
      - "636:636"
    volumes:
      - ldap_data:/var/lib/ldap
      - ldap_config:/etc/ldap/slapd.d

  phpldapadmin:
    image: osixia/phpldapadmin:0.9.0
    container_name: dbacraft_phpldapadmin_8081
    restart: unless-stopped
    environment:
      PHPLDAPADMIN_LDAP_HOSTS: "openldap"
      PHPLDAPADMIN_HTTPS: "false"
    ports:
      - "8081:80"
    depends_on:
      - openldap

volumes:
  ldap_data:
  ldap_config:

Start both containers.

root@iam:/opt/auth/ldap# docker compose up -d

Verify both containers are running.

root@iam:/opt/auth/ldap# docker ps



phpLDAPadmin is accessible at http://iam.dbacraft.com:8081








Step 2: Build the directory structure

An LDAP directory is organized as a hierarchical tree. The root of the directory is the Base DN and every object exists beneath it.

For this article, the directory starts with dc=dbacraft,dc=com and contains two organizational units.

  • ou=people stores user accounts and service accounts.
  • ou=groups stores authorization groups.
Keeping users and groups in separate organizational units makes the directory easier to manage and allows applications to search only the objects they need during authentication.

dc=dbacraft,dc=com
│
├── ou=people          ← all users and service accounts
└── ou=groups          ← all authorization groups

Create both organizational units using LDIF files with ldapadd or through the phpLDAPadmin web interface. Both methods produce the same directory structure.



Step 3: User naming convention

A consistent naming convention makes identities easier to manage, automate and audit. As the directory grows, predictable names simplify administration and reduce the chance of configuration mistakes.

For this article, every identity follows a standard naming pattern based on its role.

Identity type Naming pattern Examples
DBA users user_[name]_dba user_ahmad_dba, user_siti_dba, user_wei_dba
System administrators user_[name]_sysadmin user_farah_sysadmin, user_kumar_sysadmin
Developers user_[name]_dev user_arif_dev, user_mei_dev, user_daniel_dev
BI users user_[name]_bi user_zainab_bi, user_chong_bi, user_faiz_bi
Service accounts (read-only) svc_[system]_ro svc_billing_ro, svc_analytics_ro, svc_orders_ro
Service accounts (read-write) svc_[system]_rw svc_inventory_rw, svc_search_rw
Bind account svc_ldap_bind_ro Read-only account used by applications to search the directory

The _ro and _rw suffixes indicate the intended access level of a service account. Using descriptive names makes it easier to identify an account's purpose during routine administration, troubleshooting and security reviews.

The svc_ldap_bind_ro account is a dedicated read-only service account used by applications to search the directory during authentication. It has no administrative privileges and is separate from the LDAP administrator account. We'll use this account when integrating with database services.

Step 4: Create the users

The directory contains user accounts for database administrators, developers, system administrators, BI users and service accounts. Each user is created as an LDAP entry under ou=people using an LDIF file.

The example below shows the structure of a typical user account.

# Example: DBA user entry
dn: uid=user_ahmad_dba,ou=people,dc=dbacraft,dc=com
objectClass: inetOrgPerson
uid: user_ahmad_dba
cn: Ahmad
sn: DBA
mail: ahmad@dbacraft.com
userPassword: ********

Create the user entries using LDIF files and import them with ldapadd.

In addition to user accounts, the directory includes a dedicated read-only service account used by applications to search the directory during authentication.

# Dedicated read-only bind account for database platform integration
dn: uid=svc_ldap_bind_ro,ou=people,dc=dbacraft,dc=com
objectClass: inetOrgPerson
uid: svc_ldap_bind_ro
cn: LDAP Bind Service
sn: BindRO
mail: svc-ldap-bind@dbacraft.com
userPassword: ********

Step 5: Create the groups



Groups are used to organize users based on their responsibilities. Instead of managing permissions for individual accounts, users become members of one or more groups that represent their role within the organization.

For this article, we'll create the following groups.

Group CN Purpose Members
cn=db-admins Database administrators 6 DBA users
cn=server-admins Linux and infrastructure administrators 5 sysadmin users
cn=developers Application developers 5 developer users
cn=bi-users Reporting and analytics users 5 BI users
cn=service-accounts Application and automation service accounts 10 service accounts

Managing access through groups simplifies administration. When a user's responsibilities change, we can just update their group membership instead of managing individual accounts across multiple systems. As more databases are integrated into the directory, this group-based model scales far better than maintaining permissions for every user separately.

The following example shows the developers group.

dn: cn=developers,ou=groups,dc=dbacraft,dc=com
objectClass: groupOfNames
cn: developers
member: uid=user_arif_dev,ou=people,dc=dbacraft,dc=com
member: uid=user_mei_dev,ou=people,dc=dbacraft,dc=com
member: uid=user_daniel_dev,ou=people,dc=dbacraft,dc=com
member: uid=user_ravi_dev,ou=people,dc=dbacraft,dc=com
member: uid=user_lily_dev,ou=people,dc=dbacraft,dc=com

The same structure is used for every group in the directory, with the member entries referencing the users assigned to that group.

Step 6: Verify the directory

Use ldapsearch to verify that the directory has been created successfully. The output should include the organizational units, user accounts, service accounts and groups created in the previous steps.

root@iam:/opt/auth/ldap# ldapsearch -x -LLL \
  -D "cn=admin,dc=dbacraft,dc=com" -W \
  -b "dc=dbacraft,dc=com" dn

Example output:

dn: dc=dbacraft,dc=com

dn: ou=people,dc=dbacraft,dc=com
dn: ou=groups,dc=dbacraft,dc=com

dn: uid=user_ahmad_dba,ou=people,dc=dbacraft,dc=com
dn: uid=user_arif_dev,ou=people,dc=dbacraft,dc=com
dn: uid=user_zainab_bi,ou=people,dc=dbacraft,dc=com
dn: uid=svc_inventory_rw,ou=people,dc=dbacraft,dc=com
dn: uid=svc_ldap_bind_ro,ou=people,dc=dbacraft,dc=com

dn: cn=db-admins,ou=groups,dc=dbacraft,dc=com
dn: cn=developers,ou=groups,dc=dbacraft,dc=com
dn: cn=bi-users,ou=groups,dc=dbacraft,dc=com
dn: cn=server-admins,ou=groups,dc=dbacraft,dc=com
dn: cn=service-accounts,ou=groups,dc=dbacraft,dc=com

...

At this point, the directory structure is complete and ready to be used by database platforms. In the next article, we'll configure PostgreSQL to authenticate users against this directory.



Production considerations

Consideration Detail
Transport security This demo uses LDAP over port 389 for simplicity. In production, use LDAPS (636) or StartTLS to encrypt authentication traffic.
High availability Avoid a single LDAP server in production. Deploy replicas so authentication remains available if one server fails.
Backups Regularly back up the LDAP data and configuration. The directory becomes the central identity source for every connected database.
Bind account security Create a dedicated read-only bind account with the minimum required permissions. Do not reuse the LDAP administrator account for application authentication.
Naming convention Establish a consistent naming convention before creating users. Changing user IDs later can affect applications and database integrations that depend on them.

Wrapping Up

  • In this article, we deployed OpenLDAP and built a centralized enterprise directory using a structured hierarchy of users, service accounts and groups.
  • The directory is now ready to become the central identity source for the environments used throughout the rest of this series. By establishing consistent naming standards and group-based organization from the start, future integrations become much easier to manage.
  • From this point forward, we'll use the same directory as we integrate PostgreSQL, MongoDB, MySQL and MariaDB without rebuilding the identity infrastructure.

What is next

In Part 3, we'll integrate PostgreSQL with the directory created in this article. We'll configure LDAP authentication in pg_hba.conf, verify user authentication and examine how PostgreSQL handles authorization for LDAP-authenticated users.

References

Series: Centralized DB Authentication in the Wild

  • Part 1: Why Local Database Authentication Creates Operational Chaos
  • Part 2: Building an Enterprise LDAP Directory with OpenLDAP 
  • Part 3: Integrating PostgreSQL with a Centralized LDAP Directory (coming soon)
  • Part 4: MongoDB Enterprise LDAP Integration (coming soon)
  • Part 5: MySQL Enterprise LDAP Integration (coming soon)
  • Part 6: MariaDB LDAP Authentication using PAM and POSIX Groups (coming soon)
  • Part 7: Why LDAP Is Not Enough - Introducing Kerberos (coming soon)
  • Part 8: Certificate-Based Authentication Across PostgreSQL, MySQL, MariaDB and MongoDB (coming soon)
  • Part 9: Production Best Practices and Cross Platform Comparison (coming soon)

No comments:

Post a Comment