5e9cbd3ecf
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
110 lines
7.4 KiB
Markdown
110 lines
7.4 KiB
Markdown
# 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.
|
||
|
||
> **`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 change** — `0.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.3`→`0.1.5` jump also crosses the `0.1.4` `ExpiresUtc` verifier change.
|
||
>
|
||
> **Current state (verified 2026-07-18):** feed carries `0.1.0`–`0.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 `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 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 small `IConstraintPolicy` (shared-contract Q3).
|
||
- Shared JWT/refresh helper or not (#7).
|