Ad Onprem Ldap
Manage on-premises Active Directory users and groups from macOS or Linux over LDAPS with a bun CL…
by julianobarbosa · MIT
The skill
Manage on-premises Active Directory users and groups from macOS or Linux over LDAPS with a bun CLI, with no PowerShell, no RSAT and no Windows host. USE WHEN finding an AD user or group, listing group members (including nested), listing a user's groups, checking whether an account is disabled, locked or has an expired password, adding or removing group members, creating users or groups, disabling, enabling or unlocking accounts, moving objects between OUs, resetting an AD password, or changing a group's scope; also when someone asks for the Get-ADUser / Add-ADGroupMember / Set-ADAccountPassword equivalent on a Mac. NOT FOR Entra ID / Microsoft Graph objects (cloud-only users, SOA-converted groups), Azure RBAC, or deleting AD objects.
ad-onprem-ldap
One CLI, Tools/Ad.ts, speaks LDAP over TLS straight to the domain controllers. It does what the
RSAT ActiveDirectory cmdlets do for users and groups, and it works anywhere bun runs.
Setup
- Network path to a DC on 636/TCP (usually the corporate VPN) and DNS that resolves the domain.
bun installinsideTools/.op(1Password CLI) signed in. The bind password is read withop readinto memory only.- A config at
~/.config/ad-onprem/config.json(orAD_ONPREM_CONFIG), shaped likeconfig.example.json. It holds the domain, base DN, DCs, bind account, secret reference, CA bundle path and the OU allowlist for creates and moves. It never belongs in a repo. - The CA bundle (root, then any issuing CA) that signed the DC certificates, in PEM. Confirm the root fingerprint with the directory team before trusting it.
Commands
| Command | Does |
|---|---|
find-user <id> / find-user --prefix <text> |
One user by sAMAccountName, UPN or DN, or up to 50 by prefix |
find-group <id> |
Group, decoded scope and type, direct member count |
group-members <group> [--recursive] |
Direct members (ranged, complete), or the nested closure |
user-groups <user> [--recursive] |
Direct memberOf, or every group reached through nesting |
user-status <user> |
Enabled, locked, password expired, last logon, bad password count |
add-member / remove-member <group> <member>... |
Change membership (members may be users, groups or computers) |
create-user <sam> --ou --given --sn [--display] [--upn] |
New user, created disabled |
create-group <sam> --ou [--scope] [--type] [--description] |
New group, global security by default |
disable / enable / unlock <user> |
Account state |
move <object> --to <ou> |
Move to another OU |
reset-password <user> [--must-change] [--vault] |
New random password, stored in 1Password; only the op:// ref is printed |
set-group-scope <group> --scope <scope> |
Scope change; refuses global <-> domain local, which AD only allows via universal |
Run as bun Tools/Ad.ts <command> .... Output is JSON. Exit codes: 0 ok, 1 failed, 2 usage or
config, 3 no DC reachable, 4 not found or ambiguous, 5 refused by a safety gate.
Write contract
- Every write prints a dry-run plan (target DN, exact change, current state) and changes
nothing until it is re-run with
--apply. Show the plan to the principal and get a yes before applying. - Applied writes read the result back over the same connection (same DC, so replication lag
cannot fake success) and append one line to
~/.local/state/ad-onprem/audit.jsonl. - Creates and moves only land inside
writeOuAllowlist. - Objects with
adminCount=1(privileged, AdminSDHolder-protected) are refused unless--allow-protectedis passed. - Delete is deliberately not implemented. Disable and move to a holding OU instead.
- New users start disabled:
create-user, thenreset-password, thenenable.
Gotchas
- LDAPS only. AD refuses
unicodePwdwrites over plain 389, and a simple bind on 389 sends the password in clear. The config rejects port 389. Never setrejectUnauthorized: false; fix the CA bundle instead. - A wrong bind password is never retried on another DC. Each attempt counts toward the lockout threshold of the bind account.
- The bind name and the stored UPN can differ. AD accepts
sAMAccountName@domainat bind time even whenuserPrincipalNameis something else, so look the account up by sAMAccountName when a UPN search returns nothing. lockoutTime > 0does not mean locked. The lockout may already have expired.user-statusreads the constructedmsDS-User-Account-Control-Computed, which AD returns only on a base-scope read of the object.memberandmemberOfomit the primary group (normally Domain Users, viaprimaryGroupID).group-membersanduser-groupsadd it back; a hand-rolledldapsearchwill not.- Bit flags are swapped, not replaced.
disable,enableandset-group-scopedelete the value they read and add the new one in a single modify, so AD refuses the write if someone changed the attribute in between. Re-run on that error; nothing was overwritten. - After
--apply, the write is audited before it is read back. A failed read-back shows up asafter.readBackErrorwithmode: applied, never as a failed command. - Large groups truncate
membersilently past MaxValRange (1500 values by default).group-membersuses ranged retrieval; a hand-rolledldapsearchmust too. - Scope changes go through universal. Global and domain local never convert directly.
- Synced objects lag in Entra. A change made here shows up in Entra ID only after the next directory sync cycle, so don't verify it through Microsoft Graph right away.
- No DC reachable (exit 3) almost always means the VPN dropped or the resolver in use does not serve the AD domain. Check DNS for the DC names before debugging TLS.
References
References/LdapMapping.md: cmdlet to LDAP mapping, attributes, and rawldapsearchrecipes.
Use this skill
Connect your agent to the Skill Store MCP server and ask for it by ref: julianobarbosa/ad-onprem-ldap-51520141423.
Details
- Publisher
- julianobarbosa
- Category
- Software Development › DevOps & cloud
- License
- MIT
- Updated
- 2026-10-14
References
More from julianobarbosa
- 1Password Guide for implementing 1Password secrets management - CLI operations, service accounts, Developer…
- Alz Accelerator Deploy Azure Landing Zones using the ALZ Accelerator with AVM (Azure Verified Modules).
- Argocdclusterbootstrapping Complete ArgoCD cluster bootstrapping skill for diagnosing sync failures, creating root Applicati…
- Argocdimageupdater Manage ArgoCD Image Updater configuration, drift resolution, and ImageUpdater CRDs.