6.9 KiB
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→ wrapsILdapAuthService. - UserName tokens are always encrypted by the SDK (via the server cert) regardless of transport profile, so LDAP login works even on a
Noneendpoint.
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, readmemberOf, strip leadingCN=. GLAuth fallback extracts the primary group from theou=RDN.LdapOptions.cs— bound from config sectionAuthentication.Ldap. Keys:Enabled,Server,Port(GLAuth3893),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-planeAdminRoleviaGroupToRole.- Dev LDAP server: GLAuth at
C:\publish\glauth\(seeC:\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 (OpcUaOperationenum); denials returnBadUserAccessDenied. - 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 itsAdminRole(control-plane) andNodePermissions(data-plane) per../../spec/CANONICAL-ROLES.md. No first-classDeployer(publish ⊂FleetAdmin).- Migrate config from
Authentication.Ldap.*to the canonical schema in../../spec/SPEC.md(notablyUseTls→ the canonical transport setting;UserNameAttributekeeps its name as the canonical one). - Control-plane Admin UI (Blazor,
src/Server/ZB.MOM.WW.OtOpcUa.Security/): adoptZB.MOM.WW.Auth.AspNetCorefor 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 sharedILdapAuthService.- ALL of §3 authZ (
NodePermissions, ACL trie,NodeAcl), the control-planeAdminRolevocabulary, the JWT/DataProtectionspecifics, 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.