tests fixes and some product advisories tunes ups

This commit is contained in:
master
2026-01-30 07:57:43 +02:00
parent 644887997c
commit 55744f6a39
345 changed files with 26290 additions and 2267 deletions

View File

@@ -159,6 +159,147 @@ public sealed class OpaGateAdapterTests
Assert.Equal("decision-abc-123", result.Details["opaDecisionId"]);
}
[Fact]
public async Task EvaluateAsync_WithSupplyChainEvidence_IncludesEvidenceInInput()
{
object? capturedInput = null;
var mockClient = new CapturingMockOpaClient(
new OpaTypedResult<object>
{
Success = true,
DecisionId = "evidence-test",
Result = new { allow = true }
},
input => capturedInput = input);
var options = Options.Create(new OpaGateOptions
{
GateName = "TestOpaGate",
PolicyPath = "stella/test/allow"
});
var evidence = new OpaSupplyChainEvidence
{
Artifact = new OpaArtifactDescriptor
{
Digest = "sha256:abc123def456",
MediaType = "application/vnd.oci.image.manifest.v1+json",
Reference = "registry.example.com/app:v1.0.0"
},
Sbom = new OpaSbomReference
{
Digest = "sha256:sbom123",
Format = "cyclonedx-1.7",
ComponentCount = 42
},
Attestations = new[]
{
new OpaAttestationReference
{
Digest = "sha256:att123",
PredicateType = "https://slsa.dev/provenance/v1",
SignatureVerified = true,
RekorLogIndex = 12345
}
},
Transparency = new OpaTransparencyEvidence
{
Rekor = new[]
{
new OpaRekorReceipt
{
LogId = "rekor.sigstore.dev",
Uuid = "abc123",
LogIndex = 12345,
IntegratedTime = 1700000000,
Verified = true
}
}
},
Vex = new OpaVexEvidence
{
MergeDecision = new OpaVexMergeDecision
{
Algorithm = "trust-weighted-lattice-v1",
Inputs = new[]
{
new OpaVexMergeInput { Source = "vendor", Digest = "sha256:vex1", TrustTier = "authoritative" }
},
Decisions = new[]
{
new OpaVexDecision { Vuln = "CVE-2024-1234", Status = "not_affected", Justification = "component_not_present" }
},
HadConflicts = false
}
}
};
var context = new PolicyGateContext
{
Environment = "production",
HasReachabilityProof = true,
SupplyChainEvidence = evidence
};
var gate = new OpaGateAdapter(mockClient, options, NullLogger<OpaGateAdapter>.Instance);
await gate.EvaluateAsync(CreateMergeResult(), context);
Assert.NotNull(capturedInput);
// Serialize to JSON and verify structure
var json = JsonSerializer.Serialize(capturedInput, new JsonSerializerOptions { WriteIndented = true });
Assert.Contains("artifact", json);
Assert.Contains("sha256:abc123def456", json);
Assert.Contains("sbom", json);
Assert.Contains("cyclonedx-1.7", json);
Assert.Contains("attestations", json);
Assert.Contains("https://slsa.dev/provenance/v1", json);
Assert.Contains("transparency", json);
Assert.Contains("rekor", json);
Assert.Contains("vex", json);
Assert.Contains("trust-weighted-lattice-v1", json);
}
[Fact]
public async Task EvaluateAsync_WithoutSupplyChainEvidence_DoesNotIncludeEvidenceFields()
{
object? capturedInput = null;
var mockClient = new CapturingMockOpaClient(
new OpaTypedResult<object>
{
Success = true,
DecisionId = "no-evidence-test",
Result = new { allow = true }
},
input => capturedInput = input);
var options = Options.Create(new OpaGateOptions
{
GateName = "TestOpaGate",
PolicyPath = "stella/test/allow"
});
var gate = new OpaGateAdapter(mockClient, options, NullLogger<OpaGateAdapter>.Instance);
await gate.EvaluateAsync(CreateMergeResult(), CreateContext());
Assert.NotNull(capturedInput);
var json = JsonSerializer.Serialize(capturedInput);
// Should not contain evidence fields when not provided
Assert.DoesNotContain("\"artifact\"", json);
Assert.DoesNotContain("\"sbom\"", json);
Assert.DoesNotContain("\"attestations\"", json);
Assert.DoesNotContain("\"transparency\"", json);
Assert.DoesNotContain("\"vex\"", json);
// Should still contain standard fields
Assert.Contains("mergeResult", json);
Assert.Contains("context", json);
Assert.Contains("policy", json);
}
private sealed class MockOpaClient : IOpaClient
{
private readonly OpaTypedResult<object> _result;
@@ -207,4 +348,56 @@ public sealed class OpaGateAdapterTests
public Task DeletePolicyAsync(string policyId, CancellationToken cancellationToken = default)
=> Task.CompletedTask;
}
private sealed class CapturingMockOpaClient : IOpaClient
{
private readonly OpaTypedResult<object> _result;
private readonly Action<object> _captureInput;
public CapturingMockOpaClient(OpaTypedResult<object> result, Action<object> captureInput)
{
_result = result;
_captureInput = captureInput;
}
public Task<OpaEvaluationResult> EvaluateAsync(string policyPath, object input, CancellationToken cancellationToken = default)
{
_captureInput(input);
return Task.FromResult(new OpaEvaluationResult
{
Success = _result.Success,
DecisionId = _result.DecisionId,
Result = _result.Result,
Error = _result.Error
});
}
public Task<OpaTypedResult<TResult>> EvaluateAsync<TResult>(string policyPath, object input, CancellationToken cancellationToken = default)
{
_captureInput(input);
TResult? typedResult = default;
if (_result.Result is not null)
{
var json = JsonSerializer.Serialize(_result.Result, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
typedResult = JsonSerializer.Deserialize<TResult>(json, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
}
return Task.FromResult(new OpaTypedResult<TResult>
{
Success = _result.Success,
DecisionId = _result.DecisionId,
Result = typedResult,
Error = _result.Error
});
}
public Task<bool> HealthCheckAsync(CancellationToken cancellationToken = default)
=> Task.FromResult(true);
public Task UploadPolicyAsync(string policyId, string regoContent, CancellationToken cancellationToken = default)
=> Task.CompletedTask;
public Task DeletePolicyAsync(string policyId, CancellationToken cancellationToken = default)
=> Task.CompletedTask;
}
}