Files
scadaproj/components/auth/GAPS.md
T
Joseph Doherty 5e9cbd3ecf fix(deps)!: pin patched SQLitePCLRaw in Secrets + Auth; publish Secrets 0.1.3, Auth 0.1.5
Packing Secrets 0.1.3 surfaced NU1903: Microsoft.Data.Sqlite 10.0.7 pulls
SQLitePCLRaw.lib.e_sqlite3 2.1.11, which carries high-severity advisory
GHSA-2m69-gcr7-jv3q. Auth.ApiKeys had the same exposure and was already shipped
at 0.1.4 to three consumers, so both libs are fixed rather than only the one
being published.

Fixed the way ZB.MOM.WW.LocalDb already had it: CentralPackageTransitivePinningEnabled
plus a pin to the patched 2.1.12. Bumping Microsoft.Data.Sqlite does not help --
even 10.0.10 still resolves 2.1.11 -- so the pin is the actual fix. The pin reaches
consumers: both nuspecs now declare SQLitePCLRaw.lib.e_sqlite3 >= 2.1.12 as a direct
dependency, verified by restoring the published packages into a scratch project,
which resolves 2.1.12 and scans clean.

Auth goes 0.1.4 -> 0.1.5 with no API change (0.1.4 plus the pin). Suites re-run
after the native-lib swap with identical counts, so no behavioral regression:
Secrets 97 pass/1 skip, Auth 215 pass/1 skip (both skips are Windows-only DPAPI
and opt-in LDAP).

Consumers are NOT yet bumped and remain on vulnerable versions: mxaccessgw 0.1.4,
HistorianGateway 0.1.4, ScadaBridge 0.1.3. Secrets consumers all sit at 0.1.2 and
also lack KEK rotation.

Claude-Session: https://claude.ai/code/session_01BL2Vu1ESDQ9SCN4gVKkdts
2026-07-18 03:14:25 -04:00

7.4 KiB
Raw Blame History

Auth — gaps & adoption backlog

Divergence of each project from spec/SPEC.md, and the ordered backlog to reach the shared ZB.MOM.WW.Auth library. Status legend: gap · 🟡 partial · matches.

0.1.5 PUBLISHED 2026-07-18 — security fix. Auth.ApiKeys 0.1.4 and earlier ship a high-severity transitive vuln: Microsoft.Data.Sqlite 10.0.7 pulls SQLitePCLRaw.lib.e_sqlite3 2.1.11 (GHSA-2m69-gcr7-jv3q). 0.1.5 pins the patched 2.1.12 (same approach as ZB.MOM.WW.LocalDb); the .nuspec declares it directly, so consumers get the fix on bump without pinning themselves. No API change0.1.5 is 0.1.4 plus the pin. All three ApiKeys consumers are still on vulnerable versions (mxaccessgw 0.1.4, HistorianGateway 0.1.4, ScadaBridge 0.1.3) → bump to 0.1.5. Note ScadaBridge's 0.1.30.1.5 jump also crosses the 0.1.4 ExpiresUtc verifier change.

Current state (verified 2026-07-18): feed carries 0.1.00.1.5; consumer pins are OtOpcUa 0.1.1, ScadaBridge 0.1.3, MxAccessGateway 0.1.4, HistorianGateway 0.1.4 (a fourth consumer this file predates, taking all four packages). OtOpcUa is three minors behind — it does not get the 0.1.4 API-key-expiry enforcement. Library suite: 215 passing, 1 skipped, green 2026-07-18.

