Initial commit: scadaproj umbrella — sister-project index, auth component normalization (design + GAPS), and the built ZB.MOM.WW.Auth shared library (0.1.0, flattened in).

This commit is contained in:
dohertj2
2026-06-01 03:59:23 -04:00
commit 37e23cf9f2
73 changed files with 6836 additions and 0 deletions
+85
View File
@@ -0,0 +1,85 @@
# Auth — gaps & adoption backlog
Divergence of each project from [`spec/SPEC.md`](spec/SPEC.md), and the ordered backlog to
reach the shared `ZB.MOM.WW.Auth` library. Status legend: ⛔ gap · 🟡 partial · ✅ matches.
## 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`](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 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).