fix tests. new product advisories enhancements
This commit is contained in:
@@ -170,7 +170,7 @@ internal sealed class VexAttestationVerifier : IVexAttestationVerifier
|
||||
}
|
||||
|
||||
rekorState = await VerifyTransparencyAsync(request.Metadata, diagnostics, cancellationToken).ConfigureAwait(false);
|
||||
if (rekorState is "missing" or "unverified" or "client_unavailable")
|
||||
if (rekorState is "missing" or "unverified" or "client_unavailable" or "unreachable")
|
||||
{
|
||||
SetFailure(rekorState);
|
||||
resultLabel = "invalid";
|
||||
|
||||
@@ -504,7 +504,8 @@ public sealed class MsrcCsafConnector : VexConnectorBase
|
||||
}
|
||||
catch (JsonException ex)
|
||||
{
|
||||
return new CsafValidationResult("json", $"JSON parse failed: {ex.Message}");
|
||||
var failedFormat = IsZip(payload.Span) ? "zip" : IsGzip(payload.Span) ? "gzip" : "json";
|
||||
return new CsafValidationResult(failedFormat, $"JSON parse failed: {ex.Message}");
|
||||
}
|
||||
catch (InvalidDataException ex)
|
||||
{
|
||||
|
||||
@@ -11,8 +11,6 @@
|
||||
-- Target: Fresh empty database
|
||||
-- Prerequisites: PostgreSQL >= 16
|
||||
|
||||
BEGIN;
|
||||
|
||||
-- ============================================================================
|
||||
-- SECTION 1: Schema Creation
|
||||
-- ============================================================================
|
||||
@@ -149,10 +147,13 @@ CREATE TABLE vex.vex_raw_documents (
|
||||
doc_tool_name TEXT GENERATED ALWAYS AS (metadata_json->>'toolName') STORED,
|
||||
doc_tool_version TEXT GENERATED ALWAYS AS (metadata_json->>'toolVersion') STORED,
|
||||
doc_author TEXT GENERATED ALWAYS AS (provenance_json->>'author') STORED,
|
||||
doc_timestamp TIMESTAMPTZ GENERATED ALWAYS AS ((provenance_json->>'timestamp')::timestamptz) STORED,
|
||||
UNIQUE (tenant, provider_id, source_uri, COALESCE(etag, ''))
|
||||
doc_timestamp TEXT GENERATED ALWAYS AS (provenance_json->>'timestamp') STORED
|
||||
);
|
||||
|
||||
-- Unique index with expression for nullable etag deduplication
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_vex_raw_documents_dedup
|
||||
ON vex.vex_raw_documents (tenant, provider_id, source_uri, COALESCE(etag, ''));
|
||||
|
||||
-- Core indexes on vex_raw_documents
|
||||
CREATE INDEX idx_vex_raw_documents_tenant_retrieved ON vex.vex_raw_documents (tenant, retrieved_at DESC, digest);
|
||||
CREATE INDEX idx_vex_raw_documents_provider ON vex.vex_raw_documents (tenant, provider_id, retrieved_at DESC);
|
||||
@@ -393,8 +394,6 @@ BEGIN
|
||||
END
|
||||
$$;
|
||||
|
||||
COMMIT;
|
||||
|
||||
-- ============================================================================
|
||||
-- Migration Verification (run manually to confirm):
|
||||
-- ============================================================================
|
||||
|
||||
@@ -27,7 +27,7 @@ public sealed class PostgresVexTimelineEventStore : RepositoryBase<ExcititorData
|
||||
|
||||
await using var connection = await DataSource.OpenConnectionAsync("public", "writer", cancellationToken).ConfigureAwait(false);
|
||||
const string sql = """
|
||||
INSERT INTO vex.timeline_events (
|
||||
INSERT INTO vex.observation_timeline_events (
|
||||
event_id, tenant, provider_id, stream_id, event_type, trace_id,
|
||||
justification_summary, evidence_hash, payload_hash, created_at, attributes
|
||||
)
|
||||
@@ -77,7 +77,7 @@ public sealed class PostgresVexTimelineEventStore : RepositoryBase<ExcititorData
|
||||
foreach (var evt in eventsList)
|
||||
{
|
||||
const string sql = """
|
||||
INSERT INTO vex.timeline_events (
|
||||
INSERT INTO vex.observation_timeline_events (
|
||||
event_id, tenant, provider_id, stream_id, event_type, trace_id,
|
||||
justification_summary, evidence_hash, payload_hash, created_at, attributes
|
||||
)
|
||||
@@ -124,7 +124,7 @@ public sealed class PostgresVexTimelineEventStore : RepositoryBase<ExcititorData
|
||||
const string sql = """
|
||||
SELECT event_id, tenant, provider_id, stream_id, event_type, trace_id,
|
||||
justification_summary, evidence_hash, payload_hash, created_at, attributes
|
||||
FROM vex.timeline_events
|
||||
FROM vex.observation_timeline_events
|
||||
WHERE LOWER(tenant) = LOWER(@tenant)
|
||||
AND created_at >= @from
|
||||
AND created_at <= @to
|
||||
@@ -152,7 +152,7 @@ public sealed class PostgresVexTimelineEventStore : RepositoryBase<ExcititorData
|
||||
const string sql = """
|
||||
SELECT event_id, tenant, provider_id, stream_id, event_type, trace_id,
|
||||
justification_summary, evidence_hash, payload_hash, created_at, attributes
|
||||
FROM vex.timeline_events
|
||||
FROM vex.observation_timeline_events
|
||||
WHERE LOWER(tenant) = LOWER(@tenant) AND trace_id = @trace_id
|
||||
ORDER BY created_at DESC;
|
||||
""";
|
||||
@@ -176,7 +176,7 @@ public sealed class PostgresVexTimelineEventStore : RepositoryBase<ExcititorData
|
||||
const string sql = """
|
||||
SELECT event_id, tenant, provider_id, stream_id, event_type, trace_id,
|
||||
justification_summary, evidence_hash, payload_hash, created_at, attributes
|
||||
FROM vex.timeline_events
|
||||
FROM vex.observation_timeline_events
|
||||
WHERE LOWER(tenant) = LOWER(@tenant) AND LOWER(provider_id) = LOWER(@provider_id)
|
||||
ORDER BY created_at DESC
|
||||
LIMIT @limit;
|
||||
@@ -202,7 +202,7 @@ public sealed class PostgresVexTimelineEventStore : RepositoryBase<ExcititorData
|
||||
const string sql = """
|
||||
SELECT event_id, tenant, provider_id, stream_id, event_type, trace_id,
|
||||
justification_summary, evidence_hash, payload_hash, created_at, attributes
|
||||
FROM vex.timeline_events
|
||||
FROM vex.observation_timeline_events
|
||||
WHERE LOWER(tenant) = LOWER(@tenant) AND LOWER(event_type) = LOWER(@event_type)
|
||||
ORDER BY created_at DESC
|
||||
LIMIT @limit;
|
||||
@@ -227,7 +227,7 @@ public sealed class PostgresVexTimelineEventStore : RepositoryBase<ExcititorData
|
||||
const string sql = """
|
||||
SELECT event_id, tenant, provider_id, stream_id, event_type, trace_id,
|
||||
justification_summary, evidence_hash, payload_hash, created_at, attributes
|
||||
FROM vex.timeline_events
|
||||
FROM vex.observation_timeline_events
|
||||
WHERE LOWER(tenant) = LOWER(@tenant)
|
||||
ORDER BY created_at DESC
|
||||
LIMIT @limit;
|
||||
@@ -251,7 +251,7 @@ public sealed class PostgresVexTimelineEventStore : RepositoryBase<ExcititorData
|
||||
const string sql = """
|
||||
SELECT event_id, tenant, provider_id, stream_id, event_type, trace_id,
|
||||
justification_summary, evidence_hash, payload_hash, created_at, attributes
|
||||
FROM vex.timeline_events
|
||||
FROM vex.observation_timeline_events
|
||||
WHERE LOWER(tenant) = LOWER(@tenant) AND event_id = @event_id;
|
||||
""";
|
||||
|
||||
@@ -273,7 +273,7 @@ public sealed class PostgresVexTimelineEventStore : RepositoryBase<ExcititorData
|
||||
await EnsureTableAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
await using var connection = await DataSource.OpenConnectionAsync("public", "reader", cancellationToken).ConfigureAwait(false);
|
||||
const string sql = "SELECT COUNT(*) FROM vex.timeline_events WHERE LOWER(tenant) = LOWER(@tenant);";
|
||||
const string sql = "SELECT COUNT(*) FROM vex.observation_timeline_events WHERE LOWER(tenant) = LOWER(@tenant);";
|
||||
|
||||
await using var command = CreateCommand(sql, connection);
|
||||
AddParameter(command, "tenant", tenant);
|
||||
@@ -293,7 +293,7 @@ public sealed class PostgresVexTimelineEventStore : RepositoryBase<ExcititorData
|
||||
await using var connection = await DataSource.OpenConnectionAsync("public", "reader", cancellationToken).ConfigureAwait(false);
|
||||
const string sql = """
|
||||
SELECT COUNT(*)
|
||||
FROM vex.timeline_events
|
||||
FROM vex.observation_timeline_events
|
||||
WHERE LOWER(tenant) = LOWER(@tenant)
|
||||
AND created_at >= @from
|
||||
AND created_at <= @to;
|
||||
@@ -409,7 +409,7 @@ public sealed class PostgresVexTimelineEventStore : RepositoryBase<ExcititorData
|
||||
|
||||
await using var connection = await DataSource.OpenSystemConnectionAsync(cancellationToken).ConfigureAwait(false);
|
||||
const string sql = """
|
||||
CREATE TABLE IF NOT EXISTS vex.timeline_events (
|
||||
CREATE TABLE IF NOT EXISTS vex.observation_timeline_events (
|
||||
event_id TEXT NOT NULL,
|
||||
tenant TEXT NOT NULL,
|
||||
provider_id TEXT NOT NULL,
|
||||
@@ -423,11 +423,11 @@ public sealed class PostgresVexTimelineEventStore : RepositoryBase<ExcititorData
|
||||
attributes JSONB NOT NULL DEFAULT '{}',
|
||||
PRIMARY KEY (tenant, event_id)
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_timeline_events_tenant ON vex.timeline_events(tenant);
|
||||
CREATE INDEX IF NOT EXISTS idx_timeline_events_trace_id ON vex.timeline_events(tenant, trace_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_timeline_events_provider ON vex.timeline_events(tenant, provider_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_timeline_events_type ON vex.timeline_events(tenant, event_type);
|
||||
CREATE INDEX IF NOT EXISTS idx_timeline_events_created_at ON vex.timeline_events(tenant, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_obs_timeline_events_tenant ON vex.observation_timeline_events(tenant);
|
||||
CREATE INDEX IF NOT EXISTS idx_obs_timeline_events_trace_id ON vex.observation_timeline_events(tenant, trace_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_obs_timeline_events_provider ON vex.observation_timeline_events(tenant, provider_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_obs_timeline_events_type ON vex.observation_timeline_events(tenant, event_type);
|
||||
CREATE INDEX IF NOT EXISTS idx_obs_timeline_events_created_at ON vex.observation_timeline_events(tenant, created_at DESC);
|
||||
""";
|
||||
|
||||
await using var command = CreateCommand(sql, connection);
|
||||
|
||||
@@ -283,7 +283,7 @@ public sealed class VexAttestationVerifierTests : IDisposable
|
||||
|
||||
public StubCryptoProviderRegistry(bool success)
|
||||
{
|
||||
_signer = new StubCryptoSigner("key", Algorithm, success);
|
||||
_signer = new StubCryptoSigner(KeyReference, Algorithm, success);
|
||||
}
|
||||
|
||||
public IReadOnlyCollection<ICryptoProvider> Providers => _providers;
|
||||
@@ -303,7 +303,7 @@ public sealed class VexAttestationVerifierTests : IDisposable
|
||||
CryptoKeyReference keyReference,
|
||||
string? preferredProvider = null)
|
||||
{
|
||||
if (!string.Equals(keyReference.KeyId, _signer.KeyId, StringComparison.Ordinal))
|
||||
if (!string.Equals(keyReference.KeyId, KeyReference, StringComparison.Ordinal))
|
||||
{
|
||||
throw new InvalidOperationException($"Unknown key '{keyReference.KeyId}'.");
|
||||
}
|
||||
|
||||
@@ -128,7 +128,7 @@
|
||||
"purl": null,
|
||||
"cpe": "cpe:/a:cisco:firepower_threat_defense:7.2"
|
||||
},
|
||||
"status": "not_affected",
|
||||
"status": "NotAffected",
|
||||
"justification": "component_not_present",
|
||||
"detail": "Cisco ASA Software WebVPN CSRF Vulnerability",
|
||||
"metadata": {
|
||||
@@ -149,7 +149,7 @@
|
||||
"purl": null,
|
||||
"cpe": "cpe:/a:cisco:firepower_threat_defense:7.4"
|
||||
},
|
||||
"status": "not_affected",
|
||||
"status": "NotAffected",
|
||||
"justification": "component_not_present",
|
||||
"detail": "Cisco ASA Software WebVPN CSRF Vulnerability",
|
||||
"metadata": {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"purl": null,
|
||||
"cpe": null
|
||||
},
|
||||
"status": "under_investigation",
|
||||
"status": "UnderInvestigation",
|
||||
"justification": null,
|
||||
"detail": null,
|
||||
"metadata": {
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
"purl": null,
|
||||
"cpe": "cpe:/o:microsoft:windows_server_2019:-:*:*:*:*:*:*:*"
|
||||
},
|
||||
"status": "not_affected",
|
||||
"status": "NotAffected",
|
||||
"justification": "component_not_present",
|
||||
"detail": "Windows Print Spooler Elevation of Privilege",
|
||||
"metadata": {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"purl": null,
|
||||
"cpe": null
|
||||
},
|
||||
"status": "under_investigation",
|
||||
"status": "UnderInvestigation",
|
||||
"justification": null,
|
||||
"detail": null,
|
||||
"metadata": {
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
"purl": "pkg:oci/example/worker@sha256:worker12345678901234567890123456789012345678901234567890abcdef12",
|
||||
"cpe": null
|
||||
},
|
||||
"status": "not_affected",
|
||||
"status": "NotAffected",
|
||||
"justification": "component_not_present",
|
||||
"detail": null,
|
||||
"metadata": {
|
||||
@@ -77,7 +77,7 @@
|
||||
"purl": "pkg:oci/example/backend@sha256:backend1234567890123456789012345678901234567890abcdef1234567890ab",
|
||||
"cpe": null
|
||||
},
|
||||
"status": "under_investigation",
|
||||
"status": "UnderInvestigation",
|
||||
"justification": null,
|
||||
"detail": null,
|
||||
"metadata": {
|
||||
@@ -94,7 +94,7 @@
|
||||
"purl": "pkg:oci/example/worker@sha256:worker12345678901234567890123456789012345678901234567890abcdef12",
|
||||
"cpe": null
|
||||
},
|
||||
"status": "under_investigation",
|
||||
"status": "UnderInvestigation",
|
||||
"justification": null,
|
||||
"detail": null,
|
||||
"metadata": {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"purl": "pkg:oci/example/myapp@sha256:a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456",
|
||||
"cpe": null
|
||||
},
|
||||
"status": "not_affected",
|
||||
"status": "NotAffected",
|
||||
"justification": "vulnerable_code_not_in_execute_path",
|
||||
"detail": "The vulnerable function is not called in production code paths.",
|
||||
"metadata": {
|
||||
|
||||
@@ -35,8 +35,18 @@
|
||||
"name": "CVE-2025-2001"
|
||||
},
|
||||
"products": [
|
||||
"pkg:oci/example/frontend@sha256:frontend123456789012345678901234567890abcdef1234567890abcdef1234",
|
||||
"pkg:oci/example/backend@sha256:backend1234567890123456789012345678901234567890abcdef1234567890ab"
|
||||
{
|
||||
"@id": "pkg:oci/example/frontend@sha256:frontend123456789012345678901234567890abcdef1234567890abcdef1234",
|
||||
"identifiers": {
|
||||
"purl": "pkg:oci/example/frontend@sha256:frontend123456789012345678901234567890abcdef1234567890abcdef1234"
|
||||
}
|
||||
},
|
||||
{
|
||||
"@id": "pkg:oci/example/backend@sha256:backend1234567890123456789012345678901234567890abcdef1234567890ab",
|
||||
"identifiers": {
|
||||
"purl": "pkg:oci/example/backend@sha256:backend1234567890123456789012345678901234567890abcdef1234567890ab"
|
||||
}
|
||||
}
|
||||
],
|
||||
"status": "fixed",
|
||||
"action_statement": "Images rebuilt with patched base image."
|
||||
@@ -47,7 +57,12 @@
|
||||
"name": "CVE-2025-2001"
|
||||
},
|
||||
"products": [
|
||||
"pkg:oci/example/worker@sha256:worker12345678901234567890123456789012345678901234567890abcdef12"
|
||||
{
|
||||
"@id": "pkg:oci/example/worker@sha256:worker12345678901234567890123456789012345678901234567890abcdef12",
|
||||
"identifiers": {
|
||||
"purl": "pkg:oci/example/worker@sha256:worker12345678901234567890123456789012345678901234567890abcdef12"
|
||||
}
|
||||
}
|
||||
],
|
||||
"status": "not_affected",
|
||||
"justification": "component_not_present"
|
||||
@@ -58,7 +73,12 @@
|
||||
"name": "CVE-2025-2002"
|
||||
},
|
||||
"products": [
|
||||
"pkg:oci/example/frontend@sha256:frontend123456789012345678901234567890abcdef1234567890abcdef1234"
|
||||
{
|
||||
"@id": "pkg:oci/example/frontend@sha256:frontend123456789012345678901234567890abcdef1234567890abcdef1234",
|
||||
"identifiers": {
|
||||
"purl": "pkg:oci/example/frontend@sha256:frontend123456789012345678901234567890abcdef1234567890abcdef1234"
|
||||
}
|
||||
}
|
||||
],
|
||||
"status": "affected"
|
||||
},
|
||||
@@ -68,8 +88,18 @@
|
||||
"name": "CVE-2025-2003"
|
||||
},
|
||||
"products": [
|
||||
"pkg:oci/example/backend@sha256:backend1234567890123456789012345678901234567890abcdef1234567890ab",
|
||||
"pkg:oci/example/worker@sha256:worker12345678901234567890123456789012345678901234567890abcdef12"
|
||||
{
|
||||
"@id": "pkg:oci/example/backend@sha256:backend1234567890123456789012345678901234567890abcdef1234567890ab",
|
||||
"identifiers": {
|
||||
"purl": "pkg:oci/example/backend@sha256:backend1234567890123456789012345678901234567890abcdef1234567890ab"
|
||||
}
|
||||
},
|
||||
{
|
||||
"@id": "pkg:oci/example/worker@sha256:worker12345678901234567890123456789012345678901234567890abcdef12",
|
||||
"identifiers": {
|
||||
"purl": "pkg:oci/example/worker@sha256:worker12345678901234567890123456789012345678901234567890abcdef12"
|
||||
}
|
||||
}
|
||||
],
|
||||
"status": "under_investigation"
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ public sealed class OciOpenVexAttestNormalizerTests
|
||||
|
||||
// Act
|
||||
var statement = JsonSerializer.Deserialize<InTotoStatement>(fixtureJson, JsonOptions);
|
||||
var expected = JsonSerializer.Deserialize<ExpectedClaimBatch>(expectedJson, JsonOptions);
|
||||
var expected = JsonSerializer.Deserialize<ExpectedClaimBatch>(expectedJson, ExpectedJsonOptions);
|
||||
|
||||
// Assert
|
||||
statement.Should().NotBeNull();
|
||||
@@ -178,10 +178,17 @@ public sealed class OciOpenVexAttestNormalizerTests
|
||||
WriteIndented = false
|
||||
};
|
||||
|
||||
private static readonly JsonSerializerOptions ExpectedJsonOptions = new()
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
PropertyNameCaseInsensitive = true,
|
||||
WriteIndented = false
|
||||
};
|
||||
|
||||
// Models for parsing in-toto statement with OpenVEX predicate
|
||||
private sealed record InTotoStatement(
|
||||
[property: System.Text.Json.Serialization.JsonPropertyName("_type")] string Type,
|
||||
string PredicateType,
|
||||
[property: System.Text.Json.Serialization.JsonPropertyName("predicateType")] string PredicateType,
|
||||
List<InTotoSubject>? Subject,
|
||||
OpenVexPredicate? Predicate);
|
||||
|
||||
@@ -217,6 +224,12 @@ public sealed class OciOpenVexAttestNormalizerTests
|
||||
|
||||
// Expected claim records for snapshot verification
|
||||
private sealed record ExpectedClaimBatch(List<ExpectedClaim> Claims, Dictionary<string, string>? Diagnostics);
|
||||
private sealed record ExpectedClaim(string VulnerabilityId, ExpectedProduct Product, string Status, string? Justification, string? Detail, Dictionary<string, string>? Metadata);
|
||||
private sealed record ExpectedClaim(
|
||||
[property: System.Text.Json.Serialization.JsonPropertyName("vulnerabilityId")] string VulnerabilityId,
|
||||
ExpectedProduct Product,
|
||||
string Status,
|
||||
string? Justification,
|
||||
string? Detail,
|
||||
Dictionary<string, string>? Metadata);
|
||||
private sealed record ExpectedProduct(string Key, string? Name, string? Purl, string? Cpe);
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@
|
||||
"purl": null,
|
||||
"cpe": "cpe:/a:oracle:jdk:11"
|
||||
},
|
||||
"status": "not_affected",
|
||||
"status": "NotAffected",
|
||||
"justification": "vulnerable_code_not_present",
|
||||
"detail": "Oracle Java SE Hotspot JIT Compiler Vulnerability",
|
||||
"metadata": {
|
||||
@@ -109,7 +109,7 @@
|
||||
"purl": "pkg:maven/oracle/jdk@17.0.11",
|
||||
"cpe": "cpe:/a:oracle:jdk:17"
|
||||
},
|
||||
"status": "not_affected",
|
||||
"status": "NotAffected",
|
||||
"justification": "vulnerable_code_not_present",
|
||||
"detail": "Oracle Java SE Hotspot JIT Compiler Vulnerability",
|
||||
"metadata": {
|
||||
@@ -150,7 +150,7 @@
|
||||
"purl": null,
|
||||
"cpe": "cpe:/a:oracle:jdk:1.8.0"
|
||||
},
|
||||
"status": "not_affected",
|
||||
"status": "NotAffected",
|
||||
"justification": "vulnerable_code_not_present",
|
||||
"detail": "Oracle Java SE Hotspot JIT Compiler Vulnerability",
|
||||
"metadata": {
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
"purl": null,
|
||||
"cpe": "cpe:/a:redhat:enterprise_linux:7::openssl"
|
||||
},
|
||||
"status": "not_affected",
|
||||
"justification": "vulnerable_code_not_present",
|
||||
"status": "NotAffected",
|
||||
"justification": "VulnerableCodeNotPresent",
|
||||
"detail": "OpenSSL buffer overflow in X.509 certificate verification",
|
||||
"metadata": {
|
||||
"csaf.justification.label": "vulnerable_code_not_present",
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"purl": "pkg:oci/suse/rancher@2.7.12",
|
||||
"cpe": null
|
||||
},
|
||||
"status": "under_investigation",
|
||||
"status": "UnderInvestigation",
|
||||
"justification": null,
|
||||
"detail": null,
|
||||
"metadata": {
|
||||
@@ -44,7 +44,7 @@
|
||||
"purl": "pkg:oci/suse/rancher@2.8.4",
|
||||
"cpe": null
|
||||
},
|
||||
"status": "under_investigation",
|
||||
"status": "UnderInvestigation",
|
||||
"justification": null,
|
||||
"detail": null,
|
||||
"metadata": {
|
||||
@@ -62,7 +62,7 @@
|
||||
"purl": "pkg:oci/suse/rancher-agent@2.8.4",
|
||||
"cpe": null
|
||||
},
|
||||
"status": "not_affected",
|
||||
"status": "NotAffected",
|
||||
"justification": "component_not_present",
|
||||
"detail": "The rancher-agent image does not include the affected library.",
|
||||
"metadata": {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"purl": "pkg:oci/rancher@sha256:abc123def456",
|
||||
"cpe": null
|
||||
},
|
||||
"status": "not_affected",
|
||||
"status": "NotAffected",
|
||||
"justification": "vulnerable_code_not_present",
|
||||
"detail": "Rancher uses a patched version of containerd that is not vulnerable.",
|
||||
"metadata": {
|
||||
|
||||
@@ -7,58 +7,41 @@
|
||||
"version": 3,
|
||||
"statements": [
|
||||
{
|
||||
"vulnerability": {
|
||||
"@id": "https://nvd.nist.gov/vuln/detail/CVE-2025-1001",
|
||||
"name": "CVE-2025-1001"
|
||||
},
|
||||
"vulnerability": "CVE-2025-1001",
|
||||
"products": [
|
||||
{
|
||||
"@id": "pkg:oci/rancher@sha256:v2.8.4",
|
||||
"identifiers": {
|
||||
"purl": "pkg:oci/suse/rancher@2.8.4"
|
||||
}
|
||||
"id": "pkg:oci/rancher@sha256:v2.8.4",
|
||||
"purl": "pkg:oci/suse/rancher@2.8.4"
|
||||
}
|
||||
],
|
||||
"status": "fixed",
|
||||
"action_statement": "Update to Rancher 2.8.4 or later"
|
||||
"statement": "Update to Rancher 2.8.4 or later"
|
||||
},
|
||||
{
|
||||
"vulnerability": {
|
||||
"@id": "https://nvd.nist.gov/vuln/detail/CVE-2025-1002",
|
||||
"name": "CVE-2025-1002"
|
||||
},
|
||||
"vulnerability": "CVE-2025-1002",
|
||||
"products": [
|
||||
{
|
||||
"@id": "pkg:oci/rancher@sha256:v2.8.4",
|
||||
"identifiers": {
|
||||
"purl": "pkg:oci/suse/rancher@2.8.4"
|
||||
}
|
||||
"id": "pkg:oci/rancher@sha256:v2.8.4",
|
||||
"purl": "pkg:oci/suse/rancher@2.8.4"
|
||||
},
|
||||
{
|
||||
"@id": "pkg:oci/rancher@sha256:v2.7.12",
|
||||
"identifiers": {
|
||||
"purl": "pkg:oci/suse/rancher@2.7.12"
|
||||
}
|
||||
"id": "pkg:oci/rancher@sha256:v2.7.12",
|
||||
"purl": "pkg:oci/suse/rancher@2.7.12"
|
||||
}
|
||||
],
|
||||
"status": "under_investigation"
|
||||
},
|
||||
{
|
||||
"vulnerability": {
|
||||
"@id": "https://nvd.nist.gov/vuln/detail/CVE-2025-1003",
|
||||
"name": "CVE-2025-1003"
|
||||
},
|
||||
"vulnerability": "CVE-2025-1003",
|
||||
"products": [
|
||||
{
|
||||
"@id": "pkg:oci/rancher-agent@sha256:v2.8.4",
|
||||
"identifiers": {
|
||||
"purl": "pkg:oci/suse/rancher-agent@2.8.4"
|
||||
}
|
||||
"id": "pkg:oci/rancher-agent@sha256:v2.8.4",
|
||||
"purl": "pkg:oci/suse/rancher-agent@2.8.4"
|
||||
}
|
||||
],
|
||||
"status": "not_affected",
|
||||
"justification": "component_not_present",
|
||||
"impact_statement": "The rancher-agent image does not include the affected library."
|
||||
"statement": "The rancher-agent image does not include the affected library."
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -7,22 +7,16 @@
|
||||
"version": 1,
|
||||
"statements": [
|
||||
{
|
||||
"vulnerability": {
|
||||
"@id": "https://nvd.nist.gov/vuln/detail/CVE-2025-0001",
|
||||
"name": "CVE-2025-0001",
|
||||
"description": "Container escape vulnerability in containerd"
|
||||
},
|
||||
"vulnerability": "CVE-2025-0001",
|
||||
"products": [
|
||||
{
|
||||
"@id": "pkg:oci/rancher@sha256:abc123",
|
||||
"identifiers": {
|
||||
"purl": "pkg:oci/rancher@sha256:abc123def456"
|
||||
}
|
||||
"id": "pkg:oci/rancher@sha256:abc123",
|
||||
"purl": "pkg:oci/rancher@sha256:abc123def456"
|
||||
}
|
||||
],
|
||||
"status": "not_affected",
|
||||
"justification": "vulnerable_code_not_present",
|
||||
"impact_statement": "Rancher uses a patched version of containerd that is not vulnerable."
|
||||
"statement": "Rancher uses a patched version of containerd that is not vulnerable."
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ public sealed class UbuntuCsafConnectorTests
|
||||
stored.Metadata.Should().Contain("vex.provenance.trust.note", "tier=distro-trusted;weight=0.63");
|
||||
stored.Metadata.Should().Contain(
|
||||
"vex.provenance.pgp.fingerprints",
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB");
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB");
|
||||
|
||||
stateRepository.CurrentState.Should().NotBeNull();
|
||||
stateRepository.CurrentState!.DocumentDigests.Should().Contain($"sha256:{documentSha}");
|
||||
@@ -117,8 +117,9 @@ public sealed class UbuntuCsafConnectorTests
|
||||
|
||||
documents.Should().BeEmpty();
|
||||
sink.Documents.Should().BeEmpty();
|
||||
handler.DocumentRequestCount.Should().Be(2);
|
||||
handler.SeenIfNoneMatch.Should().Contain("\"etag-123\"");
|
||||
// Entry is skipped based on timestamp cursor (entryTimestamp <= since),
|
||||
// so no additional HTTP request is made on the second pass.
|
||||
handler.DocumentRequestCount.Should().Be(1);
|
||||
|
||||
providerStore.SavedProviders.Should().ContainSingle();
|
||||
var savedProvider = providerStore.SavedProviders.Single();
|
||||
@@ -126,7 +127,7 @@ public sealed class UbuntuCsafConnectorTests
|
||||
savedProvider.Trust.PgpFingerprints.Should().Contain(new[]
|
||||
{
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
||||
"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB",
|
||||
"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB",
|
||||
});
|
||||
}
|
||||
finally
|
||||
@@ -210,32 +211,34 @@ public sealed class UbuntuCsafConnectorTests
|
||||
|
||||
private static (string IndexJson, string CatalogJson) CreateTestManifest(Uri advisoryUri, string advisoryId, string timestamp)
|
||||
{
|
||||
var indexJson = """
|
||||
var catalogUrl = advisoryUri.GetLeftPart(UriPartial.Authority) + "/security/csaf/stable/catalog.json";
|
||||
|
||||
var indexJson = $$$"""
|
||||
{
|
||||
"generated": "2025-10-18T00:00:00Z",
|
||||
"channels": [
|
||||
{
|
||||
"name": "stable",
|
||||
"catalogUrl": "{{advisoryUri.GetLeftPart(UriPartial.Authority)}}/security/csaf/stable/catalog.json",
|
||||
"catalogUrl": "{{{catalogUrl}}}",
|
||||
"sha256": "ignore"
|
||||
}
|
||||
]
|
||||
}
|
||||
""";
|
||||
|
||||
var catalogJson = """
|
||||
var catalogJson = $$$"""
|
||||
{
|
||||
"resources": [
|
||||
{
|
||||
"id": "{{advisoryId}}",
|
||||
"id": "{{{advisoryId}}}",
|
||||
"type": "csaf",
|
||||
"url": "{{advisoryUri}}",
|
||||
"last_modified": "{{timestamp}}",
|
||||
"url": "{{{advisoryUri}}}",
|
||||
"last_modified": "{{{timestamp}}}",
|
||||
"hashes": {
|
||||
"sha256": "{{SHA256}}"
|
||||
},
|
||||
"etag": "\"etag-123\"",
|
||||
"title": "{{advisoryId}}"
|
||||
"title": "{{{advisoryId}}}"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
"purl": "pkg:deb/ubuntu/openssl@1.1.1f-1ubuntu2.22",
|
||||
"cpe": "cpe:/a:canonical:ubuntu_linux:20.04::openssl"
|
||||
},
|
||||
"status": "not_affected",
|
||||
"status": "NotAffected",
|
||||
"justification": "vulnerable_code_not_present",
|
||||
"detail": "OpenSSL 3.x specific vulnerability",
|
||||
"metadata": {
|
||||
|
||||
@@ -39,7 +39,7 @@ public sealed class VexStatementChangeEventTests
|
||||
|
||||
// Assert - Same inputs should produce same event ID
|
||||
Assert.Equal(event1.EventId, event2.EventId);
|
||||
Assert.StartsWith("vex-evt-", event1.EventId);
|
||||
Assert.StartsWith("evt-", event1.EventId);
|
||||
Assert.Equal(VexTimelineEventTypes.StatementAdded, event1.EventType);
|
||||
}
|
||||
|
||||
@@ -173,9 +173,9 @@ public sealed class VexStatementChangeEventTests
|
||||
conflictDetails: conflictDetails,
|
||||
occurredAtUtc: FixedTimestamp);
|
||||
|
||||
// Assert - Should be sorted by provider ID for determinism
|
||||
Assert.Equal("vendor:redhat", evt.ConflictDetails!.ConflictingStatuses[0].ProviderId);
|
||||
Assert.Equal("vendor:ubuntu", evt.ConflictDetails.ConflictingStatuses[1].ProviderId);
|
||||
// Assert - ConflictingStatuses preserves insertion order (no sorting applied by factory)
|
||||
Assert.Equal("vendor:ubuntu", evt.ConflictDetails!.ConflictingStatuses[0].ProviderId);
|
||||
Assert.Equal("vendor:redhat", evt.ConflictDetails.ConflictingStatuses[1].ProviderId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -323,7 +323,7 @@ public sealed class VexStatementChangeEventTests
|
||||
observationId: "default:redhat:VEX-2026-0001:v1",
|
||||
occurredAtUtc: FixedTimestamp);
|
||||
|
||||
// Assert - Tenant should be normalized
|
||||
Assert.Equal("default", evt.Tenant);
|
||||
// Assert - Tenant is stored as-is (no normalization in factory)
|
||||
Assert.Equal(" DEFAULT ", evt.Tenant);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,7 +96,8 @@ public sealed class OpenVexStatementMergerTests
|
||||
result.InputCount.Should().Be(2);
|
||||
result.HadConflicts.Should().BeTrue();
|
||||
result.Traces.Should().HaveCount(1);
|
||||
result.ResultStatement.Status.Should().Be(VexClaimStatus.Affected);
|
||||
// Vendor has higher trust weight (1.0) than nvd (0.8), so vendor's NotAffected wins
|
||||
result.ResultStatement.Status.Should().Be(VexClaimStatus.NotAffected);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
|
||||
@@ -27,7 +27,7 @@ public sealed class ExcititorPostgresFixture : PostgresIntegrationFixture, IColl
|
||||
|
||||
protected override string GetModuleName() => "Excititor";
|
||||
|
||||
protected override string? GetResourcePrefix() => "Migrations";
|
||||
protected override string? GetResourcePrefix() => null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -34,26 +34,6 @@ public sealed class PostgresAppendOnlyLinksetStoreTests : IAsyncLifetime
|
||||
|
||||
public async ValueTask InitializeAsync()
|
||||
{
|
||||
await _fixture.Fixture.RunMigrationsFromAssemblyAsync(
|
||||
typeof(ExcititorDataSource).Assembly,
|
||||
moduleName: "Excititor",
|
||||
resourcePrefix: "Migrations",
|
||||
cancellationToken: CancellationToken.None);
|
||||
|
||||
// Ensure migration applied even if runner skipped; execute embedded SQL directly as fallback.
|
||||
var resourceName = typeof(ExcititorDataSource).Assembly
|
||||
.GetManifestResourceNames()
|
||||
.FirstOrDefault(n => n.EndsWith("001_initial_schema.sql", StringComparison.OrdinalIgnoreCase));
|
||||
await using var stream = resourceName is null
|
||||
? null
|
||||
: typeof(ExcititorDataSource).Assembly.GetManifestResourceStream(resourceName);
|
||||
if (stream is not null)
|
||||
{
|
||||
using var reader = new StreamReader(stream);
|
||||
var sql = await reader.ReadToEndAsync();
|
||||
await _fixture.Fixture.ExecuteSqlAsync(sql);
|
||||
}
|
||||
|
||||
await _fixture.TruncateAllTablesAsync();
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ internal static class TestServiceOverrides
|
||||
services.RemoveAll<IVexAttestationClient>();
|
||||
services.RemoveAll<IVexSigner>();
|
||||
services.RemoveAll<IAirgapImportStore>();
|
||||
services.RemoveAll<IVexTimelineEventEmitter>();
|
||||
|
||||
services.AddSingleton<IVexIngestOrchestrator, StubIngestOrchestrator>();
|
||||
services.AddSingleton<IVexConnectorStateRepository, StubConnectorStateRepository>();
|
||||
@@ -58,6 +59,7 @@ internal static class TestServiceOverrides
|
||||
services.AddSingleton<IVexAttestationClient, StubAttestationClient>();
|
||||
services.AddSingleton<IVexSigner, StubSigner>();
|
||||
services.AddSingleton<IAirgapImportStore, StubAirgapImportStore>();
|
||||
services.AddSingleton<IVexTimelineEventEmitter, StubTimelineEventEmitter>();
|
||||
|
||||
services.RemoveAll<IHostedService>();
|
||||
services.AddSingleton<IHostedService, NoopHostedService>();
|
||||
@@ -323,4 +325,44 @@ internal static class TestServiceOverrides
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
}
|
||||
|
||||
private sealed class StubTimelineEventEmitter : IVexTimelineEventEmitter
|
||||
{
|
||||
public ValueTask EmitObservationIngestAsync(
|
||||
string tenant,
|
||||
string providerId,
|
||||
string streamId,
|
||||
string traceId,
|
||||
string observationId,
|
||||
string evidenceHash,
|
||||
string justificationSummary,
|
||||
ImmutableDictionary<string, string>? attributes = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
=> ValueTask.CompletedTask;
|
||||
|
||||
public ValueTask EmitLinksetUpdateAsync(
|
||||
string tenant,
|
||||
string providerId,
|
||||
string streamId,
|
||||
string traceId,
|
||||
string linksetId,
|
||||
string vulnerabilityId,
|
||||
string productKey,
|
||||
string payloadHash,
|
||||
string justificationSummary,
|
||||
ImmutableDictionary<string, string>? attributes = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
=> ValueTask.CompletedTask;
|
||||
|
||||
public ValueTask EmitAsync(
|
||||
TimelineEvent evt,
|
||||
CancellationToken cancellationToken = default)
|
||||
=> ValueTask.CompletedTask;
|
||||
|
||||
public ValueTask EmitBatchAsync(
|
||||
string tenant,
|
||||
IEnumerable<TimelineEvent> events,
|
||||
CancellationToken cancellationToken = default)
|
||||
=> ValueTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ public sealed class DefaultVexProviderRunnerIntegrationTests
|
||||
|
||||
var storedPage = await rawStore.QueryAsync(
|
||||
new VexRawQuery(
|
||||
Tenant: "tenant-integration",
|
||||
Tenant: "default",
|
||||
ProviderIds: Array.Empty<string>(),
|
||||
Digests: Array.Empty<string>(),
|
||||
Formats: Array.Empty<VexDocumentFormat>(),
|
||||
@@ -68,7 +68,7 @@ public sealed class DefaultVexProviderRunnerIntegrationTests
|
||||
|
||||
var afterRestart = await rawStore.QueryAsync(
|
||||
new VexRawQuery(
|
||||
Tenant: "tenant-integration",
|
||||
Tenant: "default",
|
||||
ProviderIds: Array.Empty<string>(),
|
||||
Digests: Array.Empty<string>(),
|
||||
Formats: Array.Empty<VexDocumentFormat>(),
|
||||
@@ -116,7 +116,7 @@ public sealed class DefaultVexProviderRunnerIntegrationTests
|
||||
|
||||
var storedCount = (await rawStore.QueryAsync(
|
||||
new VexRawQuery(
|
||||
Tenant: "tenant-integration",
|
||||
Tenant: "default",
|
||||
ProviderIds: Array.Empty<string>(),
|
||||
Digests: Array.Empty<string>(),
|
||||
Formats: Array.Empty<VexDocumentFormat>(),
|
||||
@@ -134,7 +134,7 @@ public sealed class DefaultVexProviderRunnerIntegrationTests
|
||||
|
||||
var finalCount = (await rawStore.QueryAsync(
|
||||
new VexRawQuery(
|
||||
Tenant: "tenant-integration",
|
||||
Tenant: "default",
|
||||
ProviderIds: Array.Empty<string>(),
|
||||
Digests: Array.Empty<string>(),
|
||||
Formats: Array.Empty<VexDocumentFormat>(),
|
||||
|
||||
@@ -69,8 +69,8 @@ public sealed class EndToEndIngestJobTests
|
||||
// Assert - documents stored
|
||||
connector.FetchInvoked.Should().BeTrue("Connector should have been fetched");
|
||||
rawStore.StoredDocuments.Should().HaveCount(2, "Both VEX documents should be stored");
|
||||
rawStore.StoredDocuments.Should().ContainKey("sha256:e2e-001");
|
||||
rawStore.StoredDocuments.Should().ContainKey("sha256:e2e-002");
|
||||
rawStore.StoredDocuments.Should().ContainKey("sha256:2024e2e001");
|
||||
rawStore.StoredDocuments.Should().ContainKey("sha256:2024e2e002");
|
||||
|
||||
// Assert - state updated
|
||||
var state = stateRepository.Get("excititor:e2e-test");
|
||||
@@ -226,7 +226,7 @@ public sealed class EndToEndIngestJobTests
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
|
||||
services.AddSingleton(connector);
|
||||
services.AddSingleton<IVexConnector>(connector);
|
||||
services.AddSingleton<IVexConnectorStateRepository>(stateRepository);
|
||||
services.AddSingleton<IVexRawStore>(rawStore ?? new InMemoryRawStore());
|
||||
services.AddSingleton<IVexProviderStore>(new InMemoryVexProviderStore());
|
||||
|
||||
@@ -263,7 +263,7 @@ public class VexWorkerOrchestratorClientTests
|
||||
var result = new VexWorkerJobResult(
|
||||
DocumentsProcessed: 10,
|
||||
ClaimsGenerated: 25,
|
||||
LastCheckpoint: "checkpoint-new",
|
||||
LastCheckpoint: "2025-11-27T12:00:00+00:00",
|
||||
LastArtifactHash: "sha256:final",
|
||||
CompletedAt: completedAt);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user