Files
scadaproj/components/auth/current-state/otopcua/CURRENT-STATE.md
T

85 lines
6.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Auth — current state: OtOpcUa
Repo: `~/Desktop/OtOpcUa` (Gitea `lmxopcua`). Stack: .NET 10, OPC Foundation UA stack.
All paths below are relative to the repo root. Verified against source on 2026-06-01.
OtOpcUa has the richest auth of the three: OPC UA session-level identity, LDAP-backed
authentication, transport security profiles, and a trie-based per-operation ACL system,
plus a separate control-plane (Admin UI) auth stack.
## 1. Authentication
Three OPC UA identity-token types are accepted at session establishment
(`src/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/OpcUaApplicationHost.cs`, impersonation handler ~226288):
- **Anonymous** — passes through without custom validation; gets no LDAP groups.
- **UserName/password (LDAP-backed)** — the primary human path (see below).
- **X.509 certificate** — validated at the secure-channel/PKI level; CN→role mapping not yet implemented.
**UserName flow:** the SDK decrypts the password with the server certificate, then
`IOpcUaUserAuthenticator.AuthenticateUserNameAsync()` validates it.
- Seam: `src/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/Security/IOpcUaUserAuthenticator.cs`
- Prod impl: `src/Server/ZB.MOM.WW.OtOpcUa.Host/OpcUa/LdapOpcUaUserAuthenticator.cs` → wraps `ILdapAuthService`.
- UserName tokens are **always encrypted** by the SDK (via the server cert) regardless of transport profile, so LDAP login works even on a `None` endpoint.
**LDAP service** (`src/Server/ZB.MOM.WW.OtOpcUa.Security/Ldap/`):
- `ILdapAuthService` / `LdapAuthService.cs` — bind-then-search (or direct bind), re-bind as user DN to verify the password, read `memberOf`, strip leading `CN=`. GLAuth fallback extracts the primary group from the `ou=` RDN.
- `LdapOptions.cs` — bound from config section **`Authentication.Ldap`**. Keys: `Enabled`, `Server`, `Port` (GLAuth `3893`), `UseTls`, `AllowInsecureLdap`, `SearchBase` (`dc=lmxopcua,dc=local`), `ServiceAccountDn`, `ServiceAccountPassword`, `UserNameAttribute` (`cn`; AD→`sAMAccountName`), `GroupAttribute` (`memberOf`), `DisplayNameAttribute`, `GroupToRole` (dict), `DevStubMode` (dev-only: accepts any non-empty creds).
- `RoleMapper.cs` — maps LDAP groups → control-plane `AdminRole` via `GroupToRole`.
- Dev LDAP server: GLAuth at `C:\publish\glauth\` (see `C:\publish\glauth\auth.md`).
## 2. Transport security
`OpcUaServer` config section; `EnabledSecurityProfiles` (default `[None, Basic256Sha256-Sign, Basic256Sha256-SignAndEncrypt]`; Aes128/Aes256 profiles also available), resolved by `SecurityProfileResolver` at startup. Server certificate auto-created under `PkiStoreRoot` (`own/ issuer/ trusted/ rejected/`). `AutoAcceptUntrustedClientCertificates` (default `false`). See `docs/security.md`.
- Profile enum + policy build: `OpcUaApplicationHost.cs` (~1523, ~374410).
## 3. Authorization (data-plane ACLs — stays bespoke)
Bitmask permissions in `src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Enums/NodePermissions.cs`:
`Browse, Read, Subscribe, HistoryRead, WriteOperate, WriteTune, WriteConfigure, AlarmRead,
AlarmAcknowledge, AlarmConfirm, AlarmShelve, MethodCall` + bundles `ReadOnly/Operator/Engineer/Admin`.
The three write tiers mirror Galaxy `SecurityClassification` (Operate/Tune/Configure).
- Scope hierarchy: `NodeAclScopeKind.cs``Cluster, Namespace, UnsArea, UnsLine, Equipment, FolderSegment, Tag`.
- Grant entity: `src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/NodeAcl.cs``(LdapGroup, ScopeKind, ScopeId, PermissionFlags)`, generation-versioned.
- Evaluation: `src/Core/ZB.MOM.WW.OtOpcUa.Core/Authorization/``TriePermissionEvaluator.cs`, `PermissionTrie.cs`, `PermissionTrieCache.cs`, `PermissionTrieBuilder.cs`, `IPermissionEvaluator.cs`. Per-operation (`OpcUaOperation` enum); denials return `BadUserAccessDenied`.
- Additive-only grants (no Deny) in Phase 6.2.
**Control-plane (Admin UI) roles are independent** of data-plane ACLs (design decision #150):
`AdminRole` enum (`ConfigViewer / ConfigEditor / FleetAdmin`); `LdapGroupRoleMapping` entity maps groups→AdminRole. Cookie + JWT stack lives in `src/Server/ZB.MOM.WW.OtOpcUa.Security/` (`ServiceCollectionExtensions.cs`, `Endpoints/AuthEndpoints.cs``/login`,`/logout`,`/ping`; `Jwt/JwtTokenService.cs`; `Blazor/CookieAuthenticationStateProvider.cs`). DataProtection keys persisted in the Config DB so cookies survive failover.
## 4. Session / identity model (stays bespoke)
`src/Core/ZB.MOM.WW.OtOpcUa.Core/Authorization/UserAuthorizationState.cs`:
`SessionId, ClusterId, LdapGroups, MembershipResolvedUtc, AuthGenerationId, MembershipVersion`,
`MembershipFreshnessInterval` (5 min, async refresh) and `AuthCacheMaxStaleness` (15 min, fail-closed).
Sessions are **generation-bound** at sign-in so grant changes can't take effect mid-session.
Auth is evaluated **per request**, not cached per session.
## 5. Secrets & config
`Authentication.Ldap.ServiceAccountPassword` should come from user-secrets/env, not source.
DataProtection keys in Config DB table `DataProtectionKeys`. Docs: `docs/security.md`, `docs/v2/acl-design.md`.
## 6. Notable limits / TODOs
No explicit Deny grants (Phase 6.2); no nested-group expansion (relies on directory flattening);
LDAP unreachable >15 min fails all sessions closed; X.509 CN→role mapping deferred;
`HistoryUpdate` currently mapped to the `HistoryRead` bit.
---
## Adoption plan → `ZB.MOM.WW.Auth`
**Replace with the shared library:**
- `LdapAuthService` + `LdapOptions` + `RoleMapper``ZB.MOM.WW.Auth.Ldap` (`ILdapAuthService`, `LdapAuthService`, `LdapOptions`, `LdapAuthResult`) + `IGroupRoleMapper<CanonicalRole>`; OtOpcUa expands each canonical role into its `AdminRole` (control-plane) and `NodePermissions` (data-plane) per [`../../spec/CANONICAL-ROLES.md`](../../spec/CANONICAL-ROLES.md). No first-class `Deployer` (publish ⊂ `FleetAdmin`).
- Migrate config from `Authentication.Ldap.*` to the canonical schema in [`../../spec/SPEC.md`](../../spec/SPEC.md) (notably `UseTls` → the canonical transport setting; `UserNameAttribute` keeps its name as the canonical one).
- **Control-plane Admin UI** (Blazor, `src/Server/ZB.MOM.WW.OtOpcUa.Security/`): adopt `ZB.MOM.WW.Auth.AspNetCore` for the canonical cookie/claim conventions + DI helpers (`ServiceCollectionExtensions.cs`, `Blazor/CookieAuthenticationStateProvider.cs`, `Endpoints/AuthEndpoints.cs`). This is OtOpcUa's HTTP auth surface — distinct from the OPC UA data plane below.
**Keep bespoke (thin adapter only):**
- `IOpcUaUserAuthenticator` / `LdapOpcUaUserAuthenticator` — keep as the OPC-UA-specific adapter that calls the shared `ILdapAuthService`.
- ALL of §3 authZ (`NodePermissions`, ACL trie, `NodeAcl`), the control-plane `AdminRole` vocabulary, the JWT/`DataProtection` specifics, and §4 session model — domain-specific, not extracted (only the cookie/claim *conventions* are shared via `.AspNetCore`).
- Transport security (§2) — OPC-UA-specific.
**No API-key work** — OtOpcUa has no API-key surface; it relies on OPC UA transport security instead.