Active Directory Security and certificates
Audit LDAP clients before enforcement
Enable bounded diagnostic logging, interpret signing and CBT events, and turn client addresses into an application remediation list.
Collect against the question
Before tightening LDAP policy, find out which applications will fail, which identities they use and which LDAP libraries they run. Counting TCP 389 connections won’t tell you that: signed or sealed SASL and StartTLS also use that port. Start with the session and policy distinctions, then collect from every DC the clients can select.
Record the collection window, DC name/build, signing policy, CBT policy and diagnostic level. Include scheduled jobs, infrequent appliances and failover paths. Keep a list of application paths with client address, bind identity, owner, authentication method, target selection, proposed correction and retest result. One IP may be a proxy or host many processes.
Read signing events in their policy context
| Event | What it establishes |
|---|---|
| 2886 | Reminder that signing enforcement is not enabled |
| 2887 | A 24-hour summary of unprotected binds accepted while signing is not required |
| 2888 | A 24-hour summary of unprotected binds rejected while signing is required |
| 2889 | Per-client unsigned bind details when diagnostic level is at least 2 |
The detailed event contains the client address and attempted identity. Preserve the message and XML, including bind-type information where supplied. The same address might make both protected and unprotected connections. See Microsoft’s client discovery and signing procedure.
Enable detailed logging with a saved restoration value
This is a DC configuration change, performed locally in an elevated session by an authorized administrator. Coordinate with anyone collecting diagnostics on the same DC. The example records whether the value existed before setting it to level 2:
$key = 'HKLM:\SYSTEM\CurrentControlSet\Services\NTDS\Diagnostics'
$name = '16 LDAP Interface Events'
$previous = Get-ItemProperty -LiteralPath $key -ErrorAction Stop
$hadValue = $previous.PSObject.Properties.Name -contains $name
$oldValue = if ($hadValue) { $previous.$name } else { $null }
New-ItemProperty -LiteralPath $key -Name $name -PropertyType DWord `
-Value 2 -Force | Out-Null
Save the old value somewhere outside the session too, in case the shell is lost. Detailed logging can substantially increase Directory Service log volume, so check retention and forwarding before starting. Microsoft’s configuration guidance documents this switch. The restoration below puts back the recorded setting rather than assuming it started at zero.
CBT summaries and audit events are different
The policy and patch requirements matter as much as the event number:
| Event | Interpretation |
|---|---|
| 3039 | A TLS bind failed CBT validation; inspect the message for missing, malformed or mismatched CBT |
| 3040 | A 24-hour summary of unprotected LDAPS binds while CBT is Never |
| 3041 | A reminder that CBT validation is not enforced |
| 3074 | A client sent binding information that would fail validation under enforcement |
| 3075 | Missing channel-binding information relevant to stricter enforcement |
3039, 3074 and 3075 require When supported or Always and diagnostic level 2. Enabling diagnostic logging while leaving CBT at Never does not produce the same compatibility inventory. The later audit events require the relevant Server 2019/2022 updates; their former extra enablement step was removed by the January 2024 and November 2023 updates respectively. The exact event conditions and diagnostic flags are in KB4520412.
Changing Never to When supported can reject a capable client that sends an invalid token. It isn’t a passive audit switch. Server 2025’s CBT overview distinguishes this policy from Always and lists the supported event triggers.
Preserve events before summarizing them
From an administrative workstation with permission and event-log connectivity to the DC:
$events = Get-WinEvent -ComputerName 'dc1.contoso.com' -FilterHashtable @{
LogName = 'Directory Service'
Id = 2886,2887,2888,2889,3039,3040,3041,3074,3075
StartTime = (Get-Date).AddDays(-7)
}
$events | Select-Object MachineName,TimeCreated,Id,RecordId,Message
# Inspect the actual event schema before automating field extraction.
$events | Select-Object -First 1 | ForEach-Object { $_.ToXml() }
Keep the source DC and RecordId when deduplicating. A daily summary is not an individual failed connection; do not add those counts to per-client events and call the sum a client population. Use a secure event export for investigation, then create a smaller owner/remediation report from it. Bind identities and application topology are useful directory information.
Connect the event to a process and an owner
Use the client timestamp and source port, where available, to correlate a client-side trace or process/socket observation. Record the service account, scheduled task, appliance firmware, LDAP library and connection configuration. Test both normal operation and the application’s reconnect/failover behavior after fixing it.
A Java application, a Windows service using its own LDAP stack and a Windows component using system APIs need not behave alike. Correct the specific client’s signing, SASL, TLS trust/name validation or CBT support. Avoid treating a successful ldp.exe test from the same machine as proof that the original application changed.
Restore logging and verify the change
At the end of the agreed collection window, restore the saved state in the same session, after confirming no concurrent owner changed it:
if ($hadValue) {
Set-ItemProperty -LiteralPath $key -Name $name -Value $oldValue
} else {
Remove-ItemProperty -LiteralPath $key -Name $name
}
After fixing a client, run its workload under the intended signing/CBT policy and check the same DCs. Test successful authentication and the expected rejection of a controlled incompatible client. Fewer log entries alone won’t tell you whether the application now works correctly.