271b96a644
I-1: Test Connect / browse configs may carry unsaved edits (the device modal merges the stored DriverConfig with the typed device config). The server (DriverInstanceIdentities.EffectiveOwnerAsync, used by AdminOperationsActor and BrowserSessionService) honours the named owner only when every endpoint value (new DriverCredentialRules.EndpointFields: Galaxy Endpoint, OpcUaClient EndpointUrl/EndpointUrls/DiscoveryUrl, Heidenhain Host/Port) is one that driver stores; otherwise the owner is null and its instance-named secrets are refused. Every Test Connect / browse whose config carries an in-scope secret:/env: reference is recorded (CredentialUseAudit: IAuditWriter event category CredentialUse + a structured Information line: user, driver, endpoint hosts, outcome). L-1: the UserCertificatePath rule is skipped only in an object that explicitly sets AuthType to non-Certificate (string or ordinal); the deploy gate also checks the runtime-merged driver+device config so the switch cannot be split from the path; the form no longer persists the hidden field. L-2: a ConfigDb fault in the browse check is a refusal; RawBrowseModal catches open failures into its error message. L-4: a relative UserCertificateDirectory resolves against AppContext.BaseDirectory. Co-Authored-By: Claude <noreply@anthropic.com>
55 lines
3.2 KiB
C#
55 lines
3.2 KiB
C#
using Shouldly;
|
|
using Xunit;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Core.Abstractions.Tests;
|
|
|
|
/// <summary>#835/#836 security review: endpoint matching (I-1), credential-reference detection (audit) and L-1.</summary>
|
|
[Trait("Category", "Unit")]
|
|
public sealed class CredentialPolicyReviewTests
|
|
{
|
|
[Theory]
|
|
[InlineData("""{"AuthType":"Anonymous","UserCertificatePath":"C:\\x.pfx"}""", 0)]
|
|
[InlineData("""{"AuthType":"username","UserCertificatePath":"/etc/x.pfx"}""", 0)]
|
|
[InlineData("""{"AuthType":0,"UserCertificatePath":"/etc/x.pfx"}""", 0)]
|
|
[InlineData("""{"AuthType":"Certificate","UserCertificatePath":"/etc/x.pfx"}""", 1)]
|
|
[InlineData("""{"authType":2,"UserCertificatePath":"/etc/x.pfx"}""", 1)]
|
|
[InlineData("""{"UserCertificatePath":"/etc/x.pfx"}""", 1)] // no switch here — the merge may supply it
|
|
[InlineData("""{"AuthType":null,"UserCertificatePath":"/etc/x.pfx"}""", 1)]
|
|
public void L1_the_path_rule_applies_unless_the_object_explicitly_switches_certificate_auth_off(string config, int expected) =>
|
|
DriverCredentialPolicy.DescribeProblems(DriverTypeNames.OpcUaClient, config).Count.ShouldBe(expected);
|
|
|
|
[Fact]
|
|
public void Endpoint_values_are_collected_at_any_depth_and_flattened()
|
|
{
|
|
DriverCredentialPolicy.EndpointValues(DriverTypeNames.OpcUaClient,
|
|
"""{"EndpointUrl":" opc.tcp://a:4840 ","EndpointUrls":["opc.tcp://b:4840"],"Devices":[{"discoveryUrl":"opc.tcp://c"}]}""")
|
|
.ShouldBe(["opc.tcp://a:4840", "opc.tcp://b:4840", "opc.tcp://c"]);
|
|
DriverCredentialPolicy.EndpointValues(DriverTypeNames.Heidenhain, """{"Devices":[{"Host":"cnc1","Port":19000}]}""")
|
|
.ShouldBe(["cnc1", "19000"]);
|
|
DriverCredentialPolicy.EndpointValues(DriverTypeNames.Modbus, """{"Host":"x"}""").ShouldBeEmpty();
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("""{"Gateway":{"Endpoint":"http://gw:5001"}}""", true)]
|
|
[InlineData("""{"Gateway":{"Endpoint":"HTTP://GW:5001"}}""", true)]
|
|
[InlineData("""{"Gateway":{}}""", true)]
|
|
[InlineData("""{"Gateway":{"Endpoint":"http://attacker:5001"}}""", false)]
|
|
public void An_unsaved_endpoint_does_not_match_the_stored_driver(string submitted, bool expected) =>
|
|
DriverCredentialPolicy.EndpointsMatchStored(DriverTypeNames.Galaxy, submitted,
|
|
["""{"Gateway":{"ApiKeySecretRef":"secret:galaxy/a/k"}}""", """{"Gateway":{"Endpoint":"http://gw:5001"}}"""])
|
|
.ShouldBe(expected);
|
|
|
|
[Fact]
|
|
public void A_type_without_endpoint_fields_never_matches() =>
|
|
DriverCredentialPolicy.EndpointsMatchStored(DriverTypeNames.Modbus, "{}", ["{}"]).ShouldBeFalse();
|
|
|
|
[Theory]
|
|
[InlineData("""{"Gateway":{"ApiKeySecretRef":"secret:galaxy/x"}}""", true)]
|
|
[InlineData("""{"Gateway":{"ApiKeySecretRef":"env:OTOPCUA_GALAXY_X"}}""", true)]
|
|
[InlineData("""{"Gateway":{"ApiKeySecretRef":"dev:k"}}""", false)]
|
|
[InlineData("""{"Gateway":{"ApiKeySecretRef":"literal"}}""", false)]
|
|
[InlineData("""{"Gateway":{"ApiKeySecretRef":"env:ConnectionStrings__ConfigDb"}}""", false)] // out of scope: refused anyway
|
|
public void HasCredentialReferences_detects_resolvable_references(string config, bool expected) =>
|
|
DriverCredentialPolicy.HasCredentialReferences(DriverTypeNames.Galaxy, config).ShouldBe(expected);
|
|
}
|