Consolidate LDAP roles into OPC UA session roles with granular write permissions
Map LDAP groups to custom OPC UA role NodeIds on RoleBasedIdentity.GrantedRoleIds during authentication, replacing the username-to-role side cache. Split ReadWrite into WriteOperate/WriteTune/WriteConfigure so write access is gated per Galaxy security classification. AnonymousCanWrite now behaves consistently regardless of LDAP state. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -120,7 +120,7 @@ Key behaviors:
|
||||
- **Message** -- Uses `CachedMessage` (from DescAttrName) when available on activation. Falls back to a generated `"Alarm active: {SourceName}"` string. Cleared alarms always use `"Alarm cleared: {SourceName}"`.
|
||||
- **Severity** -- Set from `CachedSeverity`, which was read from the Priority tag.
|
||||
- **Retain** -- `true` while the alarm is active or unacknowledged. This keeps the condition visible in condition refresh responses.
|
||||
- **Acknowledged state** -- Reset to `false` when the alarm activates, requiring explicit client acknowledgment.
|
||||
- **Acknowledged state** -- Reset to `false` when the alarm activates, requiring explicit client acknowledgment. When role-based auth is active, alarm acknowledgment requires the `AlarmAck` role on the session (checked via `GrantedRoleIds`). Users without this role receive `BadUserAccessDenied`.
|
||||
|
||||
The event is reported by walking up the notifier chain from the source variable's parent through all ancestor nodes. Each ancestor with `EventNotifier` set receives the event via `ReportEvent`, so clients subscribed at any level in the Galaxy hierarchy see alarm transitions from descendant objects.
|
||||
|
||||
|
||||
@@ -130,21 +130,27 @@ When `Ldap.Enabled` is `true`, credentials are validated against the configured
|
||||
| `Ldap.ServiceAccountPassword` | `string` | `""` | Service account password |
|
||||
| `Ldap.TimeoutSeconds` | `int` | `5` | Connection timeout |
|
||||
| `Ldap.ReadOnlyGroup` | `string` | `ReadOnly` | LDAP group granting read-only access |
|
||||
| `Ldap.ReadWriteGroup` | `string` | `ReadWrite` | LDAP group granting read-write access |
|
||||
| `Ldap.WriteOperateGroup` | `string` | `WriteOperate` | LDAP group granting write access for FreeAccess/Operate attributes |
|
||||
| `Ldap.WriteTuneGroup` | `string` | `WriteTune` | LDAP group granting write access for Tune attributes |
|
||||
| `Ldap.WriteConfigureGroup` | `string` | `WriteConfigure` | LDAP group granting write access for Configure attributes |
|
||||
| `Ldap.AlarmAckGroup` | `string` | `AlarmAck` | LDAP group granting alarm acknowledgment |
|
||||
|
||||
#### Permission Model
|
||||
|
||||
When LDAP is enabled, authenticated users receive permissions based on their LDAP group membership:
|
||||
When LDAP is enabled, LDAP group membership is mapped to OPC UA session role NodeIds during authentication. All authenticated LDAP users can browse and read nodes regardless of group membership. Groups grant additional permissions:
|
||||
|
||||
| LDAP Group | Permission |
|
||||
|---|---|
|
||||
| ReadOnly | Browse and read nodes |
|
||||
| ReadWrite | Browse, read, and write tag values |
|
||||
| ReadOnly | No additional permissions (read-only access) |
|
||||
| WriteOperate | Write FreeAccess and Operate attributes |
|
||||
| WriteTune | Write Tune attributes |
|
||||
| WriteConfigure | Write Configure attributes |
|
||||
| AlarmAck | Acknowledge alarms |
|
||||
|
||||
Users can belong to multiple groups. The `admin` user in the default GLAuth configuration belongs to all three groups.
|
||||
|
||||
Write access depends on both the user's role and the Galaxy attribute's security classification. See the [Effective Permission Matrix](Security.md#effective-permission-matrix) in the Security Guide for the full breakdown.
|
||||
|
||||
Example configuration:
|
||||
|
||||
```json
|
||||
@@ -161,7 +167,9 @@ Example configuration:
|
||||
"ServiceAccountPassword": "serviceaccount123",
|
||||
"TimeoutSeconds": 5,
|
||||
"ReadOnlyGroup": "ReadOnly",
|
||||
"ReadWriteGroup": "ReadWrite",
|
||||
"WriteOperateGroup": "WriteOperate",
|
||||
"WriteTuneGroup": "WriteTune",
|
||||
"WriteConfigureGroup": "WriteConfigure",
|
||||
"AlarmAckGroup": "AlarmAck"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,15 +63,17 @@ The `ServiceLevel` is updated whenever MXAccess connection state changes or Gala
|
||||
`UserTokenPolicies` are dynamically configured based on the `Authentication` settings in `appsettings.json`:
|
||||
|
||||
- An `Anonymous` user token policy is added when `AllowAnonymous` is `true` (the default).
|
||||
- A `UserName` user token policy is added when `Ldap.Enabled` is `true`.
|
||||
- A `UserName` user token policy is added when an authentication provider is configured (LDAP or injected).
|
||||
|
||||
Both policies can be active simultaneously, allowing clients to connect with or without credentials.
|
||||
|
||||
### Session impersonation
|
||||
|
||||
When a client presents `UserName` credentials, the server validates them through `IUserAuthenticationProvider`. If LDAP authentication is enabled, credentials are validated via LDAP bind and group membership determines the user's application-level roles (`ReadOnly`, `ReadWrite`, `AlarmAck`). If validation fails, the session is rejected.
|
||||
When a client presents `UserName` credentials, the server validates them through `IUserAuthenticationProvider`. If the provider also implements `IRoleProvider` (as `LdapAuthenticationProvider` does), LDAP group membership is resolved once during authentication and mapped to custom OPC UA role `NodeId`s in a dedicated `urn:zbmom:lmxopcua:roles` namespace. These role NodeIds are added to the session's `RoleBasedIdentity.GrantedRoleIds`.
|
||||
|
||||
On successful validation, the session identity is set to a `RoleBasedIdentity` that carries the user's granted role IDs. Authenticated users receive the `WellKnownRole_AuthenticatedUser` role. Anonymous connections receive the `WellKnownRole_Anonymous` role. When LDAP is enabled, application-level roles from group membership control write and alarm-ack permissions. Without LDAP, `AnonymousCanWrite` controls whether anonymous users can write.
|
||||
Anonymous sessions receive `WellKnownRole_Anonymous`. Authenticated sessions receive `WellKnownRole_AuthenticatedUser` plus any LDAP-derived role NodeIds. Permission checks in `LmxNodeManager` inspect `GrantedRoleIds` directly — no username extraction or side-channel cache is needed.
|
||||
|
||||
`AnonymousCanWrite` controls whether anonymous sessions can write, regardless of whether LDAP is enabled.
|
||||
|
||||
## Certificate handling
|
||||
|
||||
|
||||
@@ -266,25 +266,50 @@ The CLI tool auto-generates its own client certificate on first use (stored unde
|
||||
|
||||
## LDAP Authentication
|
||||
|
||||
The server supports LDAP-based user authentication via GLAuth (or any standard LDAP server). When enabled, OPC UA `UserName` token credentials are validated by LDAP bind, and LDAP group membership controls what operations each user can perform.
|
||||
The server supports LDAP-based user authentication via GLAuth (or any standard LDAP server). When enabled, OPC UA `UserName` token credentials are validated by LDAP bind. LDAP group membership is resolved once during authentication and mapped to custom OPC UA role `NodeId`s in the `urn:zbmom:lmxopcua:roles` namespace. These role NodeIds are stored on the session's `RoleBasedIdentity.GrantedRoleIds` and checked directly during write and alarm-ack operations.
|
||||
|
||||
### Architecture
|
||||
|
||||
```
|
||||
OPC UA Client → UserName Token → LmxOpcUa Server → LDAP Bind (validate credentials)
|
||||
→ LDAP Search (resolve group membership)
|
||||
→ Role assignment → Permission enforcement
|
||||
→ Map groups to OPC UA role NodeIds
|
||||
→ Store on RoleBasedIdentity.GrantedRoleIds
|
||||
→ Permission checks via GrantedRoleIds.Contains()
|
||||
```
|
||||
|
||||
### LDAP Groups and OPC UA Permissions
|
||||
|
||||
| LDAP Group | OPC UA Permission |
|
||||
All authenticated LDAP users can browse and read nodes regardless of group membership. Groups grant additional permissions:
|
||||
|
||||
| LDAP Group | Permission |
|
||||
|---|---|
|
||||
| ReadOnly | Browse and read nodes |
|
||||
| ReadWrite | Read and write tag values |
|
||||
| ReadOnly | No additional permissions (read-only access) |
|
||||
| WriteOperate | Write FreeAccess and Operate attributes |
|
||||
| WriteTune | Write Tune attributes |
|
||||
| WriteConfigure | Write Configure attributes |
|
||||
| AlarmAck | Acknowledge alarms |
|
||||
|
||||
Users can belong to multiple groups. A user with all three groups has full access.
|
||||
Users can belong to multiple groups. The `admin` user in the default GLAuth configuration belongs to all groups.
|
||||
|
||||
### Effective Permission Matrix
|
||||
|
||||
The effective permission for a write operation depends on two factors: the user's session role (from LDAP group membership or anonymous access) and the Galaxy attribute's security classification. The security classification controls the node's `AccessLevel` — attributes classified as `SecuredWrite`, `VerifiedWrite`, or `ViewOnly` are exposed as read-only nodes regardless of the user's role. For writable classifications, the required write role depends on the classification.
|
||||
|
||||
| | FreeAccess | Operate | SecuredWrite | VerifiedWrite | Tune | Configure | ViewOnly |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| **Anonymous (`AnonymousCanWrite=true`)** | Write | Write | Read | Read | Write | Write | Read |
|
||||
| **Anonymous (`AnonymousCanWrite=false`)** | Read | Read | Read | Read | Read | Read | Read |
|
||||
| **ReadOnly** | Read | Read | Read | Read | Read | Read | Read |
|
||||
| **WriteOperate** | Write | Write | Read | Read | Read | Read | Read |
|
||||
| **WriteTune** | Read | Read | Read | Read | Write | Read | Read |
|
||||
| **WriteConfigure** | Read | Read | Read | Read | Read | Write | Read |
|
||||
| **AlarmAck** (only) | Read | Read | Read | Read | Read | Read | Read |
|
||||
| **Admin** (all groups) | Write | Write | Read | Read | Write | Write | Read |
|
||||
|
||||
All roles can browse and read all nodes. The "Read" entries above mean the node is either read-only by classification or the user lacks the required write role. "Write" means the write is permitted by both the node's classification and the user's role.
|
||||
|
||||
Alarm acknowledgment is an independent permission controlled by the `AlarmAck` role and is not affected by security classification.
|
||||
|
||||
### GLAuth Setup
|
||||
|
||||
|
||||
Reference in New Issue
Block a user