6.3 KiB
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.
✅ ADOPTED 2026-06-02 (local-only). The full backlog (#1–#8) was implemented across all three apps on each repo's
feat/adopt-zb-authbranch — 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). SharedAuth.Ldap+Auth.ApiKeys(ScadaBridge inbound re-architected to keyId/Bearer),IGroupRoleMapper<TRole>,Transport-enum config, canonicalZbClaimTypes/ZbCookieDefaults, unified dev base DNdc=zb,dc=local, and the canonical-six roles (with ScadaBridge's accepted auditor/admin SoD collapse). Consumer pins: OtOpcUa0.1.1, MxGateway0.1.2, ScadaBridge0.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 LdapUserIdAttribute→UserNameAttribute, LdapGroupAttribute→GroupAttribute.
→ Gap A3: unify the dev base DN (dc=lmxopcua vs dc=scadabridge) — pick one shared GLAuth base.
§2 bind-then-search
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.
§5 cookie / claim conventions
⛔ 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 A1–A2) | 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 smallIConstraintPolicy(shared-contract Q3). - Shared JWT/refresh helper or not (#7).