SDK compatibility matrix: UNS cases denied against the master server since #808 (fixture UNS folder has no ACL placement) #888

Closed
opened 2026-11-12 11:12:13 -05:00 by dohertj2 · 1 comment
Owner

Found while adding the #880 cases to the separate-process SDK compatibility matrix (scripts/validation/opcua-sdk-compatibility, #782), 2026-11-12.

Symptom: every client that runs against the candidate (current master) server now fails the UNS cases with BadUserAccessDenied. That covers the SDK 1.5 Shared client, the SDK 2 Shared client and the SDK 2 driver. The affected cases are raw-UNS-URI-browse, scalar-read-write-ns-3 and typed-array-read-write-ns-3 in the Shared legs, and driver-raw-UNS-URI-scalar-and-array-read in the driver leg. All of these pass against the SDK 1.5 baseline server (83ed880e), and the raw-namespace cases pass everywhere. The 2026-11-10 evidence run passed them against the candidate at the time.

Likely cause (fixture, not product): the matrix server builds its UNS nodes ad hoc (EnsureFolder("Eq", …, Uns) plus Eq/Scalar and Eq/Vector). It applies a Cluster-scope grant with an empty UNS path map (ProcessHost.Serve). After #808 (af5c9744, e39a1250), DataPlaneAuthorizer.TryPlaceUnsSubject fails closed for a folder, or a node a folder owns, that has no placement in the authority snapshot. It does not fall back to the realm or the cluster, so even a Cluster grant denies. That is by design for production, where every UNS node comes from the deployment artifact. The fixture just never placed its folder.

Fix options: give the fixture's Eq folder a UNS placement in the DataPlaneAuthorityState.Apply call, or run the matrix with OPCUA_MATRIX_ACL=off for UNS cases and keep one ACL-on raw case. The first keeps real ACL coverage.

Not caused by #880. That change touches only client code (Client.Shared, Driver.OpcUaClient.Session), and the server code under test is identical. Evidence: scripts/validation/opcua-sdk-compatibility/evidence/2026-11-12-880/.

Found while adding the #880 cases to the separate-process SDK compatibility matrix (`scripts/validation/opcua-sdk-compatibility`, #782), 2026-11-12. **Symptom:** every client that runs against the *candidate* (current master) server now fails the UNS cases with `BadUserAccessDenied`. That covers the SDK 1.5 Shared client, the SDK 2 Shared client and the SDK 2 driver. The affected cases are `raw-UNS-URI-browse`, `scalar-read-write-ns-3` and `typed-array-read-write-ns-3` in the Shared legs, and `driver-raw-UNS-URI-scalar-and-array-read` in the driver leg. All of these pass against the SDK 1.5 baseline server (83ed880e), and the raw-namespace cases pass everywhere. The 2026-11-10 evidence run passed them against the candidate at the time. **Likely cause (fixture, not product):** the matrix server builds its UNS nodes ad hoc (`EnsureFolder("Eq", …, Uns)` plus `Eq/Scalar` and `Eq/Vector`). It applies a Cluster-scope grant with an *empty* UNS path map (`ProcessHost.Serve`). After #808 (`af5c9744`, `e39a1250`), `DataPlaneAuthorizer.TryPlaceUnsSubject` fails closed for a folder, or a node a folder owns, that has no placement in the authority snapshot. It does not fall back to the realm or the cluster, so even a Cluster grant denies. That is by design for production, where every UNS node comes from the deployment artifact. The fixture just never placed its folder. **Fix options:** give the fixture's `Eq` folder a UNS placement in the `DataPlaneAuthorityState.Apply` call, or run the matrix with `OPCUA_MATRIX_ACL=off` for UNS cases and keep one ACL-on raw case. The first keeps real ACL coverage. Not caused by #880. That change touches only client code (`Client.Shared`, `Driver.OpcUaClient.Session`), and the server code under test is identical. Evidence: `scripts/validation/opcua-sdk-compatibility/evidence/2026-11-12-880/`.
dohertj2 added the priority:p3 label 2026-11-12 11:12:13 -05:00
Author
Owner

Fixed in lmxopcua master 1e1fad76 (fixture only, no server authorization code touched).

ProcessHost.cs now passes unsFolderScopes (Eq -> path [uns, Eq], id chain [Eq]) and tagReferencingEquipment (native alarm -> Eq) into DataPlaneAuthorityState.Apply, as a deployed generation does (under #if SDK2, since the 1.5 baseline host predates #808). Negative control added: an unplaced UNS folder "Unplaced" with a variable; the Shared client leg requires its read to be BadUserAccessDenied against the #808+ server with ACL on (and not denied with ACL off / on the 1.5 baseline).

Local runs (baseline = 83ed880e published from a fresh worktree):

  • opcua-sdk-compatibility, ACL on: all 6 legs passed (Shared x4: 14/14/15/15 cases; driver+CLI x2: 7/7), incl. raw-UNS-URI-browse, scalar/typed-array ns-3, driver-raw-UNS-URI-scalar-and-array-read. Negative control observed BadUserAccessDenied on the candidate server.
  • opcua-sdk-compatibility, ACL off: all 6 legs passed; control read Good.
  • scadabridge-sdk-compatibility (same fixture, ScadaBridge 94ae1860), ACL on and off: all 14 trusted legs (None/Sign/SignAndEncrypt + 4 failover) 11/11 for baseline and candidate; untrusted legs fail closed as designed. README note updated.
Fixed in lmxopcua master 1e1fad76 (fixture only, no server authorization code touched). ProcessHost.cs now passes unsFolderScopes (Eq -> path [uns, Eq], id chain [Eq]) and tagReferencingEquipment (native alarm -> Eq) into DataPlaneAuthorityState.Apply, as a deployed generation does (under #if SDK2, since the 1.5 baseline host predates #808). Negative control added: an unplaced UNS folder "Unplaced" with a variable; the Shared client leg requires its read to be BadUserAccessDenied against the #808+ server with ACL on (and not denied with ACL off / on the 1.5 baseline). Local runs (baseline = 83ed880e published from a fresh worktree): - opcua-sdk-compatibility, ACL on: all 6 legs passed (Shared x4: 14/14/15/15 cases; driver+CLI x2: 7/7), incl. raw-UNS-URI-browse, scalar/typed-array ns-3, driver-raw-UNS-URI-scalar-and-array-read. Negative control observed BadUserAccessDenied on the candidate server. - opcua-sdk-compatibility, ACL off: all 6 legs passed; control read Good. - scadabridge-sdk-compatibility (same fixture, ScadaBridge 94ae1860), ACL on and off: all 14 trusted legs (None/Sign/SignAndEncrypt + 4 failover) 11/11 for baseline and candidate; untrusted legs fail closed as designed. README note updated.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: dohertj2/lmxopcua#888