Add authentication and role-based write access control

Implements configurable user authentication (anonymous + username/password)
with pluggable credential provider (IUserAuthenticationProvider). Anonymous
writes can be disabled via AnonymousCanWrite setting while reads remain
open. Adds -U/-P flags to all CLI commands for authenticated sessions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-03-27 02:14:37 -04:00
parent b27d355763
commit bbd043e97b
24 changed files with 499 additions and 34 deletions

View File

@@ -22,6 +22,8 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.OpcUa
private readonly IMxAccessClient _mxAccessClient;
private readonly PerformanceMetrics _metrics;
private readonly HistorianDataSource? _historianDataSource;
private readonly AuthenticationConfiguration _authConfig;
private readonly IUserAuthenticationProvider? _authProvider;
private ApplicationInstance? _application;
private LmxOpcUaServer? _server;
@@ -48,12 +50,16 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.OpcUa
/// <param name="metrics">The metrics collector shared with the node manager and runtime bridge.</param>
/// <param name="historianDataSource">The optional historian adapter that enables OPC UA history read support.</param>
public OpcUaServerHost(OpcUaConfiguration config, IMxAccessClient mxAccessClient, PerformanceMetrics metrics,
HistorianDataSource? historianDataSource = null)
HistorianDataSource? historianDataSource = null,
AuthenticationConfiguration? authConfig = null,
IUserAuthenticationProvider? authProvider = null)
{
_config = config;
_mxAccessClient = mxAccessClient;
_metrics = metrics;
_historianDataSource = historianDataSource;
_authConfig = authConfig ?? new AuthenticationConfiguration();
_authProvider = authProvider;
}
/// <summary>
@@ -84,10 +90,7 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.OpcUa
SecurityPolicyUri = SecurityPolicies.None
}
},
UserTokenPolicies =
{
new UserTokenPolicy(UserTokenType.Anonymous)
}
UserTokenPolicies = BuildUserTokenPolicies()
},
SecurityConfiguration = new SecurityConfiguration
@@ -160,7 +163,8 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.OpcUa
certOk = await _application.CheckApplicationInstanceCertificate(false, 2048);
}
_server = new LmxOpcUaServer(_config.GalaxyName, _mxAccessClient, _metrics, _historianDataSource, _config.AlarmTrackingEnabled);
_server = new LmxOpcUaServer(_config.GalaxyName, _mxAccessClient, _metrics, _historianDataSource,
_config.AlarmTrackingEnabled, _authConfig, _authProvider);
await _application.Start(_server);
Log.Information("OPC UA server started on opc.tcp://localhost:{Port}{EndpointPath} (namespace={Namespace})",
@@ -188,6 +192,23 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.OpcUa
}
}
private UserTokenPolicyCollection BuildUserTokenPolicies()
{
var policies = new UserTokenPolicyCollection();
if (_authConfig.AllowAnonymous)
policies.Add(new UserTokenPolicy(UserTokenType.Anonymous));
if (_authConfig.Users.Count > 0)
policies.Add(new UserTokenPolicy(UserTokenType.UserName));
if (policies.Count == 0)
{
Log.Warning("No authentication methods configured — adding Anonymous as fallback");
policies.Add(new UserTokenPolicy(UserTokenType.Anonymous));
}
return policies;
}
/// <summary>
/// Stops the host and releases server resources.
/// </summary>