[Auth] Live-verify user-bind identity + AD forest checks against real AD before publishing 0.4.4 #51

Closed
opened 2026-11-09 15:26:49 -05:00 by dohertj2 · 1 comment
Owner

Before publishing Auth 0.4.4 (scadaproj#40, #50): live-verify the user-bind identity checks against a real AD — the "Who am I?" bound-identity match (#40) and the msDS-PrincipalName / unique-sAMAccountName forest check (#50) were tested only against GLAuth and unit fakes. Include BTDB Manager's single-domain User-mode setup (NAM AD). Also consider: a misconfigured SQL Server key-store section still throws on first store creation outside VerifyAsync (no startup validation, to avoid breaking hosts that register but don't configure it); LocalDb snapshot rows dead-lettered past MaxSnapshotDeferredRows or as orphans are never re-sent (no re-request path).

Before publishing Auth 0.4.4 (scadaproj#40, #50): live-verify the user-bind identity checks against a real AD — the "Who am I?" bound-identity match (#40) and the msDS-PrincipalName / unique-sAMAccountName forest check (#50) were tested only against GLAuth and unit fakes. Include BTDB Manager's single-domain User-mode setup (NAM AD). Also consider: a misconfigured SQL Server key-store section still throws on first store creation outside VerifyAsync (no startup validation, to avoid breaking hosts that register but don't configure it); LocalDb snapshot rows dead-lettered past MaxSnapshotDeferredRows or as orphans are never re-sent (no re-request path).
dohertj2 added the type:testpriority:p2 labels 2026-11-09 15:26:49 -05:00
dohertj2 added the blocked label 2026-11-10 08:41:38 -05:00
Author
Owner

Live-verified against the NAM AD on 2026-11-12, then published as Auth 0.5.0. 0.5.0 carries 0.4.2–0.4.4 plus the new passwordless ILdapGroupLookup (ScadaBridge#46). Commits f5ef0ef (code) and f98ba08 (index) are on main.

Method. A scratch harness drove the real LdapAuthService (0.5.0 source) against uswap-vdc-nam1/2. It bound with the user's own NAM account (permission given for this) and the service account svc_igntadmin. Both passwords came from the Keychain through the winrm CLI's keyring and were passed to the process environment only. The output was scanned for both secrets: no leak. The user's account received one wrong-password attempt in total.

User-bind identity (#40): PASS

  • BindMode=User, NAM\{0}, LDAPS 636 with failover to nam2: sign-in succeeds, with the same 176 groups as ServiceAccount mode. This is BTDB Manager's production shape: User mode, LDAPS, sAMAccountName, down-level template.
  • The UPN template {0}@nam.zimmer.com succeeds. A typed NAM\name is accepted, and refused with no bind when AcceptDomainQualifiedNames=false. A wrong password gives BadCredentials.
  • Raw checks: AD answers Who-am-I as u:NAM\<sAMAccountName>. A base read returns msDS-PrincipalName equal to that answer. Another account's msDS-PrincipalName differs (negative control).
  • Non-vacuity: with the library's msDS-PrincipalName comparison mutated to a wrong domain, every user-bind sign-in fails GroupLookupFailed (DC and GC) while the ServiceAccount paths stay green. That comparison is what decides against real AD.

AD forest (#50): PASS

  • The forest root is DC=zimmer,DC=com. The user's sAMAccountName has exactly 1 holder across the forest in the Global Catalog.
  • User-mode sign-in against the GC (LDAPS 3269) with the forest-wide base succeeds. The mutation above also fails it, so the GC serves msDS-PrincipalName too.
  • A same-name account in another domain wasn't available to test the refusal case live. Unit fakes cover that case.

Service-account path (0.4.4) and the new lookup: PASS

  • ServiceAccount sign-in: 176 groups, canonical username.
  • GetUserGroupsAsync returns the same groups, username and display name, and canonicalises an upper-cased name. Unknown name gives UserNotFound. A real disabled AD account gives UserDisabled.
  • A dead primary fails over to nam1 or nam2. The sticky second lookup took ~250 ms.
  • All endpoints dead, or a nonexistent service account, gives DirectoryUnavailable. StartTLS 389 works. User mode gives NotSupported.

Considered, not done (outside the acceptance): a misconfigured SQL Server key-store section still throws on first resolve, which is documented in the README. The LocalDb dead-letter re-request path belongs to the LocalDb lib.

Feed: all five packages are at 0.5.0, and registration lists 0.5.0. SHA256 of each downloaded copy matches the local pack:

  • Abstractions 6de01b87…3b913
  • Ldap 10229fcd…0aee
  • ApiKeys 761e9153…41c67c4
  • ApiKeys.SqlServer ed77ea8c…6e117
  • AspNetCore 175ffcd2…d9ce9
Live-verified against the NAM AD on 2026-11-12, then published as **Auth 0.5.0**. 0.5.0 carries 0.4.2–0.4.4 plus the new passwordless `ILdapGroupLookup` (ScadaBridge#46). Commits `f5ef0ef` (code) and `f98ba08` (index) are on `main`. **Method.** A scratch harness drove the real `LdapAuthService` (0.5.0 source) against `uswap-vdc-nam1/2`. It bound with the user's own NAM account (permission given for this) and the service account `svc_igntadmin`. Both passwords came from the Keychain through the winrm CLI's `keyring` and were passed to the process environment only. The output was scanned for both secrets: no leak. The user's account received one wrong-password attempt in total. **User-bind identity (#40)**: PASS - `BindMode=User`, `NAM\{0}`, LDAPS 636 with failover to nam2: sign-in succeeds, with the same 176 groups as ServiceAccount mode. This is BTDB Manager's production shape: User mode, LDAPS, `sAMAccountName`, down-level template. - The UPN template `{0}@nam.zimmer.com` succeeds. A typed `NAM\name` is accepted, and refused with no bind when `AcceptDomainQualifiedNames=false`. A wrong password gives `BadCredentials`. - Raw checks: AD answers Who-am-I as `u:NAM\<sAMAccountName>`. A base read returns `msDS-PrincipalName` equal to that answer. Another account's `msDS-PrincipalName` differs (negative control). - **Non-vacuity:** with the library's `msDS-PrincipalName` comparison mutated to a wrong domain, every user-bind sign-in fails `GroupLookupFailed` (DC and GC) while the ServiceAccount paths stay green. That comparison is what decides against real AD. **AD forest (#50)**: PASS - The forest root is `DC=zimmer,DC=com`. The user's `sAMAccountName` has exactly 1 holder across the forest in the Global Catalog. - User-mode sign-in against the GC (LDAPS 3269) with the forest-wide base succeeds. The mutation above also fails it, so the GC serves `msDS-PrincipalName` too. - A same-name account in another domain wasn't available to test the refusal case live. Unit fakes cover that case. **Service-account path (0.4.4) and the new lookup**: PASS - ServiceAccount sign-in: 176 groups, canonical username. - `GetUserGroupsAsync` returns the same groups, username and display name, and canonicalises an upper-cased name. Unknown name gives `UserNotFound`. **A real disabled AD account gives `UserDisabled`.** - A dead primary fails over to nam1 or nam2. The sticky second lookup took ~250 ms. - All endpoints dead, or a nonexistent service account, gives `DirectoryUnavailable`. StartTLS 389 works. User mode gives `NotSupported`. **Considered, not done (outside the acceptance):** a misconfigured SQL Server key-store section still throws on first resolve, which is documented in the README. The LocalDb dead-letter re-request path belongs to the LocalDb lib. **Feed:** all five packages are at `0.5.0`, and registration lists 0.5.0. SHA256 of each downloaded copy matches the local pack: - Abstractions `6de01b87…3b913` - Ldap `10229fcd…0aee` - ApiKeys `761e9153…41c67c4` - ApiKeys.SqlServer `ed77ea8c…6e117` - AspNetCore `175ffcd2…d9ce9`
Sign in to join this conversation.