Diagnostic for pinning down the LDAP settings the four ZB.MOM.WW apps need against the Zimmer NAM domain, and for answering which AD groups can be mapped to app roles. Found the referral defect fixed in 0.2.1. Two independent halves in one run: Part A drives the REAL ZB.MOM.WW.Auth.LdapAuthService -- same bind-then-search- then-bind, same options validator, same failure enum -- so its verdict IS what the apps produce rather than an approximation of it. Part B asks the directory what the library cannot, which is where the group answer comes from: full group DNs, nested groups via LDAP_MATCHING_RULE_IN_CHAIN, the primary group (AD omits it from memberOf, so it can never be a role group), security vs distribution type, decoded userAccountControl, referral counts, and the TLS chain with key sizes. --list-groups '<pat>' enumerates every matching group in the directory, which a membership dump structurally cannot do -- it only ever shows one account's memberships. Passwords are prompted for and never echoed; they cannot be passed as arguments, which would put them in shell history. --self-bind lets the user under test stand in for the service account, so group discovery does not require the svc_igntnapp secret. Not packable, outside the Auth solution and its central package management, referencing the Auth projects directly so it tracks current source.
ldapprobe — ZB / NAM Active Directory auth + group probe
A throwaway diagnostic for pinning down the LDAP settings the four ZB.MOM.WW apps need
against the Zimmer NAM domain, and for answering the open question in
../../ldap_setup.md: which AD groups should we map to app roles?
The NAM directory has no established group→role convention (the Ignition gateway assigns
roles per-user inside the application and ignores AD groups entirely), while
ZB.MOM.WW.Auth.Ldap fails closed on zero groups — a user with a perfectly good
password is refused if memberOf comes back empty. This probe exists to close that gap
with data rather than guesswork.
Running it
cd ~/Desktop/scadaproj
# no service-account password to hand — bind as the user under test:
dotnet run --project tools/LdapProbe -- --insecure --self-bind <sAMAccountName>
# full fidelity, service account from the environment:
export ZB_LDAP_SVC_PASSWORD='...'
dotnet run --project tools/LdapProbe -- --insecure <sAMAccountName>
# which transports actually reach the DCs from here:
dotnet run --project tools/LdapProbe -- --sweep --insecure -u x
# what UG_WW_* groups exist across the whole directory:
dotnet run --project tools/LdapProbe -- --insecure --self-bind -g 'UG_WW_*' <sAMAccountName>
dotnet run --project tools/LdapProbe -- --help
Quote the pattern — an unquoted UG_WW_* is glob-expanded by the shell.
Passwords are always prompted for and never echoed. They cannot be passed on the command
line — that would put them in shell history. The service-account password may come from
$ZB_LDAP_SVC_PASSWORD instead.
Requires the Zimmer VPN to be up: the DCs are uswap-vdc-nam1 (10.201.216.2) and
uswap-vdc-nam2 (10.201.138.78).
What it reports
Part A drives the real ZB.MOM.WW.Auth.LdapAuthService, so its verdict is exactly
what ScadaBridge / OtOpcUa / MxGateway / HistorianGateway will produce — same bind-then-
search-then-bind flow, same options validator, same failure enum, same fail-closed rules.
Its five failure values are each explained in place, because ServiceAccountBindFailed
in particular collapses three very different causes (connect/TLS, service-account bind,
and the search itself) into one value.
Part B asks the directory things the library does not, which is where the group answer comes from:
| why it matters | |
|---|---|
| full user entry | userAccountControl decoded, so a disabled / locked / password-expired account is named rather than showing up as "bad credentials" |
direct memberOf |
the only set the shared library sees |
| primary group | AD omits it from memberOf — so it can never be used as a role group, and a user whose only group is Domain Users will always fail |
| transitive groups | resolved server-side via LDAP_MATCHING_RULE_IN_CHAIN; a group reachable only by nesting is flagged, because mapping one would silently grant nobody anything |
| group type | security vs distribution, and scope — decoded from groupType |
| referral count | a search based at the domain root makes AD return continuation referrals — the cause of the ServiceAccountBindFailed fixed in Auth 0.2.1 |
--list-groups <pattern> adds a third, independent section: every group in the
directory whose cn matches, not just the ones the test account belongs to. A membership
dump structurally cannot answer "what groups exist to choose from" — it only ever shows
one person's memberships, which for a long-tenured account skews heavily toward admin
groups. Each row shows the group type, its member count (EMPTY and N+ for AD's ranged
1500-value cap are both called out) and whether the probed account holds it.
It searches from the domain root by default, derived from the resolved user DN, because groups here live in at least three different containers and a narrower base under-reports without saying so.
It closes with a ready-to-paste appsettings block, and — when the library failed
system-side while a direct query succeeded — automatically retries Part A against the
user's own container to test whether the domain-root search base is the culprit.
--insecure
The DC certificates chain to the internal zimmer-rootca, which is not in a dev
machine's trust store, so an honest run fails TLS validation. --insecure bypasses
validation for diagnosis only and the probe says so on every run. The certificate
inspector still prints the real chain, key size and SslPolicyErrors either way, so you
can see precisely why validation failed rather than guessing.
Note the two hazards are separate: an untrusted root affects every client, whereas the RSA 1024-bit DC keys are rejected only by OpenSSL at its default security level — i.e. by Debian-based .NET containers, not by macOS or Windows hosts.
Before deploying, fix trust properly: install zimmer-rootca (+ zimmer-issueca1) into
the host/container trust store, or pin the chain via
LdapOptions.ServerCertificateValidationCallback. The emitted config block always sets
AllowInsecure: false for that reason.
Not shipped
This is a diagnostic, deliberately outside the ZB.MOM.WW.Auth solution and its central
package management: IsPackable=false, no CI, no tests. It references the Auth projects
directly rather than the 0.2.0 feed package so it always tracks current source.