85 lines
6.9 KiB
Markdown
85 lines
6.9 KiB
Markdown
# 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 ~226–288):
|
||
|
||
- **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` (~15–23, ~374–410).
|
||
|
||
## 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.
|