Introduction

LDAP (Lightweight Directory Access Protocol) and Active Directory are the backbone of enterprise identity management. Ansible's community.general collection provides modules for managing LDAP entries directly — create OUs, provision users, manage groups, reset passwords, and configure Linux hosts to authenticate against LDAP/AD via SSSD. This guide covers both OpenLDAP and Active Directory operations.

Prerequisites

# Install collection
ansible-galaxy collection install community.general

# Python dependency on controller
pip install python-ldap

LDAP Module Reference

ModulePurpose
community.general.ldap_entryCreate/delete LDAP entries
community.general.ldap_attrsModify attributes on existing entries
community.general.ldap_searchSearch LDAP directory
community.general.ldap_passwdSet LDAP passwords

Connection Variables

# group_vars/all/ldap.yml
ldap_uri: ldaps://ldap.example.com
ldap_base: dc=example,dc=com
ldap_bind_dn: cn=admin,dc=example,dc=com
ldap_bind_pw: "{{ vault_ldap_admin_password }}"

Create Organizational Units

---
- name: Create LDAP OUs
  hosts: localhost
  gather_facts: false
  tasks:
    - name: Create OUs
      community.general.ldap_entry:
        dn: "ou={{ item }},{{ ldap_base }}"
        objectClass:
          - organizationalUnit
        attributes:
          description: "{{ item | capitalize }} OU"
        server_uri: "{{ ldap_uri }}"
        bind_dn: "{{ ldap_bind_dn }}"
        bind_pw: "{{ ldap_bind_pw }}"
        state: present
      loop:
        - people
        - groups
        - services
        - departments

Provision Users

Single User

- name: Create LDAP user
  community.general.ldap_entry:
    dn: "uid=jdoe,ou=people,{{ ldap_base }}"
    objectClass:
      - inetOrgPerson
      - posixAccount
      - shadowAccount
    attributes:
      cn: John Doe
      sn: Doe
      givenName: John
      mail: jdoe@example.com
      uid: jdoe
      uidNumber: "10001"
      gidNumber: "10000"
      homeDirectory: /home/jdoe
      loginShell: /bin/bash
      userPassword: "{{ vault_jdoe_password | password_hash('ldap_salted_sha1') }}"
    server_uri: "{{ ldap_uri }}"
    bind_dn: "{{ ldap_bind_dn }}"
    bind_pw: "{{ ldap_bind_pw }}"
    state: present

Bulk User Provisioning

- name: Provision users from list
  community.general.ldap_entry:
    dn: "uid={{ item.username }},ou=people,{{ ldap_base }}"
    objectClass:
      - inetOrgPerson
      - posixAccount
      - shadowAccount
    attributes:
      cn: "{{ item.first_name }} {{ item.last_name }}"
      sn: "{{ item.last_name }}"
      givenName: "{{ item.first_name }}"
      mail: "{{ item.email }}"
      uid: "{{ item.username }}"
      uidNumber: "{{ item.uid }}"
      gidNumber: "{{ item.gid | default('10000') }}"
      homeDirectory: "/home/{{ item.username }}"
      loginShell: "{{ item.shell | default('/bin/bash') }}"
      departmentNumber: "{{ item.department | default('') }}"
      title: "{{ item.title | default('') }}"
    server_uri: "{{ ldap_uri }}"
    bind_dn: "{{ ldap_bind_dn }}"
    bind_pw: "{{ ldap_bind_pw }}"
    state: present
  loop: "{{ ldap_users }}"
  loop_control:
    label: "{{ item.username }}"
  no_log: true
# vars/users.yml
ldap_users:
  - username: jdoe
    first_name: John
    last_name: Doe
    email: jdoe@example.com
    uid: "10001"
    department: Engineering
    title: Senior Developer

  - username: asmith
    first_name: Alice
    last_name: Smith
    email: asmith@example.com
    uid: "10002"
    department: Operations
    title: DevOps Engineer

Manage Groups

- name: Create LDAP group
  community.general.ldap_entry:
    dn: "cn=developers,ou=groups,{{ ldap_base }}"
    objectClass:
      - posixGroup
      - groupOfNames
    attributes:
      gidNumber: "10100"
      description: "Development team"
      member: "uid=jdoe,ou=people,{{ ldap_base }}"
    server_uri: "{{ ldap_uri }}"
    bind_dn: "{{ ldap_bind_dn }}"
    bind_pw: "{{ ldap_bind_pw }}"
    state: present

- name: Add members to group
  community.general.ldap_attrs:
    dn: "cn=developers,ou=groups,{{ ldap_base }}"
    attributes:
      member: "uid={{ item }},ou=people,{{ ldap_base }}"
    server_uri: "{{ ldap_uri }}"
    bind_dn: "{{ ldap_bind_dn }}"
    bind_pw: "{{ ldap_bind_pw }}"
    state: present
  loop:
    - jdoe
    - asmith
    - bwilson

- name: Remove member from group
  community.general.ldap_attrs:
    dn: "cn=developers,ou=groups,{{ ldap_base }}"
    attributes:
      member: "uid=bwilson,ou=people,{{ ldap_base }}"
    server_uri: "{{ ldap_uri }}"
    bind_dn: "{{ ldap_bind_dn }}"
    bind_pw: "{{ ldap_bind_pw }}"
    state: absent

Password Management

- name: Reset user password
  community.general.ldap_passwd:
    dn: "uid={{ item.username }},ou=people,{{ ldap_base }}"
    passwd: "{{ item.new_password }}"
    server_uri: "{{ ldap_uri }}"
    bind_dn: "{{ ldap_bind_dn }}"
    bind_pw: "{{ ldap_bind_pw }}"
  loop: "{{ password_resets }}"
  no_log: true

