Search icon
Script icon
If icon
Log icon
SlackIncomingWebhook icon
Fail icon
Pause icon
Modify icon
Schedule icon

Repair Duplicate LDAP uidNumber Collisions Safely

Repair duplicate POSIX uidNumber values behind approval, avoiding gidNumber reservations and failing if uniqueness is not restored.

Categories
Infrastructure

Two valid POSIX entries with one uidNumber are one kernel identity. A file owner, sudo rule or audit record can therefore be attributed to the wrong person even though both LDAP accounts still bind. This blueprint repairs that collision in place and leaves both entries valid.

How it works

  1. detect_people (io.kestra.plugin.ldap.Search) reads POSIX entries under people_base_dn with uidNumber, gidNumber, objectClass and createTimestamp.
  2. detect_inventory (io.kestra.plugin.ldap.Search) reads all uidNumber and gidNumber values under inventory_base_dn. The second search supplies the reservation set for allocation.
  3. analyze (io.kestra.plugin.scripts.python.Script) groups numeric uidNumber values, keeps the oldest creation timestamp and plans a new number for every newer entry. It refuses protected DNs, missing or tied timestamps, an empty inventory, plans above max_repairs and exhausted allocation ranges.
  4. findings_alert logs and posts the exact collision groups and planned target timestamps. Safe defaults never write.
  5. With auto_remediate true and dry_run false, approval_hold pauses with the target DNs, old and new numbers and both creation timestamps. After approval, recheck_people and recheck_inventory run again. recheck_plan refuses any target, keeper, duplicate membership or candidate reservation drift.
  6. remediate (io.kestra.plugin.ldap.Modify) applies the native LDIF only after the fresh plan passes. verify_people and verify_inventory run again and verify_result fails unless every duplicate is gone, every target has its planned number and no target number is a gidNumber.

What you get

  • A read-only collision report with the DNs and creation timestamps that make the keeper choice reviewable.
  • A bounded repair that changes only uidNumber on newer and unprotected entries.
  • Allocation checked against both user IDs and group IDs, with a post-write uniqueness assertion.
  • A stale-approval guard that re-reads the directory immediately before the native Modify.
  • Slack alerts for findings and failed runs.

Who it's for

  • Identity and Linux platform teams operating OpenLDAP or another LDAP directory with POSIX accounts.
  • Teams where shared NFS ownership, sudo matching or audit attribution depends on stable numeric identities.
  • Operators who need a scheduled audit but require every directory write to be reviewed and verified.

Why orchestrate this with Kestra

Repairing a collision by hand means two searches, sorting creation timestamps, checking group ID reservations, writing LDIF, approving a change and searching again. Kestra keeps those steps in one auditable execution with a disabled safe schedule, a human pause, native LDAP tasks and a failed run when the directory does not reach the expected state.

Tested end to end

Kestra 2.0.6 with the bundled LDAP, script, flow and Slack tasks and OpenLDAP 1.5.0. Every case below ran on the final revision and was confirmed with ldapsearch inside the directory container, separately from the flow's own reads. In the repair case the newer of two entries sharing uidNumber 12000 moved to 10001 while the keeper stayed at 12000 and every other entry was unchanged.

A bind that may read but not write the directory made ldap.Modify report SUCCESS while the server answered insufficient access rights. The flow's own collision and reservation searches failed the run and the entry kept its number, so the task state alone is never trusted. The bind password is a SECRET input.

Case Execution Result
Healthy 5rHqRnWMtlhtwtSHsvVyw7 SUCCESS. No duplicate, no write
Dry run with a duplicate 4dGGncZfFhnhnSmS4ddAdl SUCCESS. Collision reported, directory unchanged
Declined approval 67lkGuDhCkk7JT7oTyP5Zn SUCCESS. Newer entry still 12000, no write
Approved repair 5FeLuq3zzuAWXubHjRFAY7 SUCCESS. Newer entry 12000 to 10001, keeper and other entries unchanged, zero duplicates
Repeat 4sVy8JDKQJGHGheF1R35iO SUCCESS. No targets, no approval, no write
Write refused by the directory 1xSLQSSE6U0CCTcxexpp4M FAILED as intended. ldap.Modify reported SUCCESS on insufficient access rights, verification failed the run, entry unchanged
Target deleted during the Pause 40yp9YtN6SSSam0Zvbd4Q8 FAILED as intended. Stale plan refused before Modify
Bind password stored nowhere 4tZIplCqX5fJrks8GWQdaE SUCCESS. A distinct bind password was absent from the stored execution, its logs and the execution search
Fresh OpenLDAP: healthy 6pTyDjbfDmxvWaDUoCOSJu SUCCESS
Fresh OpenLDAP: dry run 4NsImBZqbDhhbPDLUI5jNy SUCCESS
Fresh OpenLDAP: declined 3J7mBDSjYvQpRdVK3uPrw5 SUCCESS
Fresh OpenLDAP: approved repair 5gFBlgyxHqs7XyX5aL3xff SUCCESS. 12000 to 10001, zero duplicates
Fresh OpenLDAP: repeat 6IYIlxNLiVjREytxmaeYHs SUCCESS
Fresh OpenLDAP: target deleted during the Pause 2o3zwPhw66e69plxqkXlhp FAILED as intended

Controls for protected DNs, allocation exhaustion, an empty inventory, Unicode DNs, a 512-entry inventory, concurrent executions and an LDAP outage ran on the previous revision, which differs only in the password input type.

Prerequisites

An LDAP directory reachable from Kestra, with a bind account allowed to search both bases and modify only the people entries that should be repaired. Entries need readable createTimestamp values and POSIX uidNumber attributes.

Secrets

  • LDAP_PASSWORD: bind password for bind_dn.
  • SLACK_WEBHOOK_URL: incoming webhook for findings and failure alerts.

Inputs

  • dry_run (default true) and auto_remediate (default false) guard every write.
  • ldap_host, ldap_port, bind_dn, people_base_dn and inventory_base_dn define the directory scope.
  • protected_dns is a semicolon-separated DN allowlist that is never modified.
  • allocation_floor and allocation_ceiling bound the first-free allocator; max_repairs bounds one write pass.
  • page_size enables paged searches for inventories larger than a server result limit.

Quick start

  1. Add LDAP_PASSWORD and SLACK_WEBHOOK_URL to the namespace.
  2. Run with defaults to report duplicate groups without writing.
  3. Review the DNs and timestamps, then run with dry_run false and auto_remediate true.
  4. Approve the pause only after confirming the target and keeper entries.
  5. Enable the schedule only after the report is trusted. Its inputs remain pinned to safe mode.

How to extend

  • Set people_base_dn to a single organizational unit when only one population owns a UID range.
  • Add known service or system DNs to protected_dns before enabling remediation.
  • Set allocation_floor and allocation_ceiling to the numeric range reserved by the platform team.
  • Keep inventory_base_dn broad enough to include every source of gidNumber, including group entries outside the people OU.

Links

See How

New to Kestra?

Use blueprints to kickstart your first workflows.