From 063d004fda45307a4116a0f745f18c9b99079f2b Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Mon, 15 Jun 2026 00:42:32 -0400 Subject: [PATCH] docs(security): fix data-plane alarm-ack GroupToRole value (AlarmAck, not AlarmAcknowledge) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The gate reads the literal role string OpcUaDataPlaneRoles.AlarmAck = "AlarmAck" (OtOpcUaNodeManager.cs:643), but the Role-grant-source section told operators to map their alarm-ack group to "AlarmAcknowledge" (the PermissionFlags ACL bit, a different vocabulary) — which silently never satisfies the ack gate. Fix the three role-string occurrences + add a code-true note; generalize the scripted-alarm note to native alarms. --- docs/security.md | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/docs/security.md b/docs/security.md index e04d4127..4b7d055e 100644 --- a/docs/security.md +++ b/docs/security.md @@ -160,7 +160,7 @@ LDAP is configured under the `Security:Ldap` section (bound to `LdapOptions`, `s } ``` -`GroupToRole` maps LDAP group names → roles (case-insensitive); a user gets every role whose source group is in their membership. The values are the canonical control-plane role strings (`Viewer` / `Designer` / `Administrator`, plus the appsettings-only `Operator` for the `DriverOperator` policy); the same map also supplies data-plane role strings (`ReadOnly`, `WriteOperate`, `WriteTune`, `WriteConfigure`, `AlarmAcknowledge`) — see [Role grant source (data-plane)](#role-grant-source-data-plane) below. `UserNameAttribute: "sAMAccountName"` is the critical AD override — the GLAuth dev default is `cn`, which is not how AD users are looked up; use `userPrincipalName` instead if operators log in with `user@corp.example.com` form. `LdapOptionsValidator` (`src/Server/ZB.MOM.WW.OtOpcUa.Host/Configuration/LdapOptionsValidator.cs`) fails startup when `Transport = None` and `AllowInsecure = false` on a real-LDAP (non-DevStub) config. +`GroupToRole` maps LDAP group names → roles (case-insensitive); a user gets every role whose source group is in their membership. The values are the canonical control-plane role strings (`Viewer` / `Designer` / `Administrator`, plus the appsettings-only `Operator` for the `DriverOperator` policy); the same map also supplies data-plane role strings (`ReadOnly`, `WriteOperate`, `WriteTune`, `WriteConfigure`, `AlarmAck`) — see [Role grant source (data-plane)](#role-grant-source-data-plane) below. `UserNameAttribute: "sAMAccountName"` is the critical AD override — the GLAuth dev default is `cn`, which is not how AD users are looked up; use `userPrincipalName` instead if operators log in with `user@corp.example.com` form. `LdapOptionsValidator` (`src/Server/ZB.MOM.WW.OtOpcUa.Host/Configuration/LdapOptionsValidator.cs`) fails startup when `Transport = None` and `AllowInsecure = false` on a real-LDAP (non-DevStub) config. --- @@ -248,7 +248,7 @@ Data-plane roles come from `Security:Ldap:GroupToRole` (appsettings), **not** fr `LdapGroupRoleMapping` table. That table's `Role` column is the `AdminRole` enum (`Administrator`/`Designer`/`Viewer`) and supplies **control-plane** roles only — it cannot emit the data-plane role strings the OPC UA gates read (`ReadOnly`, `WriteOperate`, `WriteTune`, -`WriteConfigure`, `AlarmAcknowledge`). A deployment therefore **must** map its data-plane LDAP groups +`WriteConfigure`, `AlarmAck`). A deployment therefore **must** map its data-plane LDAP groups to those role strings via `GroupToRole`, e.g.: ```json @@ -256,14 +256,21 @@ to those role strings via `GroupToRole`, e.g.: "ot-operators": "WriteOperate", "ot-tuners": "WriteTune", "ot-engineers": "WriteConfigure", - "ot-alarm-ack": "AlarmAcknowledge", + "ot-alarm-ack": "AlarmAck", "ot-readonly": "ReadOnly" } ``` If this mapping is absent the data-plane evaluator is strictly default-deny: inbound operator writes and OPC UA Part-9 alarm acknowledgement all return `BadUserAccessDenied` even for users who -authenticate successfully. (The same requirement gates the pre-existing scripted-alarm ack path.) +authenticate successfully. (The same requirement gates both the scripted-alarm and the native +Galaxy-alarm Part-9 ack/confirm/shelve paths.) + +The role strings above are **exact, case-insensitive, and code-true** — the inbound gates compare +against the constants in `OpcUaDataPlaneRoles` (`AlarmAck`, `WriteOperate`) and the bare strings +`ReadOnly` / `WriteTune` / `WriteConfigure`. In particular the alarm-ack role is `AlarmAck`, **not** +`AlarmAcknowledge` (that spelling is the `PermissionFlags` ACL bit, a different vocabulary); a +`GroupToRole` value of `AlarmAcknowledge` silently never satisfies the ack gate. ---