Resolve DA, A&C, and security spec gaps with ServerCapabilities, alarm methods, and modern profiles

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>
This commit is contained in:
Joseph Doherty
2026-04-06 22:02:05 -04:00
parent 41f0e9ec4c
commit 6d47687573
12 changed files with 345 additions and 20 deletions
@@ -1,5 +1,6 @@
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Opc.Ua;
using Opc.Ua.Configuration;
@@ -185,11 +186,12 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.OpcUa
// Check/create application certificate
var minKeySize = (ushort)_securityConfig.MinimumCertificateKeySize;
var certOk = await _application.CheckApplicationInstanceCertificate(false, minKeySize);
var certLifetimeMonths = (ushort)_securityConfig.CertificateLifetimeMonths;
var certOk = await _application.CheckApplicationInstanceCertificate(false, minKeySize, certLifetimeMonths);
if (!certOk)
{
Log.Warning("Application certificate check failed, attempting to create...");
certOk = await _application.CheckApplicationInstanceCertificate(false, minKeySize);
certOk = await _application.CheckApplicationInstanceCertificate(false, minKeySize, certLifetimeMonths);
}
_server = new LmxOpcUaServer(_config.GalaxyName, _mxAccessClient, _metrics, _historianDataSource,
@@ -203,15 +205,22 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.OpcUa
private void OnCertificateValidation(CertificateValidator sender, CertificateValidationEventArgs e)
{
var cert = e.Certificate;
var subject = cert?.Subject ?? "Unknown";
var thumbprint = cert?.Thumbprint ?? "N/A";
if (_securityConfig.AutoAcceptClientCertificates)
{
e.Accept = true;
Log.Debug("Client certificate auto-accepted: {Subject}", e.Certificate?.Subject);
Log.Warning(
"Client certificate auto-accepted: Subject={Subject}, Thumbprint={Thumbprint}, ValidTo={ValidTo}",
subject, thumbprint, cert?.NotAfter.ToString("yyyy-MM-dd"));
}
else
{
Log.Warning("Client certificate validation: {Error} for {Subject} — Accepted={Accepted}",
e.Error?.StatusCode, e.Certificate?.Subject, e.Accept);
Log.Warning(
"Client certificate validation: Error={Error}, Subject={Subject}, Thumbprint={Thumbprint}, Accepted={Accepted}",
e.Error?.StatusCode, subject, thumbprint, e.Accept);
}
}
@@ -244,6 +253,11 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.OpcUa
if (_authConfig.Ldap.Enabled || _authProvider != null)
policies.Add(new UserTokenPolicy(UserTokenType.UserName));
// X.509 certificate authentication is always available when security is configured
if (_securityConfig.Profiles.Any(p =>
!p.Equals("None", StringComparison.OrdinalIgnoreCase)))
policies.Add(new UserTokenPolicy(UserTokenType.Certificate));
if (policies.Count == 0)
{
Log.Warning("No authentication methods configured — adding Anonymous as fallback");