Files
lmxopcua/service_info.md
Joseph Doherty 50b85d41bd 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>
2026-03-29 01:50:16 -04:00

7.6 KiB

Service Update Summary

Updated service instance: C:\publish\lmxopcua\instance1

Update time: 2026-03-25 12:54-12:55 America/New_York

Backup created before deploy: C:\publish\lmxopcua\backups\20260325-125444

Configuration preserved:

  • C:\publish\lmxopcua\instance1\appsettings.json was not overwritten.

Deployed binary:

  • C:\publish\lmxopcua\instance1\ZB.MOM.WW.LmxOpcUa.Host.exe
  • Last write time: 2026-03-25 12:53:58
  • Size: 143360

Windows service:

  • Name: LmxOpcUa
  • Display name: LMX OPC UA Server
  • Account: LocalSystem
  • Status after update: Running
  • Process ID after restart: 29236

Restart evidence:

  • Service log file: C:\publish\lmxopcua\instance1\logs\lmxopcua-20260325_004.log
  • Last startup line: 2026-03-25 12:55:08.619 -04:00 [INF] The LmxOpcUa service was started.

CLI Verification

Endpoint from deployed config:

  • opc.tcp://localhost:4840/LmxOpcUa

CLI used:

  • C:\Users\dohertj2\Desktop\lmxopcua\tools\opcuacli-dotnet\bin\Debug\net10.0\opcuacli-dotnet.exe

Commands run:

opcuacli-dotnet.exe connect -u opc.tcp://localhost:4840/LmxOpcUa
opcuacli-dotnet.exe read -u opc.tcp://localhost:4840/LmxOpcUa -n 'ns=1;s=MESReceiver_001.MoveInPartNumbers'
opcuacli-dotnet.exe read -u opc.tcp://localhost:4840/LmxOpcUa -n 'ns=1;s=MESReceiver_001.MoveInPartNumbers[]'

Observed results:

  • connect: succeeded, server reported as LmxOpcUa.
  • read ns=1;s=MESReceiver_001.MoveInPartNumbers: succeeded with good status 0x00000000.
  • read ns=1;s=MESReceiver_001.MoveInPartNumbers[]: failed with BadNodeIdUnknown (0x80340000).

Instance 2 (Redundant Secondary)

Deployed: 2026-03-28

Deployment path: C:\publish\lmxopcua\instance2

Configuration:

  • OpcUa.Port: 4841
  • OpcUa.ServerName: LmxOpcUa2
  • OpcUa.ApplicationUri: urn:localhost:LmxOpcUa:instance2
  • Dashboard.Port: 8082
  • MxAccess.ClientName: LmxOpcUa2
  • Redundancy.Enabled: true
  • Redundancy.Mode: Warm
  • Redundancy.Role: Secondary
  • Redundancy.ServerUris: ["urn:localhost:LmxOpcUa:instance1", "urn:localhost:LmxOpcUa:instance2"]

Windows service:

  • Name: LmxOpcUa2
  • Display name: LMX OPC UA Server (Instance 2)
  • Account: LocalSystem
  • Endpoint: opc.tcp://localhost:4841/LmxOpcUa

Instance 1 redundancy update (same date):

  • OpcUa.ApplicationUri: urn:localhost:LmxOpcUa:instance1
  • Redundancy.Enabled: true
  • Redundancy.Mode: Warm
  • Redundancy.Role: Primary
  • Redundancy.ServerUris: ["urn:localhost:LmxOpcUa:instance1", "urn:localhost:LmxOpcUa:instance2"]

CLI verification:

opcuacli-dotnet.exe redundancy -u opc.tcp://localhost:4840/LmxOpcUa
  → Redundancy Mode: Warm, Service Level: 200, Application URI: urn:localhost:LmxOpcUa:instance1

opcuacli-dotnet.exe redundancy -u opc.tcp://localhost:4841/LmxOpcUa
  → Redundancy Mode: Warm, Service Level: 150, Application URI: urn:localhost:LmxOpcUa:instance2

Both instances report the same ServerUriArray and expose the same Galaxy namespace (urn:ZB:LmxOpcUa).

LDAP Authentication Update

Updated: 2026-03-28

Both instances updated to use LDAP authentication via GLAuth.