- name: Set password expiry attributes
  community.general.ldap_attrs:
    dn: "uid={{ item }},ou=people,{{ ldap_base }}"
    attributes:
      shadowMax: "90"
      shadowWarning: "14"
      shadowLastChange: "{{ (ansible_date_time.epoch | int / 86400) | int }}"
    server_uri: "{{ ldap_uri }}"
    bind_dn: "{{ ldap_bind_dn }}"
    bind_pw: "{{ ldap_bind_pw }}"
    state: exact
  loop: "{{ ldap_users | map(attribute='username') | list }}"

Search LDAP

- name: Find all users in Engineering
  community.general.ldap_search:
    dn: "ou=people,{{ ldap_base }}"
    filter: "(&(objectClass=inetOrgPerson)(departmentNumber=Engineering))"
    attrs:
      - uid
      - cn
      - mail
    server_uri: "{{ ldap_uri }}"
    bind_dn: "{{ ldap_bind_dn }}"
    bind_pw: "{{ ldap_bind_pw }}"
  register: engineering_users

- name: Display users
  ansible.builtin.debug:
    msg: "{{ item.cn }}: {{ item.mail }}"
  loop: "{{ engineering_users.results }}"

Configure SSSD (Linux → LDAP/AD Auth)

---
- name: Configure SSSD for LDAP authentication
  hosts: linux_servers
  become: true
  tasks:
    - name: Install SSSD packages
      ansible.builtin.package:
        name:
          - sssd
          - sssd-ldap
          - sssd-tools
          - oddjob-mkhomedir
        state: present

    - name: Deploy SSSD config
      ansible.builtin.template:
        src: sssd.conf.j2
        dest: /etc/sssd/sssd.conf
        mode: '0600'
        owner: root
      notify: restart sssd

    - name: Enable SSSD and mkhomedir
      ansible.builtin.command: >
        authselect select sssd with-mkhomedir --force
      changed_when: true

    - name: Enable oddjobd for home directory creation
      ansible.builtin.service:
        name: oddjobd
        state: started
        enabled: true

    - name: Start SSSD
      ansible.builtin.service:
        name: sssd
        state: started
        enabled: true

  handlers:
    - name: restart sssd
      ansible.builtin.service:
        name: sssd
        state: restarted
# templates/sssd.conf.j2
[sssd]
domains = example.com
services = nss, pam, ssh
config_file_version = 2

[domain/example.com]
id_provider = ldap
auth_provider = ldap
chpass_provider = ldap

ldap_uri = {{ ldap_uri }}
ldap_search_base = {{ ldap_base }}
ldap_default_bind_dn = {{ ldap_bind_dn }}
ldap_default_authtok = {{ ldap_bind_pw }}

ldap_user_search_base = ou=people,{{ ldap_base }}
ldap_group_search_base = ou=groups,{{ ldap_base }}

ldap_tls_reqcert = demand
ldap_tls_cacert = /etc/ssl/certs/ca-certificates.crt

cache_credentials = true
enumerate = false

# Home directory
fallback_homedir = /home/%u
default_shell = /bin/bash

# Access control — only allow specific groups
access_provider = ldap
ldap_access_filter = (memberOf=cn=linux-users,ou=groups,{{ ldap_base }})

Active Directory Specific

# SSSD config for AD (templates/sssd-ad.conf.j2)
[domain/example.com]
id_provider = ad
auth_provider = ad
access_provider = ad

ad_domain = example.com
ad_server = dc01.example.com, dc02.example.com
krb5_realm = EXAMPLE.COM

ldap_id_mapping = true
fallback_homedir = /home/%u@%d
default_shell = /bin/bash

# Allow only specific AD groups
ad_access_filter = (memberOf=CN=Linux Users,OU=Groups,DC=example,DC=com)
- name: Join AD domain
  ansible.builtin.command: >
    realm join --user=admin@EXAMPLE.COM example.com
  args:
    creates: /etc/krb5.keytab
  no_log: true

Deprovisioning

- name: Disable LDAP user
  community.general.ldap_attrs:
    dn: "uid={{ item }},ou=people,{{ ldap_base }}"
    attributes:
      loginShell: /usr/sbin/nologin
      shadowExpire: "0"
    server_uri: "{{ ldap_uri }}"
    bind_dn: "{{ ldap_bind_dn }}"
    bind_pw: "{{ ldap_bind_pw }}"
    state: exact
  loop: "{{ disabled_users }}"

- name: Delete LDAP user
  community.general.ldap_entry:
    dn: "uid={{ item }},ou=people,{{ ldap_base }}"
    server_uri: "{{ ldap_uri }}"
    bind_dn: "{{ ldap_bind_dn }}"
    bind_pw: "{{ ldap_bind_pw }}"
    state: absent
  loop: "{{ deleted_users }}"

Troubleshooting

Test LDAP Connection

- name: Test LDAP search
  community.general.ldap_search:
    dn: "{{ ldap_base }}"
    filter: "(objectClass=organization)"
    server_uri: "{{ ldap_uri }}"
    bind_dn: "{{ ldap_bind_dn }}"
    bind_pw: "{{ ldap_bind_pw }}"
  register: ldap_test

- ansible.builtin.debug:
    var: ldap_test.results

SSSD Cache Issues

- name: Clear SSSD cache
  ansible.builtin.command: sss_cache -E
  changed_when: true

Conclusion

Ansible manages the full LDAP/AD lifecycle — create OUs, provision users in bulk, manage group membership, reset passwords, and configure Linux hosts for LDAP authentication via SSSD. Use community.general.ldap_entry for creating entries, ldap_attrs for modifying attributes, and ldap_search for querying. Store user lists in variables files for declarative user management where adding or removing a user is just editing a YAML list and running the playbook.