fix(opcua): drop the reflective folder-event verdict invalidation; deny the SDK channel under enforcement (#878)
v2-ci / unit-tests (push) Waiting to run
v2-ci / unit-tests (push) Waiting to run
The SDK memoizes INodeManager3.ValidateEventRolePermissions per (item, event type, SourceNode). #826 kept it fresh by reflecting into the private MonitoredNode2.InvalidatePermissionCaches (plus a detach/re-attach fallback). Since #855 condition events never take that channel, and the search for #878 found nothing production-raised that does except a condition's SDK method audits, which reach it only with auditing on, SignAndEncrypt and a SecurityAdmin session (nothing grants that role). Under enforcement the channel verdict is now a flat deny for items on our nodes, so no cached verdict depends on a grant, and InvalidateFolderEventVerdicts, the reflection, the fallback, its counter and the guard test are removed. Method audits still reach Server-object items, decided per event. Tests: FolderEventVerdictInvalidationTests -> FolderEventVerdictFreshnessTests; new revoked-grant test (positive control: a still-granted session) and method-audit test (SecurityAdmin mapped for the test; positive controls: Server-object item, flag off); the plain-event test runs each mode on a fresh server. Mutation-checked. Claude-Session: https://claude.ai/code/session_01GT67ACmpMR7b9LGRPLijMi
This commit is contained in:
+30
-13
@@ -823,13 +823,28 @@ Behaviours worth knowing before enabling it:
|
||||
|
||||
The generation check also narrows the **Server-object** rule: a wired folder whose reference the generation
|
||||
dropped no longer admits the native alarm there either. **`ConditionRefresh`** on a folder item is filtered the
|
||||
same way. The hook is `INodeManager3.ValidateEventRolePermissions`, which the node manager re-implements (the
|
||||
base method is not virtual); the SDK memoizes that verdict per `(item, event type, SourceNode)`, so the node
|
||||
manager clears each notifier's cache whole (sessions and orphaned, session-less subscriptions alike — an orphaned
|
||||
subscription must not queue a moved alarm's events for a later `TransferSubscriptions`) on every new generation or
|
||||
authorizer, on each `AclRevalidationTimer` tick, and at the end of every apply, and not at all while enforcement is
|
||||
off. A verdict is decided on the condition currently live under the event's NodeId, so a replaced instance's late
|
||||
event cannot leave a deny behind for its successor. Only alarm-bearing raw tags are carried in the reference map. Withheld events are not replayed and the client is not told; restoring the reference or
|
||||
same way. Condition events are decided **per event, per item, uncached** by the node manager's own delivery
|
||||
(`DeliverToNotifierItems`, #855), so a deploy, a revoked grant or a dropped reference decides the very next event —
|
||||
for orphaned, session-less subscriptions too (an orphaned subscription must not queue a moved alarm's events for a
|
||||
later `TransferSubscriptions`). A verdict is decided on the condition currently live under the event's NodeId, so a
|
||||
replaced instance's late event is treated exactly as the live one's.
|
||||
|
||||
**No SDK verdict cache to invalidate (Gitea #878).** Whatever still reaches one of our folders through the SDK's
|
||||
own `MonitoredNode2` channel is judged by `INodeManager3.ValidateEventRolePermissions` (re-implemented — the base
|
||||
method is not virtual), and the SDK **memoizes** that verdict per `(item, event type, SourceNode)` until the
|
||||
monitored node's permission generation moves — not when a deployment or grant changes. #826 kept it fresh by calling
|
||||
the SDK's *private* `MonitoredNode2.InvalidatePermissionCaches` by reflection (with a detach/re-attach fallback).
|
||||
#878 removed that: under enforcement every event on that channel is **withheld** from items on our nodes, so the
|
||||
cached verdict is a constant deny that no grant can make stale. What rides the channel (SDK 2.0.1, decompiled): only a
|
||||
condition's SDK-raised Part 9 method audit events — and those reach it only with auditing on
|
||||
(`ServerConfiguration.AuditingEnabled`, never set here), over SignAndEncrypt, to a session holding `SecurityAdmin`,
|
||||
the one well-known role the SDK's type check admits to audit types, which the SDK's default `RoleManager` maps to no
|
||||
identity and this server never grants — plus any event a `NodeState.ReportEvent` on one of our folders would raise
|
||||
(none is raised). Condition events never take the channel (#855), and the SDK's own condition reports stay off
|
||||
(`AutoReportStateChanges = false`). A condition's method audits still reach **Server-object** items, decided per
|
||||
event. Residual: a verdict cached while enforcement was **off** survives the switch to on; the switch happens once,
|
||||
when the host wires the authorizer just after the server starts listening, and the only allow it could carry is a
|
||||
method audit to a `SecurityAdmin` session. Only alarm-bearing raw tags are carried in the reference map. Withheld events are not replayed and the client is not told; restoring the reference or
|
||||
grant resumes the feed on the next event. ⚠️ A native alarm reaches an equipment folder's items under
|
||||
enforcement only while the installed generation carries that reference — a hand-built authority (tests) must
|
||||
supply `tagReferencingEquipment`. Inert with the flag off.
|
||||
@@ -846,12 +861,13 @@ Behaviours worth knowing before enabling it:
|
||||
folder it legitimately notifies through for the session), which in this ACL model is the deliberate
|
||||
generalisation of "the source": a `Tag` grant covers a native alarm's raw node and every UNS projection of it, and
|
||||
an `Equipment` grant covers the alarms it owns or references.
|
||||
- **A condition's Part 9 method audit events** (Acknowledge, Comment, Shelve…) — SourceNode is the condition, so they
|
||||
are resolved to it and follow exactly the condition rule (plus the SDK's audit/SignAndEncrypt guard on the
|
||||
Server-object path).
|
||||
- **A condition's Part 9 method audit events** (Acknowledge, Comment, Shelve…) — SourceNode is the condition. At the
|
||||
Server object they are resolved to it and follow exactly the condition rule (plus the SDK's audit/SignAndEncrypt
|
||||
guard). At a folder item they travel the SDK's channel and are **withheld** under enforcement (Gitea #878, see
|
||||
above) — stricter than Part 3, which it permits; only a `SecurityAdmin` session could receive them at all.
|
||||
- **Any other event on a folder item** (none is produced today; `GeneralModelChangeEvent` and the rejected-write
|
||||
`AuditWriteUpdateEvent` go to the Server object, #588) names no live condition and is **withheld** under
|
||||
enforcement — fail closed, which is stricter than the folder's permission and never laxer than Part 3.
|
||||
`AuditWriteUpdateEvent` go to the Server object, #588) is **withheld** under enforcement — fail closed, which is
|
||||
stricter than the folder's permission and never laxer than Part 3.
|
||||
With the flag off only the SDK's own type + SourceNode `ReceiveEvents` check applies, as before.
|
||||
|
||||
**Event bursts never block under the node-manager lock (Gitea #838).** Each notifier with event items has an SDK
|
||||
@@ -925,7 +941,8 @@ Behaviours worth knowing before enabling it:
|
||||
type/source `ReceiveEvents` + the #826 decision under enforcement — its item-independent part, the live condition,
|
||||
the notifier check and the visibility scopes, resolved once per hop) **per event, uncached**, then the item's gate and
|
||||
cut, then `QueueEvent`. The `MonitoredNode2` stays for item attachment and for what still uses its channel (a
|
||||
condition's SDK method audit events, data-change items on the folder), so `InvalidateFolderEventVerdicts` stays.
|
||||
condition's SDK method audit events, data-change items on the folder). (#878 later withheld everything on that
|
||||
channel from our folder items under enforcement and removed `InvalidateFolderEventVerdicts`, see above.)
|
||||
- **No notifier of ours defers any more.** `QueueEvent` never waits for a consumer (the item queue discards oldest at
|
||||
`QueueSize`) and the verdict runs on the reporter's thread, which already holds `Lock` — so a burst under one
|
||||
`Lock` hold completes with nothing deferred, and the #838 deadlock shape is gone for condition events. Folder items
|
||||
|
||||
Reference in New Issue
Block a user