Configuration changes (both instances):

  • Authentication.AllowAnonymous: true (anonymous can browse/read)
  • Authentication.AnonymousCanWrite: false (anonymous writes blocked)
  • Authentication.Ldap.Enabled: true
  • Authentication.Ldap.Host: localhost
  • Authentication.Ldap.Port: 3893
  • Authentication.Ldap.BaseDN: dc=lmxopcua,dc=local

LDAP server: GLAuth v2.4.0 at C:\publish\glauth\ (Windows service: GLAuth)

Permission verification (instance1, port 4840):

anonymous read    → allowed
anonymous write   → denied  (BadUserAccessDenied)
readonly  read    → allowed
readonly  write   → denied  (BadUserAccessDenied)
readwrite write   → allowed
admin     write   → allowed
alarmack  write   → denied  (BadUserAccessDenied)
bad password      → denied  (connection rejected)

Alarm Notifier Chain Update

Updated: 2026-03-28

Both instances updated with alarm event propagation up the notifier chain.

Code changes:

  • Alarm events now walk up the parent chain (ReportEventUpNotifierChain), reporting to every ancestor node
  • EventNotifier = SubscribeToEvents is set on all ancestors of alarm-containing nodes (EnableEventNotifierUpChain)
  • Removed separate Server.ReportEvent call (no longer needed — the walk reaches the root)

No configuration changes required — alarm tracking was already enabled (AlarmTrackingEnabled: true).

Verification (instance1, port 4840):

alarms --node TestArea --refresh:
  TestMachine_001.TestAlarm001 → visible (Severity=500, Retain=True)
  TestMachine_001.TestAlarm002 → visible (Severity=500, Retain=True)
  TestMachine_001.TestAlarm003 → visible (Severity=500, Retain=True)
  TestMachine_002.TestAlarm001 → visible (Severity=500, Retain=True)
  TestMachine_002.TestAlarm003 → visible (Severity=500, Retain=True)

alarms --node DEV --refresh:
  Same 5 alarms visible at DEV (grandparent) level

Auth Consolidation Update

Updated: 2026-03-28

Both instances updated to consolidate LDAP roles into OPC UA session roles (RoleBasedIdentity.GrantedRoleIds).

Code changes:

  • LDAP groups now map to custom OPC UA role NodeIds in urn:zbmom:lmxopcua:roles namespace
  • Roles stored on session identity via GrantedRoleIds — no username-to-role side cache
  • Permission checks use GrantedRoleIds.Contains() instead of username extraction
  • AnonymousCanWrite behavior is consistent regardless of LDAP state
  • Galaxy namespace moved from ns=2 to ns=3 (roles namespace is ns=2)

No configuration changes required.

Verification (instance1, port 4840):

anonymous read      → allowed
anonymous write     → denied  (BadUserAccessDenied, AnonymousCanWrite=false)
readonly  write     → denied  (BadUserAccessDenied)
readwrite write     → allowed
admin     write     → allowed
alarmack  write     → denied  (BadUserAccessDenied)
bad password        → rejected (connection failed)

Granular Write Roles Update

Updated: 2026-03-28

Both instances updated with granular write roles replacing the single ReadWrite role.

Code changes:

  • ReadWrite role replaced by WriteOperate, WriteTune, WriteConfigure
  • Write permission checks now consider the Galaxy security classification of the target attribute
  • SecurityClassification stored in TagMetadata for per-node lookup at write time

GLAuth changes:

  • New groups: WriteOperate (5502), WriteTune (5504), WriteConfigure (5505)
  • New users: writeop, writetune, writeconfig
  • admin user added to all groups (5502, 5503, 5504, 5505)

Config changes (both instances):

  • Authentication.Ldap.ReadWriteGroup replaced by WriteOperateGroup, WriteTuneGroup, WriteConfigureGroup

Verification (instance1, port 4840, Operate-classified attributes):

anonymous read        → allowed
anonymous write       → denied  (AnonymousCanWrite=false)
readonly  write       → denied  (no write role)
writeop   write       → allowed (WriteOperate matches Operate classification)
writetune write       → denied  (WriteTune doesn't match Operate)
writeconfig write     → denied  (WriteConfigure doesn't match Operate)
admin     write       → allowed (has all write roles)

Notes

The service deployment and restart succeeded. The live CLI checks confirm the endpoint is reachable and that the array node identifier has changed to the bracketless form. The array value on the live service still prints as blank even though the status is good, so if this environment should have populated MoveInPartNumbers, the runtime data path still needs follow-up investigation.