4e01cbc2c3
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.
101 lines
5.3 KiB
Markdown
101 lines
5.3 KiB
Markdown
# 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`](../../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
|
|
|
|
```bash
|
|
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.
|