up
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
using System.Net;
|
||||
using System.Net.Http.Json;
|
||||
using System.Net;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
@@ -12,113 +12,113 @@ using StellaOps.Policy;
|
||||
using StellaOps.Scanner.WebService.Contracts;
|
||||
using StellaOps.Scanner.WebService.Services;
|
||||
using System.Linq;
|
||||
|
||||
namespace StellaOps.Scanner.WebService.Tests;
|
||||
|
||||
public sealed class ReportsEndpointsTests
|
||||
{
|
||||
private static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.Web)
|
||||
{
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
|
||||
};
|
||||
|
||||
[Fact]
|
||||
public async Task ReportsEndpointReturnsSignedEnvelope()
|
||||
{
|
||||
const string policyYaml = """
|
||||
version: "1.0"
|
||||
rules:
|
||||
- name: Block Critical
|
||||
severity: [Critical]
|
||||
action: block
|
||||
""";
|
||||
|
||||
var hmacKey = Convert.ToBase64String(Encoding.UTF8.GetBytes("scanner-report-hmac-key-2025!"));
|
||||
|
||||
using var factory = new ScannerApplicationFactory(configuration =>
|
||||
{
|
||||
configuration["scanner:signing:enabled"] = "true";
|
||||
configuration["scanner:signing:keyId"] = "scanner-report-signing";
|
||||
configuration["scanner:signing:algorithm"] = "hs256";
|
||||
configuration["scanner:signing:keyPem"] = hmacKey;
|
||||
configuration["scanner:features:enableSignedReports"] = "true";
|
||||
});
|
||||
|
||||
var store = factory.Services.GetRequiredService<PolicySnapshotStore>();
|
||||
await store.SaveAsync(
|
||||
new PolicySnapshotContent(policyYaml, PolicyDocumentFormat.Yaml, "tester", "seed", "initial"),
|
||||
CancellationToken.None);
|
||||
|
||||
using var client = factory.CreateClient();
|
||||
|
||||
var request = new ReportRequestDto
|
||||
{
|
||||
ImageDigest = "sha256:deadbeef",
|
||||
Findings = new[]
|
||||
{
|
||||
new PolicyPreviewFindingDto
|
||||
{
|
||||
Id = "finding-1",
|
||||
Severity = "Critical",
|
||||
Source = "NVD",
|
||||
Tags = new[] { "reachability:runtime" }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var response = await client.PostAsJsonAsync("/api/v1/reports", request);
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var raw = await response.Content.ReadAsStringAsync();
|
||||
Assert.False(string.IsNullOrWhiteSpace(raw), raw);
|
||||
var payload = JsonSerializer.Deserialize<ReportResponseDto>(raw, SerializerOptions);
|
||||
Assert.NotNull(payload);
|
||||
Assert.NotNull(payload!.Report);
|
||||
Assert.NotNull(payload.Dsse);
|
||||
Assert.StartsWith("report-", payload.Report.ReportId, StringComparison.Ordinal);
|
||||
Assert.Equal("blocked", payload.Report.Verdict);
|
||||
|
||||
var dsse = payload.Dsse!;
|
||||
Assert.Equal("application/vnd.stellaops.report+json", dsse.PayloadType);
|
||||
var decodedPayload = Convert.FromBase64String(dsse.Payload);
|
||||
var canonicalPayload = JsonSerializer.SerializeToUtf8Bytes(payload.Report, SerializerOptions);
|
||||
var expectedBase64 = Convert.ToBase64String(canonicalPayload);
|
||||
Assert.Equal(expectedBase64, dsse.Payload);
|
||||
|
||||
var reportVerdict = Assert.Single(payload.Report.Verdicts);
|
||||
Assert.Equal("NVD", reportVerdict.SourceTrust);
|
||||
Assert.Equal("runtime", reportVerdict.Reachability);
|
||||
Assert.NotNull(reportVerdict.Inputs);
|
||||
Assert.True(reportVerdict.Inputs!.ContainsKey("severityWeight"));
|
||||
Assert.Equal(PolicyScoringConfig.Default.Version, reportVerdict.ConfigVersion);
|
||||
|
||||
var signature = Assert.Single(dsse.Signatures);
|
||||
Assert.Equal("scanner-report-signing", signature.KeyId);
|
||||
Assert.Equal("hs256", signature.Algorithm, ignoreCase: true);
|
||||
|
||||
using var hmac = new System.Security.Cryptography.HMACSHA256(Convert.FromBase64String(hmacKey));
|
||||
var expectedSig = Convert.ToBase64String(hmac.ComputeHash(decodedPayload));
|
||||
var actualSig = signature.Signature;
|
||||
Assert.True(expectedSig == actualSig, $"expected:{expectedSig}, actual:{actualSig}");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReportsEndpointValidatesDigest()
|
||||
{
|
||||
using var factory = new ScannerApplicationFactory();
|
||||
using var client = factory.CreateClient();
|
||||
|
||||
var request = new ReportRequestDto
|
||||
{
|
||||
ImageDigest = "",
|
||||
Findings = Array.Empty<PolicyPreviewFindingDto>()
|
||||
};
|
||||
|
||||
var response = await client.PostAsJsonAsync("/api/v1/reports", request);
|
||||
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
namespace StellaOps.Scanner.WebService.Tests;
|
||||
|
||||
public sealed class ReportsEndpointsTests
|
||||
{
|
||||
private static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.Web)
|
||||
{
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
|
||||
};
|
||||
|
||||
[Fact]
|
||||
public async Task ReportsEndpointReturnsSignedEnvelope()
|
||||
{
|
||||
const string policyYaml = """
|
||||
version: "1.0"
|
||||
rules:
|
||||
- name: Block Critical
|
||||
severity: [Critical]
|
||||
action: block
|
||||
""";
|
||||
|
||||
var hmacKey = Convert.ToBase64String(Encoding.UTF8.GetBytes("scanner-report-hmac-key-2025!"));
|
||||
|
||||
using var factory = new ScannerApplicationFactory(configuration =>
|
||||
{
|
||||
configuration["scanner:signing:enabled"] = "true";
|
||||
configuration["scanner:signing:keyId"] = "scanner-report-signing";
|
||||
configuration["scanner:signing:algorithm"] = "hs256";
|
||||
configuration["scanner:signing:keyPem"] = hmacKey;
|
||||
configuration["scanner:features:enableSignedReports"] = "true";
|
||||
});
|
||||
|
||||
var store = factory.Services.GetRequiredService<PolicySnapshotStore>();
|
||||
await store.SaveAsync(
|
||||
new PolicySnapshotContent(policyYaml, PolicyDocumentFormat.Yaml, "tester", "seed", "initial"),
|
||||
CancellationToken.None);
|
||||
|
||||
using var client = factory.CreateClient();
|
||||
|
||||
var request = new ReportRequestDto
|
||||
{
|
||||
ImageDigest = "sha256:deadbeef",
|
||||
Findings = new[]
|
||||
{
|
||||
new PolicyPreviewFindingDto
|
||||
{
|
||||
Id = "finding-1",
|
||||
Severity = "Critical",
|
||||
Source = "NVD",
|
||||
Tags = new[] { "reachability:runtime" }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var response = await client.PostAsJsonAsync("/api/v1/reports", request);
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var raw = await response.Content.ReadAsStringAsync();
|
||||
Assert.False(string.IsNullOrWhiteSpace(raw), raw);
|
||||
var payload = JsonSerializer.Deserialize<ReportResponseDto>(raw, SerializerOptions);
|
||||
Assert.NotNull(payload);
|
||||
Assert.NotNull(payload!.Report);
|
||||
Assert.NotNull(payload.Dsse);
|
||||
Assert.StartsWith("report-", payload.Report.ReportId, StringComparison.Ordinal);
|
||||
Assert.Equal("blocked", payload.Report.Verdict);
|
||||
|
||||
var dsse = payload.Dsse!;
|
||||
Assert.Equal("application/vnd.stellaops.report+json", dsse.PayloadType);
|
||||
var decodedPayload = Convert.FromBase64String(dsse.Payload);
|
||||
var canonicalPayload = JsonSerializer.SerializeToUtf8Bytes(payload.Report, SerializerOptions);
|
||||
var expectedBase64 = Convert.ToBase64String(canonicalPayload);
|
||||
Assert.Equal(expectedBase64, dsse.Payload);
|
||||
|
||||
var reportVerdict = Assert.Single(payload.Report.Verdicts);
|
||||
Assert.Equal("NVD", reportVerdict.SourceTrust);
|
||||
Assert.Equal("runtime", reportVerdict.Reachability);
|
||||
Assert.NotNull(reportVerdict.Inputs);
|
||||
Assert.True(reportVerdict.Inputs!.ContainsKey("severityWeight"));
|
||||
Assert.Equal(PolicyScoringConfig.Default.Version, reportVerdict.ConfigVersion);
|
||||
|
||||
var signature = Assert.Single(dsse.Signatures);
|
||||
Assert.Equal("scanner-report-signing", signature.KeyId);
|
||||
Assert.Equal("hs256", signature.Algorithm, ignoreCase: true);
|
||||
|
||||
using var hmac = new System.Security.Cryptography.HMACSHA256(Convert.FromBase64String(hmacKey));
|
||||
var expectedSig = Convert.ToBase64String(hmac.ComputeHash(decodedPayload));
|
||||
var actualSig = signature.Signature;
|
||||
Assert.True(expectedSig == actualSig, $"expected:{expectedSig}, actual:{actualSig}");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReportsEndpointValidatesDigest()
|
||||
{
|
||||
using var factory = new ScannerApplicationFactory();
|
||||
using var client = factory.CreateClient();
|
||||
|
||||
var request = new ReportRequestDto
|
||||
{
|
||||
ImageDigest = "",
|
||||
Findings = Array.Empty<PolicyPreviewFindingDto>()
|
||||
};
|
||||
|
||||
var response = await client.PostAsJsonAsync("/api/v1/reports", request);
|
||||
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReportsEndpointReturnsServiceUnavailableWhenPolicyMissing()
|
||||
{
|
||||
using var factory = new ScannerApplicationFactory();
|
||||
|
||||
Reference in New Issue
Block a user