ADOPTED 2026-06-02 (local-only). The full backlog (#1#8) was implemented across all three apps on each repo's feat/adopt-zb-auth branch — committed + spec/code-reviewed, then merged to each repo's local default (main/master) and PUSHED to origin (gitea) on 2026-06-03 (in sync; feat/* kept locally). Shared Auth.Ldap + Auth.ApiKeys (ScadaBridge inbound re-architected to keyId/Bearer), IGroupRoleMapper<TRole>, Transport-enum config, canonical ZbClaimTypes/ZbCookieDefaults, unified dev base DN dc=zb,dc=local, and the canonical-six roles (with ScadaBridge's accepted auditor/admin SoD collapse). Consumer pins: OtOpcUa 0.1.1, MxGateway 0.1.2, ScadaBridge 0.1.3. Detail: docs/plans/2026-06-02-auth-audit-normalization*.md. The /🟡 cells below describe the PRE-adoption divergence (kept for history).

Divergence vs spec

§1 LDAP config schema

Spec key OtOpcUa MxAccessGateway ScadaBridge
Section nesting 🟡 Authentication:Ldap (nested) MxGateway:Ldap (nested) flat ScadaBridge:Security:Ldap*
Transport enum UseTls bool UseTls bool LdapTransport enum
AllowInsecure 🟡 AllowInsecureLdap 🟡 AllowInsecureLdap 🟡 AllowInsecureLdap (rename)
UserNameAttribute UserNameAttribute UserNameAttribute LdapUserIdAttribute
GroupAttribute memberOf memberOf 🟡 LdapGroupAttribute (rename)
dev SearchBase dc=lmxopcua,dc=local dc=lmxopcua,dc=local dc=scadabridge,dc=local

Gap A1: adopt the Transport enum in OtOpcUa + gateway (replace UseTls). → Gap A2: ScadaBridge: nest keys + rename LdapUserIdAttributeUserNameAttribute, LdapGroupAttributeGroupAttribute. → Gap A3: unify the dev base DN (dc=lmxopcua vs dc=scadabridge) — pick one shared GLAuth base.

All three do bind-then-search; ScadaBridge has the most complete hygiene (RFC-4514 + filter escaping, per-op timeout, fail-closed on group lookup, username trim, service-account-bind distinction). 🟡 OtOpcUa/gateway: confirm each has filter escaping + fail-closed-on-group-lookup parity. → Gap B1: make ScadaBridge's hygiene the shared baseline; backfill any missing checks.

§3 group→role mapping

Mechanism split: OtOpcUa + gateway map in config (GroupToRole); ScadaBridge maps in the database (LdapGroupMapping). → Gap C1: IGroupRoleMapper<CanonicalRole> must support both backings; ship a config-backed and a DB/delegate-backed mapper.

Role vocabulary now standardized to the canonical six (spec/CANONICAL-ROLES.md); native enforcement stays per-project. → Gap C2: implement the canonical → native expansion in each project. ⚠ Removing Auditor collapses ScadaBridge AuditReadOnly→Viewer and Audit→Administrator, losing its auditor/admin separation-of-duties (accepted). OtOpcUa lacks a first-class Deployer (publish ⊂ FleetAdmin); ScadaBridge has no Operator/Engineer; mxaccessgw no Designer/Deployer — each project assigns only the applicable subset.

§4 API-key contract

OtOpcUa MxAccessGateway ScadaBridge
Has API keys n/a (OPC UA transport security) mxgw_…, SQLite, scopes + constraints 🟡 X-API-Key, per-method approval (Inbound API only)
Peppered HMAC-SHA256
Constant-time compare
Token format <prefix>_<id>_<secret> raw X-API-Key (no keyId/prefix structure)
Audit log append-only 🟡 (verify)

Gap D1: extract mxaccessgw's pipeline as ZB.MOM.WW.Auth.ApiKeys. → Gap D2: ScadaBridge Inbound API adopts it; reconcile token format and model "per-method approval" as the opaque constraint policy.

Cookie names differ (MxGatewayDashboard vs ZB.MOM.WW.ScadaBridge.Auth vs OtOpcUa control-plane cookie); claim-type conventions differ. → Gap E1: define canonical claim types + cookie defaults in ZB.MOM.WW.Auth.AspNetCore; each app keeps its own cookie name but shares attributes/claims.

§6 dev / secrets

All never-log-secrets and pepper-external. 🟡 escape-hatch flag names vary. Covered by A3 (base DN) + E.

Adoption backlog (ordered)

# Item Projects Priority Effort Risk Notes
1 Extract ZB.MOM.WW.Auth.Ldap from ScadaBridge's hardened impl all 3 High M Med security-sensitive; needs strong tests before cutover
2 Extract ZB.MOM.WW.Auth.ApiKeys from mxaccessgw Model A gw, SB High M Med gateway adopts first (it's the source), then SB
3 IGroupRoleMapper<TRole> seam + config & DB mappers (Gap C1) all 3 High S Low unblocks per-project role retention
4 Config migration to §1 schema (Gaps A1A2) all 3 Med S Low mechanical; do with #1
5 ZB.MOM.WW.Auth.AspNetCore claims/cookie conventions (Gap E1) all 3 (UIs) Med S Low incl. OtOpcUa Blazor Admin UI control-plane
6 Unify dev GLAuth base DN (Gap A3) all 3 Low S Low dev-only; touches fixtures/infra
7 Decide shared JWT/refresh helper vs per-project SB (+?) Low S Low only if a 2nd project wants the same
8 Adopt canonical roles: canonical → native mapping per project (Gap C2) all 3 Med M Med governance (assign canonical role per LDAP group org-wide) + each project's expansion; SB audit roles collapse

Sequencing: #3 first (cheap, unblocks), then #1 and #2 in parallel (independent libraries), then #4#5 alongside cutover, then #6#7 as cleanup. Each extraction lands behind tests in the source project before any consumer migrates. This stays consistent with the repos' loose coupling: adoption is opt-in per project, one consumer version-bump at a time.

Decisions still open

  • Shared dev base DN value (A3).
  • Whether constraints stay opaque object? or get a small IConstraintPolicy (shared-contract Q3).
  • Shared JWT/refresh helper or not (#7).