Add ServerCapabilities/OperationLimits node, enable diagnostics, add OnModifyMonitoredItemsComplete override for DA compliance. Wire shelving, enable/disable, confirm, and addcomment handlers on alarm conditions with LocalTime/Quality event fields for Part 9 compliance. Add Aes128/Aes256 security profiles, X.509 certificate authentication, and AUDIT-prefixed auth logging. Fix flaky probe monitor test. Update docs for all changes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
330 lines
13 KiB
Markdown
330 lines
13 KiB
Markdown
# 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:
|
|
|
|
```powershell
|
|
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)
|
|
```
|
|
|
|
## Historian SDK Migration
|
|
|
|
Updated: `2026-04-06`
|
|
|
|
Both instances updated to use the Wonderware Historian SDK (`aahClientManaged.dll`) instead of direct SQL queries for historical data access.
|
|
|
|
Code changes:
|
|
- `HistorianDataSource` rewritten from `SqlConnection`/`SqlDataReader` to `ArchestrA.HistorianAccess` SDK
|
|
- Persistent connection with lazy connect and auto-reconnect on failure
|
|
- `HistorianConfiguration.ConnectionString` replaced with `ServerName`, `IntegratedSecurity`, `UserName`, `Password`, `Port`
|
|
- `HistorianDataSource` now implements `IDisposable`, disposed on service shutdown
|
|
- `ConfigurationValidator` validates Historian SDK settings at startup
|
|
|
|
SDK DLLs deployed to both instances:
|
|
- `aahClientManaged.dll` (primary SDK, v2.0.0.0)
|
|
- `aahClient.dll`, `aahClientCommon.dll` (dependencies)
|
|
- `Historian.CBE.dll`, `Historian.DPAPI.dll`, `ArchestrA.CloudHistorian.Contract.dll`
|
|
|
|
Configuration changes (both instances):
|
|
- `Historian.ConnectionString` removed
|
|
- `Historian.ServerName`: `"localhost"`
|
|
- `Historian.IntegratedSecurity`: `true`
|
|
- `Historian.Port`: `32568`
|
|
- `Historian.Enabled`: `true` (unchanged)
|
|
|
|
Verification (instance1 startup log):
|
|
```
|
|
Historian.Enabled=true, ServerName=localhost, IntegratedSecurity=true, Port=32568
|
|
Historian.CommandTimeoutSeconds=30, MaxValuesPerRead=10000
|
|
=== Configuration Valid ===
|
|
LmxOpcUa service started successfully
|
|
```
|
|
|
|
## HistoryServerCapabilities and Continuation Points
|
|
|
|
Updated: `2026-04-06`
|
|
|
|
Both instances updated with OPC UA Part 11 spec compliance improvements.
|
|
|
|
Code changes:
|
|
- `HistoryServerCapabilities` node populated under `ServerCapabilities` with all boolean capability properties
|
|
- `AggregateFunctions` folder populated with references to 7 supported aggregate functions
|
|
- `HistoryContinuationPointManager` added — stores remaining data when results exceed `NumValuesPerNode`
|
|
- `HistoryReadRawModified` and `HistoryReadProcessed` now return `ContinuationPoint` in `HistoryReadResult` for partial reads
|
|
- Follow-up requests with `ContinuationPoint` resume from stored state; invalid/expired points return `BadContinuationPointInvalid`
|
|
|
|
No configuration changes required.
|
|
|
|
Verification (instance1 startup log):
|
|
```
|
|
HistoryServerCapabilities configured with 7 aggregate functions
|
|
LmxOpcUa service started successfully
|
|
```
|
|
|
|
## Remaining Historian Gaps Fix
|
|
|
|
Updated: `2026-04-06`
|
|
|
|
Both instances updated with remaining OPC UA Part 11 spec compliance fixes.
|
|
|
|
Code changes:
|
|
- **Gap 4**: `HistoryReadRawModified` returns `BadHistoryOperationUnsupported` when `IsReadModified=true`
|
|
- **Gap 5**: `HistoryReadAtTime` override added with `ReadAtTimeAsync` using SDK `HistorianRetrievalMode.Interpolated`
|
|
- **Gap 8**: `HistoricalDataConfigurationState` child nodes added to historized variables (`Stepped=false`, `Definition="Wonderware Historian"`)
|
|
- **Gap 10**: `ReturnBounds` parameter handled — boundary `DataValue` entries with `BadBoundNotFound` inserted at StartTime/EndTime
|
|
- **Gap 11**: `StandardDeviation` aggregate added to client enum, mapper, CLI (aliases: `stddev`/`stdev`), and UI dropdown
|
|
|
|
No configuration changes required.
|
|
|
|
## Historical Event Access
|
|
|
|
Updated: `2026-04-06`
|
|
|
|
Both instances updated with OPC UA historical event access (Gap 7).
|
|
|
|
Code changes:
|
|
- `HistorianDataSource.ReadEventsAsync` queries Historian event store via separate `HistorianConnectionType.Event` connection
|
|
- `LmxNodeManager.HistoryReadEvents` override maps `HistorianEvent` records to OPC UA `HistoryEventFieldList` entries
|
|
- `AccessHistoryEventsCapability` set to `true` when `AlarmTrackingEnabled` is true
|
|
- Event fields: EventId, EventType, SourceNode, SourceName, Time, ReceiveTime, Message, Severity
|
|
|
|
No configuration changes required. All historian gaps (1-11) are now resolved.
|
|
|
|
## Data Access Gaps Fix
|
|
|
|
Updated: `2026-04-06`
|
|
|
|
Both instances updated with OPC UA DA spec compliance fixes.
|
|
|
|
Code changes:
|
|
- `ConfigureServerCapabilities()` populates `ServerCapabilities` node: `ServerProfileArray`, `LocaleIdArray`, `MinSupportedSampleRate`, continuation point limits, array/string limits, and 12 `OperationLimits` values
|
|
- `Server_ServerDiagnostics_EnabledFlag` set to `true` — SDK auto-tracks session/subscription counts
|
|
- `OnModifyMonitoredItemsComplete` override logs monitored item modifications
|
|
|
|
No configuration changes required. All DA gaps (1-8) resolved.
|
|
|
|
## Alarms & Conditions Gaps Fix
|
|
|
|
Updated: `2026-04-06`
|
|
|
|
Both instances updated with OPC UA Part 9 alarm spec compliance fixes.
|
|
|
|
Code changes:
|
|
- Wired `OnConfirm`, `OnAddComment`, `OnEnableDisable`, `OnShelve`, `OnTimedUnshelve` handlers on each `AlarmConditionState`
|
|
- Shelving: `SetShelvingState()` manages `TimedShelve`, `OneShotShelve`, `Unshelve` state machine
|
|
- `ReportAlarmEvent` now populates `LocalTime` (timezone offset + DST) and `Quality` event fields
|
|
- Flaky `Monitor_ProbeDataChange_PreventsStaleReconnect` test fixed (increased stale threshold from 2s to 5s)
|
|
|
|
No configuration changes required. All A&C gaps (1-10) resolved.
|
|
|
|
## Security Gaps Fix
|
|
|
|
Updated: `2026-04-06`
|
|
|
|
Both instances updated with OPC UA Part 2/4/7 security spec compliance fixes.
|
|
|
|
Code changes:
|
|
- `SecurityProfileResolver`: Added 4 modern AES profiles (`Aes128_Sha256_RsaOaep-Sign/SignAndEncrypt`, `Aes256_Sha256_RsaPss-Sign/SignAndEncrypt`)
|
|
- `OnImpersonateUser`: Added `X509IdentityToken` handling with CN extraction and role assignment
|
|
- `BuildUserTokenPolicies`: Advertises `UserTokenType.Certificate` when non-None security profiles are configured
|
|
- `OnCertificateValidation`: Enhanced logging with certificate thumbprint, subject, and expiry
|
|
- Authentication audit logging: `AUDIT:` prefixed log entries for success/failure with session ID and roles
|
|
|
|
No configuration changes required. All security gaps (1-10) resolved.
|
|
|
|
## 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.
|