Add configurable transport security profiles and bind address

Adds Security section to appsettings.json with configurable OPC UA
transport profiles (None, Basic256Sha256-Sign, Basic256Sha256-SignAndEncrypt),
certificate policy settings, and a configurable BindAddress for the
OPC UA endpoint. Defaults preserve backward compatibility.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-03-27 15:59:43 -04:00
parent bbd043e97b
commit 55173665b1
28 changed files with 1092 additions and 87 deletions

View File

@@ -25,6 +25,7 @@ namespace ZB.MOM.WW.LmxOpcUa.Tests.Configuration
configuration.GetSection("MxAccess").Bind(config.MxAccess);
configuration.GetSection("GalaxyRepository").Bind(config.GalaxyRepository);
configuration.GetSection("Dashboard").Bind(config.Dashboard);
configuration.GetSection("Security").Bind(config.Security);
return config;
}
@@ -35,6 +36,7 @@ namespace ZB.MOM.WW.LmxOpcUa.Tests.Configuration
public void OpcUa_Section_BindsCorrectly()
{
var config = LoadFromJson();
config.OpcUa.BindAddress.ShouldBe("0.0.0.0");
config.OpcUa.Port.ShouldBe(4840);
config.OpcUa.EndpointPath.ShouldBe("/LmxOpcUa");
config.OpcUa.ServerName.ShouldBe("LmxOpcUa");
@@ -117,12 +119,31 @@ namespace ZB.MOM.WW.LmxOpcUa.Tests.Configuration
public void DefaultValues_AreCorrect()
{
var config = new AppConfiguration();
config.OpcUa.BindAddress.ShouldBe("0.0.0.0");
config.OpcUa.Port.ShouldBe(4840);
config.MxAccess.ClientName.ShouldBe("LmxOpcUa");
config.GalaxyRepository.ChangeDetectionIntervalSeconds.ShouldBe(30);
config.Dashboard.Enabled.ShouldBe(true);
}
/// <summary>
/// Confirms that BindAddress can be overridden to a specific hostname or IP.
/// </summary>
[Fact]
public void OpcUa_BindAddress_CanBeOverridden()
{
var configuration = new ConfigurationBuilder()
.AddInMemoryCollection(new[]
{
new System.Collections.Generic.KeyValuePair<string, string>("OpcUa:BindAddress", "localhost"),
})
.Build();
var config = new OpcUaConfiguration();
configuration.GetSection("OpcUa").Bind(config);
config.BindAddress.ShouldBe("localhost");
}
/// <summary>
/// Confirms that a valid configuration passes startup validation.
/// </summary>
@@ -154,5 +175,66 @@ namespace ZB.MOM.WW.LmxOpcUa.Tests.Configuration
config.OpcUa.GalaxyName = "";
ConfigurationValidator.ValidateAndLog(config).ShouldBe(false);
}
/// <summary>
/// Confirms that the Security section binds profile list from appsettings.json.
/// </summary>
[Fact]
public void Security_Section_BindsProfilesCorrectly()
{
var config = LoadFromJson();
config.Security.Profiles.ShouldContain("None");
config.Security.AutoAcceptClientCertificates.ShouldBe(true);
config.Security.MinimumCertificateKeySize.ShouldBe(2048);
}
/// <summary>
/// Confirms that a minimum key size below 2048 is rejected by the validator.
/// </summary>
[Fact]
public void Validator_InvalidMinKeySize_ReturnsFalse()
{
var config = new AppConfiguration();
config.Security.MinimumCertificateKeySize = 1024;
ConfigurationValidator.ValidateAndLog(config).ShouldBe(false);
}
/// <summary>
/// Confirms that a valid configuration with security defaults passes validation.
/// </summary>
[Fact]
public void Validator_DefaultSecurityConfig_ReturnsTrue()
{
var config = LoadFromJson();
ConfigurationValidator.ValidateAndLog(config).ShouldBe(true);
}
/// <summary>
/// Confirms that custom security profiles can be bound from in-memory configuration.
/// </summary>
[Fact]
public void Security_Section_BindsCustomProfiles()
{
var configuration = new ConfigurationBuilder()
.AddInMemoryCollection(new[]
{
new System.Collections.Generic.KeyValuePair<string, string>("Security:Profiles:0", "None"),
new System.Collections.Generic.KeyValuePair<string, string>("Security:Profiles:1", "Basic256Sha256-SignAndEncrypt"),
new System.Collections.Generic.KeyValuePair<string, string>("Security:AutoAcceptClientCertificates", "false"),
new System.Collections.Generic.KeyValuePair<string, string>("Security:MinimumCertificateKeySize", "4096"),
})
.Build();
// Clear default list before binding to match production behavior
var config = new AppConfiguration();
config.Security.Profiles.Clear();
configuration.GetSection("Security").Bind(config.Security);
config.Security.Profiles.Count.ShouldBe(2);
config.Security.Profiles.ShouldContain("None");
config.Security.Profiles.ShouldContain("Basic256Sha256-SignAndEncrypt");
config.Security.AutoAcceptClientCertificates.ShouldBe(false);
config.Security.MinimumCertificateKeySize.ShouldBe(4096);
}
}
}