Node ACLs are authored and deployed but never enforced — wire IPermissionEvaluator or retire the surface #520
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Split out of the
deferment.md§3.1 audit. Decision taken 2026-07-27: make the non-enforcement explicit now (docs + UI warnings — done), and track the real wire-up here.What is true today
IPermissionEvaluator,TriePermissionEvaluator,PermissionTrieCache,PermissionTrieBuilderlive insrc/Core/ZB.MOM.WW.OtOpcUa.Core/Authorization/with 30 passing unit tests.IPermissionEvaluator.Authorizehas zero production call sites. Nothing registers it in DI.ZB.MOM.WW.OtOpcUa.OpcUaServer.csprojdoes not referenceZB.MOM.WW.OtOpcUa.Coreat all — the evaluator is not even on the node manager's compile-time reference graph.NodeAclrows (/clusters/{id}/acls), andConfigComposersnapshots every row into every deployment artifact — so an ACL edit shifts the artifactRevisionHashand ships fleet-wide.DeploymentArtifactnever deserializes them. The bytes arrive and are dropped.Net: an operator can author a grant, deploy it green, and have it change nothing.
Actual enforcement is two server-wide role checks —
WriteOperate(Value writes, either realm) andAlarmAck(all five Part 9 alarm methods). Read, Browse, HistoryRead and CreateMonitoredItems have no authorization check at all, so any admitted session including Anonymous reads the whole address space.Four blockers — none of them is "call the evaluator"
NodeHierarchyKindhas exactly one member,Equipment. Raw NodeIds areFolder/Driver/Device/TagGroup/TagRawPaths, whichNodeScopecannot express — yet the write gate is attached to raw tags. Roughly half the writable address space has no scope to evaluate. This is a design decision, not wiring.NodeAcl.LdapGroup, butOpcUaUserAuthResultcarries only mapped role strings; the group list is discarded insideLdapOpcUaUserAuthenticator.PermissionTrieBuilder'sscopePathsargument has no producer anywhere in the tree. Building without it drops every sub-cluster grant into the root-child fallback — the hazard the builder's own comments warn about. Note every existing evaluator unit test runs in the no-scopePaths"deterministic test mode", so the production hierarchy path is effectively untested.UserAuthorizationStatehas the freshness/staleness predicates but nothing constructs one, stampsAuthGenerationId, or drives a refresh.Plus: the node side never parses ACLs out of the artifact, and
OtOpcUaNodeManagerhas no cluster identity (NodeScope.ClusterIdisrequiredand the evaluator fails closed on mismatch).Smaller inconsistencies inside the subsystem
NodeAclScopeKind.FolderSegmentis offered in the authoring dropdown but the trie walker has no level to match it at.NodePermissions.AlarmReadhas noOpcUaOperationmapping — grantable but unreachable.NodeAcl.ScopeIdhas no FK and no existence check, so scope ids can dangle silently.SystemPlatformhierarchy kind thatacl-design.mddescribes was retired with v3.Scope of a fix
Write has a real chokepoint already (
OnEquipmentTagWrite) — it runs under the node-managerLock, so the check must stay non-blocking; the evaluator is pure, so that fits. Read has no seam at all — it needs either aReadoverride or a per-variableOnSimpleReadValuehandler on the hot publish path.Already done
docs/security.md,docs/ReadWriteOperations.md,docs/v2/acl-design.mdanddocs/v2/v2-release-readiness.mdcorrected; warning banners added toClusterAcls.razorandAclEdit.razor.v2-release-readiness.mdhad marked this "CLOSED 2026-04-19, PR #94" with a detailed description of Browse/CreateMonitoredItems/Call gating — none of those types exist insrc/.