95 lines
6.3 KiB
Markdown
95 lines
6.3 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.
|
||
|
||
> **✅ 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).
|