# 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`; 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.