# 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 # full fidelity, service account from the environment: export ZB_LDAP_SVC_PASSWORD='...' dotnet run --project tools/LdapProbe -- --insecure # 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_*' 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 `** 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.