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:
@@ -10,39 +10,39 @@ using System.Threading.Tasks;
|
||||
using StellaOps.Concelier.Exporter.Json;
|
||||
using StellaOps.Concelier.Models;
|
||||
using StellaOps.Cryptography;
|
||||
|
||||
namespace StellaOps.Concelier.Exporter.Json.Tests;
|
||||
|
||||
public sealed class JsonExportSnapshotBuilderTests : IDisposable
|
||||
{
|
||||
private readonly string _root;
|
||||
|
||||
public JsonExportSnapshotBuilderTests()
|
||||
{
|
||||
_root = Directory.CreateTempSubdirectory("concelier-json-export-tests").FullName;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WritesDeterministicTree()
|
||||
{
|
||||
var options = new JsonExportOptions { OutputRoot = _root };
|
||||
var builder = new JsonExportSnapshotBuilder(options, new VulnListJsonExportPathResolver());
|
||||
var exportedAt = DateTimeOffset.Parse("2024-07-15T12:00:00Z", CultureInfo.InvariantCulture);
|
||||
|
||||
var advisories = new[]
|
||||
{
|
||||
CreateAdvisory(
|
||||
advisoryKey: "CVE-2024-9999",
|
||||
aliases: new[] { "GHSA-zzzz-yyyy-xxxx", "CVE-2024-9999" },
|
||||
title: "Deterministic Snapshot",
|
||||
severity: "critical"),
|
||||
CreateAdvisory(
|
||||
advisoryKey: "VENDOR-2024-42",
|
||||
aliases: new[] { "ALIAS-1", "ALIAS-2" },
|
||||
title: "Vendor Advisory",
|
||||
severity: "medium"),
|
||||
};
|
||||
|
||||
|
||||
namespace StellaOps.Concelier.Exporter.Json.Tests;
|
||||
|
||||
public sealed class JsonExportSnapshotBuilderTests : IDisposable
|
||||
{
|
||||
private readonly string _root;
|
||||
|
||||
public JsonExportSnapshotBuilderTests()
|
||||
{
|
||||
_root = Directory.CreateTempSubdirectory("concelier-json-export-tests").FullName;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WritesDeterministicTree()
|
||||
{
|
||||
var options = new JsonExportOptions { OutputRoot = _root };
|
||||
var builder = new JsonExportSnapshotBuilder(options, new VulnListJsonExportPathResolver());
|
||||
var exportedAt = DateTimeOffset.Parse("2024-07-15T12:00:00Z", CultureInfo.InvariantCulture);
|
||||
|
||||
var advisories = new[]
|
||||
{
|
||||
CreateAdvisory(
|
||||
advisoryKey: "CVE-2024-9999",
|
||||
aliases: new[] { "GHSA-zzzz-yyyy-xxxx", "CVE-2024-9999" },
|
||||
title: "Deterministic Snapshot",
|
||||
severity: "critical"),
|
||||
CreateAdvisory(
|
||||
advisoryKey: "VENDOR-2024-42",
|
||||
aliases: new[] { "ALIAS-1", "ALIAS-2" },
|
||||
title: "Vendor Advisory",
|
||||
severity: "medium"),
|
||||
};
|
||||
|
||||
var result = await builder.WriteAsync(advisories, exportedAt, cancellationToken: CancellationToken.None);
|
||||
|
||||
Assert.Equal(advisories.Length, result.AdvisoryCount);
|
||||
@@ -51,49 +51,49 @@ public sealed class JsonExportSnapshotBuilderTests : IDisposable
|
||||
advisories.Select(a => a.AdvisoryKey).OrderBy(key => key, StringComparer.Ordinal),
|
||||
result.Advisories.Select(a => a.AdvisoryKey).OrderBy(key => key, StringComparer.Ordinal));
|
||||
Assert.Equal(exportedAt, result.ExportedAt);
|
||||
|
||||
var expectedFiles = result.FilePaths.OrderBy(x => x, StringComparer.Ordinal).ToArray();
|
||||
Assert.Contains("nvd/2024/CVE-2024-9999.json", expectedFiles);
|
||||
Assert.Contains("misc/VENDOR-2024-42.json", expectedFiles);
|
||||
|
||||
var cvePath = ResolvePath(result.ExportDirectory, "nvd/2024/CVE-2024-9999.json");
|
||||
Assert.True(File.Exists(cvePath));
|
||||
var actualJson = await File.ReadAllTextAsync(cvePath, CancellationToken.None);
|
||||
Assert.Equal(SnapshotSerializer.ToSnapshot(advisories[0]), actualJson);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ProducesIdenticalBytesAcrossRuns()
|
||||
{
|
||||
var options = new JsonExportOptions { OutputRoot = _root };
|
||||
var builder = new JsonExportSnapshotBuilder(options, new VulnListJsonExportPathResolver());
|
||||
var exportedAt = DateTimeOffset.Parse("2024-05-01T00:00:00Z", CultureInfo.InvariantCulture);
|
||||
var advisories = new[]
|
||||
{
|
||||
CreateAdvisory("CVE-2024-1000", new[] { "CVE-2024-1000", "GHSA-aaaa-bbbb-cccc" }, "Snapshot Stable", "high"),
|
||||
};
|
||||
|
||||
var first = await builder.WriteAsync(advisories, exportedAt, exportName: "20240501T000000Z", CancellationToken.None);
|
||||
var firstDigest = ComputeDigest(first);
|
||||
|
||||
var second = await builder.WriteAsync(advisories, exportedAt, exportName: "20240501T000000Z", CancellationToken.None);
|
||||
var secondDigest = ComputeDigest(second);
|
||||
|
||||
Assert.Equal(Convert.ToHexString(firstDigest), Convert.ToHexString(secondDigest));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
var expectedFiles = result.FilePaths.OrderBy(x => x, StringComparer.Ordinal).ToArray();
|
||||
Assert.Contains("nvd/2024/CVE-2024-9999.json", expectedFiles);
|
||||
Assert.Contains("misc/VENDOR-2024-42.json", expectedFiles);
|
||||
|
||||
var cvePath = ResolvePath(result.ExportDirectory, "nvd/2024/CVE-2024-9999.json");
|
||||
Assert.True(File.Exists(cvePath));
|
||||
var actualJson = await File.ReadAllTextAsync(cvePath, CancellationToken.None);
|
||||
Assert.Equal(SnapshotSerializer.ToSnapshot(advisories[0]), actualJson);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ProducesIdenticalBytesAcrossRuns()
|
||||
{
|
||||
var options = new JsonExportOptions { OutputRoot = _root };
|
||||
var builder = new JsonExportSnapshotBuilder(options, new VulnListJsonExportPathResolver());
|
||||
var exportedAt = DateTimeOffset.Parse("2024-05-01T00:00:00Z", CultureInfo.InvariantCulture);
|
||||
var advisories = new[]
|
||||
{
|
||||
CreateAdvisory("CVE-2024-1000", new[] { "CVE-2024-1000", "GHSA-aaaa-bbbb-cccc" }, "Snapshot Stable", "high"),
|
||||
};
|
||||
|
||||
var first = await builder.WriteAsync(advisories, exportedAt, exportName: "20240501T000000Z", CancellationToken.None);
|
||||
var firstDigest = ComputeDigest(first);
|
||||
|
||||
var second = await builder.WriteAsync(advisories, exportedAt, exportName: "20240501T000000Z", CancellationToken.None);
|
||||
var secondDigest = ComputeDigest(second);
|
||||
|
||||
Assert.Equal(Convert.ToHexString(firstDigest), Convert.ToHexString(secondDigest));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WriteAsync_NormalizesInputOrdering()
|
||||
{
|
||||
var options = new JsonExportOptions { OutputRoot = _root };
|
||||
var builder = new JsonExportSnapshotBuilder(options, new VulnListJsonExportPathResolver());
|
||||
var exportedAt = DateTimeOffset.Parse("2024-06-01T00:00:00Z", CultureInfo.InvariantCulture);
|
||||
|
||||
var advisoryA = CreateAdvisory("CVE-2024-1000", new[] { "CVE-2024-1000" }, "Alpha", "high");
|
||||
var advisoryB = CreateAdvisory("VENDOR-0001", new[] { "VENDOR-0001" }, "Vendor Advisory", "medium");
|
||||
|
||||
var result = await builder.WriteAsync(new[] { advisoryB, advisoryA }, exportedAt, cancellationToken: CancellationToken.None);
|
||||
|
||||
|
||||
var advisoryA = CreateAdvisory("CVE-2024-1000", new[] { "CVE-2024-1000" }, "Alpha", "high");
|
||||
var advisoryB = CreateAdvisory("VENDOR-0001", new[] { "VENDOR-0001" }, "Vendor Advisory", "medium");
|
||||
|
||||
var result = await builder.WriteAsync(new[] { advisoryB, advisoryA }, exportedAt, cancellationToken: CancellationToken.None);
|
||||
|
||||
var expectedOrder = result.FilePaths.OrderBy(path => path, StringComparer.Ordinal).ToArray();
|
||||
Assert.Equal(expectedOrder, result.FilePaths.ToArray());
|
||||
}
|
||||
@@ -129,54 +129,54 @@ public sealed class JsonExportSnapshotBuilderTests : IDisposable
|
||||
{
|
||||
var options = new JsonExportOptions { OutputRoot = _root };
|
||||
var builder = new JsonExportSnapshotBuilder(options, new VulnListJsonExportPathResolver());
|
||||
var exportedAt = DateTimeOffset.Parse("2024-08-01T00:00:00Z", CultureInfo.InvariantCulture);
|
||||
|
||||
var advisories = new[]
|
||||
{
|
||||
CreateAdvisory("CVE-2024-2000", new[] { "CVE-2024-2000" }, "Streaming One", "medium"),
|
||||
CreateAdvisory("CVE-2024-2001", new[] { "CVE-2024-2001" }, "Streaming Two", "low"),
|
||||
};
|
||||
|
||||
var sequence = new SingleEnumerationAsyncSequence(advisories);
|
||||
var exportedAt = DateTimeOffset.Parse("2024-08-01T00:00:00Z", CultureInfo.InvariantCulture);
|
||||
|
||||
var advisories = new[]
|
||||
{
|
||||
CreateAdvisory("CVE-2024-2000", new[] { "CVE-2024-2000" }, "Streaming One", "medium"),
|
||||
CreateAdvisory("CVE-2024-2001", new[] { "CVE-2024-2001" }, "Streaming Two", "low"),
|
||||
};
|
||||
|
||||
var sequence = new SingleEnumerationAsyncSequence(advisories);
|
||||
var result = await builder.WriteAsync(sequence, exportedAt, cancellationToken: CancellationToken.None);
|
||||
|
||||
Assert.Equal(advisories.Length, result.AdvisoryCount);
|
||||
Assert.Equal(advisories.Length, result.Advisories.Length);
|
||||
}
|
||||
|
||||
private static Advisory CreateAdvisory(string advisoryKey, string[] aliases, string title, string severity)
|
||||
{
|
||||
return new Advisory(
|
||||
advisoryKey: advisoryKey,
|
||||
title: title,
|
||||
summary: null,
|
||||
language: "EN",
|
||||
published: DateTimeOffset.Parse("2024-01-01T00:00:00Z", CultureInfo.InvariantCulture),
|
||||
modified: DateTimeOffset.Parse("2024-01-02T00:00:00Z", CultureInfo.InvariantCulture),
|
||||
severity: severity,
|
||||
exploitKnown: false,
|
||||
aliases: aliases,
|
||||
references: new[]
|
||||
{
|
||||
new AdvisoryReference("https://example.com/advisory", "advisory", null, null, AdvisoryProvenance.Empty),
|
||||
},
|
||||
affectedPackages: new[]
|
||||
{
|
||||
new AffectedPackage(
|
||||
AffectedPackageTypes.SemVer,
|
||||
"sample/package",
|
||||
platform: null,
|
||||
versionRanges: Array.Empty<AffectedVersionRange>(),
|
||||
statuses: Array.Empty<AffectedPackageStatus>(),
|
||||
provenance: Array.Empty<AdvisoryProvenance>()),
|
||||
},
|
||||
cvssMetrics: Array.Empty<CvssMetric>(),
|
||||
provenance: new[]
|
||||
{
|
||||
new AdvisoryProvenance("concelier", "normalized", "canonical", DateTimeOffset.Parse("2024-01-02T00:00:00Z", CultureInfo.InvariantCulture)),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private static Advisory CreateAdvisory(string advisoryKey, string[] aliases, string title, string severity)
|
||||
{
|
||||
return new Advisory(
|
||||
advisoryKey: advisoryKey,
|
||||
title: title,
|
||||
summary: null,
|
||||
language: "EN",
|
||||
published: DateTimeOffset.Parse("2024-01-01T00:00:00Z", CultureInfo.InvariantCulture),
|
||||
modified: DateTimeOffset.Parse("2024-01-02T00:00:00Z", CultureInfo.InvariantCulture),
|
||||
severity: severity,
|
||||
exploitKnown: false,
|
||||
aliases: aliases,
|
||||
references: new[]
|
||||
{
|
||||
new AdvisoryReference("https://example.com/advisory", "advisory", null, null, AdvisoryProvenance.Empty),
|
||||
},
|
||||
affectedPackages: new[]
|
||||
{
|
||||
new AffectedPackage(
|
||||
AffectedPackageTypes.SemVer,
|
||||
"sample/package",
|
||||
platform: null,
|
||||
versionRanges: Array.Empty<AffectedVersionRange>(),
|
||||
statuses: Array.Empty<AffectedPackageStatus>(),
|
||||
provenance: Array.Empty<AdvisoryProvenance>()),
|
||||
},
|
||||
cvssMetrics: Array.Empty<CvssMetric>(),
|
||||
provenance: new[]
|
||||
{
|
||||
new AdvisoryProvenance("concelier", "normalized", "canonical", DateTimeOffset.Parse("2024-01-02T00:00:00Z", CultureInfo.InvariantCulture)),
|
||||
});
|
||||
}
|
||||
|
||||
private static byte[] ComputeDigest(JsonExportResult result)
|
||||
{
|
||||
var hash = CryptoHashFactory.CreateDefault();
|
||||
@@ -191,56 +191,56 @@ public sealed class JsonExportSnapshotBuilderTests : IDisposable
|
||||
|
||||
return hash.ComputeHash(buffer.WrittenSpan, HashAlgorithms.Sha256);
|
||||
}
|
||||
|
||||
private static string ResolvePath(string root, string relative)
|
||||
{
|
||||
var segments = relative.Split('/', StringSplitOptions.RemoveEmptyEntries);
|
||||
return Path.Combine(new[] { root }.Concat(segments).ToArray());
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Directory.Exists(_root))
|
||||
{
|
||||
Directory.Delete(_root, recursive: true);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// best effort cleanup
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class SingleEnumerationAsyncSequence : IAsyncEnumerable<Advisory>
|
||||
{
|
||||
private readonly IReadOnlyList<Advisory> _advisories;
|
||||
private int _enumerated;
|
||||
|
||||
public SingleEnumerationAsyncSequence(IReadOnlyList<Advisory> advisories)
|
||||
{
|
||||
_advisories = advisories ?? throw new ArgumentNullException(nameof(advisories));
|
||||
}
|
||||
|
||||
public IAsyncEnumerator<Advisory> GetAsyncEnumerator(CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (Interlocked.Exchange(ref _enumerated, 1) == 1)
|
||||
{
|
||||
throw new InvalidOperationException("Sequence was enumerated more than once.");
|
||||
}
|
||||
|
||||
return Enumerate(cancellationToken);
|
||||
|
||||
async IAsyncEnumerator<Advisory> Enumerate([EnumeratorCancellation] CancellationToken ct)
|
||||
{
|
||||
foreach (var advisory in _advisories)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
yield return advisory;
|
||||
await Task.Yield();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string ResolvePath(string root, string relative)
|
||||
{
|
||||
var segments = relative.Split('/', StringSplitOptions.RemoveEmptyEntries);
|
||||
return Path.Combine(new[] { root }.Concat(segments).ToArray());
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Directory.Exists(_root))
|
||||
{
|
||||
Directory.Delete(_root, recursive: true);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// best effort cleanup
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class SingleEnumerationAsyncSequence : IAsyncEnumerable<Advisory>
|
||||
{
|
||||
private readonly IReadOnlyList<Advisory> _advisories;
|
||||
private int _enumerated;
|
||||
|
||||
public SingleEnumerationAsyncSequence(IReadOnlyList<Advisory> advisories)
|
||||
{
|
||||
_advisories = advisories ?? throw new ArgumentNullException(nameof(advisories));
|
||||
}
|
||||
|
||||
public IAsyncEnumerator<Advisory> GetAsyncEnumerator(CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (Interlocked.Exchange(ref _enumerated, 1) == 1)
|
||||
{
|
||||
throw new InvalidOperationException("Sequence was enumerated more than once.");
|
||||
}
|
||||
|
||||
return Enumerate(cancellationToken);
|
||||
|
||||
async IAsyncEnumerator<Advisory> Enumerate([EnumeratorCancellation] CancellationToken ct)
|
||||
{
|
||||
foreach (var advisory in _advisories)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
yield return advisory;
|
||||
await Task.Yield();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ using StellaOps.Concelier.Storage.Exporting;
|
||||
using StellaOps.Concelier.Models;
|
||||
using StellaOps.Cryptography;
|
||||
using StellaOps.Cryptography.DependencyInjection;
|
||||
using StellaOps.Provenance.Mongo;
|
||||
using StellaOps.Provenance;
|
||||
|
||||
namespace StellaOps.Concelier.Exporter.Json.Tests;
|
||||
|
||||
|
||||
@@ -1,182 +1,182 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using StellaOps.Concelier.Exporter.Json;
|
||||
using StellaOps.Concelier.Models;
|
||||
|
||||
namespace StellaOps.Concelier.Exporter.Json.Tests;
|
||||
|
||||
public sealed class JsonExporterParitySmokeTests : IDisposable
|
||||
{
|
||||
private readonly string _root;
|
||||
|
||||
public JsonExporterParitySmokeTests()
|
||||
{
|
||||
_root = Directory.CreateTempSubdirectory("concelier-json-parity-tests").FullName;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExportProducesVulnListCompatiblePaths()
|
||||
{
|
||||
var options = new JsonExportOptions { OutputRoot = _root };
|
||||
var builder = new JsonExportSnapshotBuilder(options, new VulnListJsonExportPathResolver());
|
||||
var exportedAt = DateTimeOffset.Parse("2024-09-01T12:00:00Z", CultureInfo.InvariantCulture);
|
||||
|
||||
var advisories = CreateSampleAdvisories();
|
||||
var result = await builder.WriteAsync(advisories, exportedAt, exportName: "parity-test", CancellationToken.None);
|
||||
|
||||
var expected = new[]
|
||||
{
|
||||
"amazon/2/ALAS2-2024-1234.json",
|
||||
"debian/DLA-2024-1234.json",
|
||||
"ghsa/go/github.com%2Facme%2Fsample/GHSA-AAAA-BBBB-CCCC.json",
|
||||
"nvd/2023/CVE-2023-27524.json",
|
||||
"oracle/linux/ELSA-2024-12345.json",
|
||||
"redhat/oval/RHSA-2024_0252.json",
|
||||
"ubuntu/USN-6620-1.json",
|
||||
"wolfi/WOLFI-2024-0001.json",
|
||||
};
|
||||
|
||||
Assert.Equal(expected, result.FilePaths.ToArray());
|
||||
|
||||
foreach (var path in expected)
|
||||
{
|
||||
var fullPath = ResolvePath(result.ExportDirectory, path);
|
||||
Assert.True(File.Exists(fullPath), $"Expected export file '{path}' to be present");
|
||||
}
|
||||
}
|
||||
|
||||
private static IReadOnlyList<Advisory> CreateSampleAdvisories()
|
||||
{
|
||||
var published = DateTimeOffset.Parse("2024-01-01T00:00:00Z", CultureInfo.InvariantCulture);
|
||||
var modified = DateTimeOffset.Parse("2024-02-01T00:00:00Z", CultureInfo.InvariantCulture);
|
||||
|
||||
return new[]
|
||||
{
|
||||
CreateAdvisory(
|
||||
"CVE-2023-27524",
|
||||
"Apache Superset Improper Authentication",
|
||||
new[] { "CVE-2023-27524" },
|
||||
null,
|
||||
"nvd",
|
||||
published,
|
||||
modified),
|
||||
CreateAdvisory(
|
||||
"GHSA-aaaa-bbbb-cccc",
|
||||
"Sample GHSA",
|
||||
new[] { "CVE-2024-2000" },
|
||||
new[]
|
||||
{
|
||||
new AffectedPackage(
|
||||
AffectedPackageTypes.SemVer,
|
||||
"pkg:go/github.com/acme/sample@1.0.0",
|
||||
provenance: new[] { new AdvisoryProvenance("ghsa", "map", "", published) })
|
||||
},
|
||||
"ghsa",
|
||||
published,
|
||||
modified),
|
||||
CreateAdvisory(
|
||||
"USN-6620-1",
|
||||
"Ubuntu Security Notice",
|
||||
null,
|
||||
null,
|
||||
"ubuntu",
|
||||
published,
|
||||
modified),
|
||||
CreateAdvisory(
|
||||
"DLA-2024-1234",
|
||||
"Debian LTS Advisory",
|
||||
null,
|
||||
null,
|
||||
"debian",
|
||||
published,
|
||||
modified),
|
||||
CreateAdvisory(
|
||||
"RHSA-2024:0252",
|
||||
"Red Hat Security Advisory",
|
||||
null,
|
||||
null,
|
||||
"redhat",
|
||||
published,
|
||||
modified),
|
||||
CreateAdvisory(
|
||||
"ALAS2-2024-1234",
|
||||
"Amazon Linux Advisory",
|
||||
null,
|
||||
null,
|
||||
"amazon",
|
||||
published,
|
||||
modified),
|
||||
CreateAdvisory(
|
||||
"ELSA-2024-12345",
|
||||
"Oracle Linux Advisory",
|
||||
null,
|
||||
null,
|
||||
"oracle",
|
||||
published,
|
||||
modified),
|
||||
CreateAdvisory(
|
||||
"WOLFI-2024-0001",
|
||||
"Wolfi Advisory",
|
||||
null,
|
||||
null,
|
||||
"wolfi",
|
||||
published,
|
||||
modified),
|
||||
};
|
||||
}
|
||||
|
||||
private static Advisory CreateAdvisory(
|
||||
string advisoryKey,
|
||||
string title,
|
||||
IEnumerable<string>? aliases,
|
||||
IEnumerable<AffectedPackage>? packages,
|
||||
string? provenanceSource,
|
||||
DateTimeOffset? published,
|
||||
DateTimeOffset? modified)
|
||||
{
|
||||
var provenance = provenanceSource is null
|
||||
? Array.Empty<AdvisoryProvenance>()
|
||||
: new[] { new AdvisoryProvenance(provenanceSource, "normalize", "", modified ?? DateTimeOffset.UtcNow) };
|
||||
|
||||
return new Advisory(
|
||||
advisoryKey,
|
||||
title,
|
||||
summary: null,
|
||||
language: "en",
|
||||
published,
|
||||
modified,
|
||||
severity: "medium",
|
||||
exploitKnown: false,
|
||||
aliases: aliases ?? Array.Empty<string>(),
|
||||
references: Array.Empty<AdvisoryReference>(),
|
||||
affectedPackages: packages ?? Array.Empty<AffectedPackage>(),
|
||||
cvssMetrics: Array.Empty<CvssMetric>(),
|
||||
provenance: provenance);
|
||||
}
|
||||
|
||||
private static string ResolvePath(string root, string relative)
|
||||
{
|
||||
var segments = relative.Split('/', StringSplitOptions.RemoveEmptyEntries);
|
||||
return Path.Combine(new[] { root }.Concat(segments).ToArray());
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Directory.Exists(_root))
|
||||
{
|
||||
Directory.Delete(_root, recursive: true);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// best effort cleanup
|
||||
}
|
||||
}
|
||||
}
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using StellaOps.Concelier.Exporter.Json;
|
||||
using StellaOps.Concelier.Models;
|
||||
|
||||
namespace StellaOps.Concelier.Exporter.Json.Tests;
|
||||
|
||||
public sealed class JsonExporterParitySmokeTests : IDisposable
|
||||
{
|
||||
private readonly string _root;
|
||||
|
||||
public JsonExporterParitySmokeTests()
|
||||
{
|
||||
_root = Directory.CreateTempSubdirectory("concelier-json-parity-tests").FullName;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExportProducesVulnListCompatiblePaths()
|
||||
{
|
||||
var options = new JsonExportOptions { OutputRoot = _root };
|
||||
var builder = new JsonExportSnapshotBuilder(options, new VulnListJsonExportPathResolver());
|
||||
var exportedAt = DateTimeOffset.Parse("2024-09-01T12:00:00Z", CultureInfo.InvariantCulture);
|
||||
|
||||
var advisories = CreateSampleAdvisories();
|
||||
var result = await builder.WriteAsync(advisories, exportedAt, exportName: "parity-test", CancellationToken.None);
|
||||
|
||||
var expected = new[]
|
||||
{
|
||||
"amazon/2/ALAS2-2024-1234.json",
|
||||
"debian/DLA-2024-1234.json",
|
||||
"ghsa/go/github.com%2Facme%2Fsample/GHSA-AAAA-BBBB-CCCC.json",
|
||||
"nvd/2023/CVE-2023-27524.json",
|
||||
"oracle/linux/ELSA-2024-12345.json",
|
||||
"redhat/oval/RHSA-2024_0252.json",
|
||||
"ubuntu/USN-6620-1.json",
|
||||
"wolfi/WOLFI-2024-0001.json",
|
||||
};
|
||||
|
||||
Assert.Equal(expected, result.FilePaths.ToArray());
|
||||
|
||||
foreach (var path in expected)
|
||||
{
|
||||
var fullPath = ResolvePath(result.ExportDirectory, path);
|
||||
Assert.True(File.Exists(fullPath), $"Expected export file '{path}' to be present");
|
||||
}
|
||||
}
|
||||
|
||||
private static IReadOnlyList<Advisory> CreateSampleAdvisories()
|
||||
{
|
||||
var published = DateTimeOffset.Parse("2024-01-01T00:00:00Z", CultureInfo.InvariantCulture);
|
||||
var modified = DateTimeOffset.Parse("2024-02-01T00:00:00Z", CultureInfo.InvariantCulture);
|
||||
|
||||
return new[]
|
||||
{
|
||||
CreateAdvisory(
|
||||
"CVE-2023-27524",
|
||||
"Apache Superset Improper Authentication",
|
||||
new[] { "CVE-2023-27524" },
|
||||
null,
|
||||
"nvd",
|
||||
published,
|
||||
modified),
|
||||
CreateAdvisory(
|
||||
"GHSA-aaaa-bbbb-cccc",
|
||||
"Sample GHSA",
|
||||
new[] { "CVE-2024-2000" },
|
||||
new[]
|
||||
{
|
||||
new AffectedPackage(
|
||||
AffectedPackageTypes.SemVer,
|
||||
"pkg:go/github.com/acme/sample@1.0.0",
|
||||
provenance: new[] { new AdvisoryProvenance("ghsa", "map", "", published) })
|
||||
},
|
||||
"ghsa",
|
||||
published,
|
||||
modified),
|
||||
CreateAdvisory(
|
||||
"USN-6620-1",
|
||||
"Ubuntu Security Notice",
|
||||
null,
|
||||
null,
|
||||
"ubuntu",
|
||||
published,
|
||||
modified),
|
||||
CreateAdvisory(
|
||||
"DLA-2024-1234",
|
||||
"Debian LTS Advisory",
|
||||
null,
|
||||
null,
|
||||
"debian",
|
||||
published,
|
||||
modified),
|
||||
CreateAdvisory(
|
||||
"RHSA-2024:0252",
|
||||
"Red Hat Security Advisory",
|
||||
null,
|
||||
null,
|
||||
"redhat",
|
||||
published,
|
||||
modified),
|
||||
CreateAdvisory(
|
||||
"ALAS2-2024-1234",
|
||||
"Amazon Linux Advisory",
|
||||
null,
|
||||
null,
|
||||
"amazon",
|
||||
published,
|
||||
modified),
|
||||
CreateAdvisory(
|
||||
"ELSA-2024-12345",
|
||||
"Oracle Linux Advisory",
|
||||
null,
|
||||
null,
|
||||
"oracle",
|
||||
published,
|
||||
modified),
|
||||
CreateAdvisory(
|
||||
"WOLFI-2024-0001",
|
||||
"Wolfi Advisory",
|
||||
null,
|
||||
null,
|
||||
"wolfi",
|
||||
published,
|
||||
modified),
|
||||
};
|
||||
}
|
||||
|
||||
private static Advisory CreateAdvisory(
|
||||
string advisoryKey,
|
||||
string title,
|
||||
IEnumerable<string>? aliases,
|
||||
IEnumerable<AffectedPackage>? packages,
|
||||
string? provenanceSource,
|
||||
DateTimeOffset? published,
|
||||
DateTimeOffset? modified)
|
||||
{
|
||||
var provenance = provenanceSource is null
|
||||
? Array.Empty<AdvisoryProvenance>()
|
||||
: new[] { new AdvisoryProvenance(provenanceSource, "normalize", "", modified ?? DateTimeOffset.UtcNow) };
|
||||
|
||||
return new Advisory(
|
||||
advisoryKey,
|
||||
title,
|
||||
summary: null,
|
||||
language: "en",
|
||||
published,
|
||||
modified,
|
||||
severity: "medium",
|
||||
exploitKnown: false,
|
||||
aliases: aliases ?? Array.Empty<string>(),
|
||||
references: Array.Empty<AdvisoryReference>(),
|
||||
affectedPackages: packages ?? Array.Empty<AffectedPackage>(),
|
||||
cvssMetrics: Array.Empty<CvssMetric>(),
|
||||
provenance: provenance);
|
||||
}
|
||||
|
||||
private static string ResolvePath(string root, string relative)
|
||||
{
|
||||
var segments = relative.Split('/', StringSplitOptions.RemoveEmptyEntries);
|
||||
return Path.Combine(new[] { root }.Concat(segments).ToArray());
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Directory.Exists(_root))
|
||||
{
|
||||
Directory.Delete(_root, recursive: true);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// best effort cleanup
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ using StellaOps.Concelier.Exporter.Json;
|
||||
using StellaOps.Concelier.Models;
|
||||
using StellaOps.Concelier.Storage.Advisories;
|
||||
using StellaOps.Concelier.Storage.Exporting;
|
||||
using StellaOps.Provenance.Mongo;
|
||||
using StellaOps.Provenance;
|
||||
using StellaOps.Cryptography;
|
||||
using StellaOps.Cryptography.DependencyInjection;
|
||||
|
||||
|
||||
@@ -1,109 +1,109 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using StellaOps.Concelier.Exporter.Json;
|
||||
using StellaOps.Concelier.Models;
|
||||
|
||||
namespace StellaOps.Concelier.Exporter.Json.Tests;
|
||||
|
||||
public sealed class VulnListJsonExportPathResolverTests
|
||||
{
|
||||
private static readonly DateTimeOffset DefaultPublished = DateTimeOffset.Parse("2024-01-01T00:00:00Z", CultureInfo.InvariantCulture);
|
||||
|
||||
[Fact]
|
||||
public void ResolvesCvePath()
|
||||
{
|
||||
var advisory = CreateAdvisory("CVE-2024-1234");
|
||||
var resolver = new VulnListJsonExportPathResolver();
|
||||
|
||||
var path = resolver.GetRelativePath(advisory);
|
||||
|
||||
Assert.Equal(Path.Combine("nvd", "2024", "CVE-2024-1234.json"), path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResolvesGhsaWithPackage()
|
||||
{
|
||||
var package = new AffectedPackage(
|
||||
AffectedPackageTypes.SemVer,
|
||||
"pkg:go/github.com/acme/widget@1.0.0",
|
||||
platform: null,
|
||||
versionRanges: Array.Empty<AffectedVersionRange>(),
|
||||
statuses: Array.Empty<AffectedPackageStatus>(),
|
||||
provenance: Array.Empty<AdvisoryProvenance>());
|
||||
|
||||
var advisory = CreateAdvisory(
|
||||
"GHSA-aaaa-bbbb-cccc",
|
||||
aliases: new[] { "CVE-2024-2000" },
|
||||
packages: new[] { package });
|
||||
var resolver = new VulnListJsonExportPathResolver();
|
||||
|
||||
var path = resolver.GetRelativePath(advisory);
|
||||
|
||||
Assert.Equal(Path.Combine("ghsa", "go", "github.com%2Facme%2Fwidget", "GHSA-AAAA-BBBB-CCCC.json"), path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResolvesUbuntuUsn()
|
||||
{
|
||||
var advisory = CreateAdvisory("USN-6620-1");
|
||||
var resolver = new VulnListJsonExportPathResolver();
|
||||
var path = resolver.GetRelativePath(advisory);
|
||||
|
||||
Assert.Equal(Path.Combine("ubuntu", "USN-6620-1.json"), path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResolvesDebianDla()
|
||||
{
|
||||
var advisory = CreateAdvisory("DLA-1234-1");
|
||||
var resolver = new VulnListJsonExportPathResolver();
|
||||
var path = resolver.GetRelativePath(advisory);
|
||||
|
||||
Assert.Equal(Path.Combine("debian", "DLA-1234-1.json"), path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResolvesRedHatRhsa()
|
||||
{
|
||||
var advisory = CreateAdvisory("RHSA-2024:0252");
|
||||
var resolver = new VulnListJsonExportPathResolver();
|
||||
var path = resolver.GetRelativePath(advisory);
|
||||
|
||||
Assert.Equal(Path.Combine("redhat", "oval", "RHSA-2024_0252.json"), path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResolvesAmazonAlas()
|
||||
{
|
||||
var advisory = CreateAdvisory("ALAS2-2024-1234");
|
||||
var resolver = new VulnListJsonExportPathResolver();
|
||||
var path = resolver.GetRelativePath(advisory);
|
||||
|
||||
Assert.Equal(Path.Combine("amazon", "2", "ALAS2-2024-1234.json"), path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResolvesOracleElsa()
|
||||
{
|
||||
var advisory = CreateAdvisory("ELSA-2024-12345");
|
||||
var resolver = new VulnListJsonExportPathResolver();
|
||||
var path = resolver.GetRelativePath(advisory);
|
||||
|
||||
Assert.Equal(Path.Combine("oracle", "linux", "ELSA-2024-12345.json"), path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResolvesRockyRlsa()
|
||||
{
|
||||
var advisory = CreateAdvisory("RLSA-2024:0417");
|
||||
var resolver = new VulnListJsonExportPathResolver();
|
||||
var path = resolver.GetRelativePath(advisory);
|
||||
|
||||
Assert.Equal(Path.Combine("rocky", "RLSA-2024_0417.json"), path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using StellaOps.Concelier.Exporter.Json;
|
||||
using StellaOps.Concelier.Models;
|
||||
|
||||
namespace StellaOps.Concelier.Exporter.Json.Tests;
|
||||
|
||||
public sealed class VulnListJsonExportPathResolverTests
|
||||
{
|
||||
private static readonly DateTimeOffset DefaultPublished = DateTimeOffset.Parse("2024-01-01T00:00:00Z", CultureInfo.InvariantCulture);
|
||||
|
||||
[Fact]
|
||||
public void ResolvesCvePath()
|
||||
{
|
||||
var advisory = CreateAdvisory("CVE-2024-1234");
|
||||
var resolver = new VulnListJsonExportPathResolver();
|
||||
|
||||
var path = resolver.GetRelativePath(advisory);
|
||||
|
||||
Assert.Equal(Path.Combine("nvd", "2024", "CVE-2024-1234.json"), path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResolvesGhsaWithPackage()
|
||||
{
|
||||
var package = new AffectedPackage(
|
||||
AffectedPackageTypes.SemVer,
|
||||
"pkg:go/github.com/acme/widget@1.0.0",
|
||||
platform: null,
|
||||
versionRanges: Array.Empty<AffectedVersionRange>(),
|
||||
statuses: Array.Empty<AffectedPackageStatus>(),
|
||||
provenance: Array.Empty<AdvisoryProvenance>());
|
||||
|
||||
var advisory = CreateAdvisory(
|
||||
"GHSA-aaaa-bbbb-cccc",
|
||||
aliases: new[] { "CVE-2024-2000" },
|
||||
packages: new[] { package });
|
||||
var resolver = new VulnListJsonExportPathResolver();
|
||||
|
||||
var path = resolver.GetRelativePath(advisory);
|
||||
|
||||
Assert.Equal(Path.Combine("ghsa", "go", "github.com%2Facme%2Fwidget", "GHSA-AAAA-BBBB-CCCC.json"), path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResolvesUbuntuUsn()
|
||||
{
|
||||
var advisory = CreateAdvisory("USN-6620-1");
|
||||
var resolver = new VulnListJsonExportPathResolver();
|
||||
var path = resolver.GetRelativePath(advisory);
|
||||
|
||||
Assert.Equal(Path.Combine("ubuntu", "USN-6620-1.json"), path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResolvesDebianDla()
|
||||
{
|
||||
var advisory = CreateAdvisory("DLA-1234-1");
|
||||
var resolver = new VulnListJsonExportPathResolver();
|
||||
var path = resolver.GetRelativePath(advisory);
|
||||
|
||||
Assert.Equal(Path.Combine("debian", "DLA-1234-1.json"), path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResolvesRedHatRhsa()
|
||||
{
|
||||
var advisory = CreateAdvisory("RHSA-2024:0252");
|
||||
var resolver = new VulnListJsonExportPathResolver();
|
||||
var path = resolver.GetRelativePath(advisory);
|
||||
|
||||
Assert.Equal(Path.Combine("redhat", "oval", "RHSA-2024_0252.json"), path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResolvesAmazonAlas()
|
||||
{
|
||||
var advisory = CreateAdvisory("ALAS2-2024-1234");
|
||||
var resolver = new VulnListJsonExportPathResolver();
|
||||
var path = resolver.GetRelativePath(advisory);
|
||||
|
||||
Assert.Equal(Path.Combine("amazon", "2", "ALAS2-2024-1234.json"), path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResolvesOracleElsa()
|
||||
{
|
||||
var advisory = CreateAdvisory("ELSA-2024-12345");
|
||||
var resolver = new VulnListJsonExportPathResolver();
|
||||
var path = resolver.GetRelativePath(advisory);
|
||||
|
||||
Assert.Equal(Path.Combine("oracle", "linux", "ELSA-2024-12345.json"), path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResolvesRockyRlsa()
|
||||
{
|
||||
var advisory = CreateAdvisory("RLSA-2024:0417");
|
||||
var resolver = new VulnListJsonExportPathResolver();
|
||||
var path = resolver.GetRelativePath(advisory);
|
||||
|
||||
Assert.Equal(Path.Combine("rocky", "RLSA-2024_0417.json"), path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResolvesByProvenanceFallback()
|
||||
{
|
||||
var provenance = new[] { new AdvisoryProvenance("wolfi", "map", "", DefaultPublished) };
|
||||
@@ -130,30 +130,30 @@ public sealed class VulnListJsonExportPathResolverTests
|
||||
{
|
||||
var advisory = CreateAdvisory("CUSTOM-2024-99");
|
||||
var resolver = new VulnListJsonExportPathResolver();
|
||||
var path = resolver.GetRelativePath(advisory);
|
||||
|
||||
Assert.Equal(Path.Combine("misc", "CUSTOM-2024-99.json"), path);
|
||||
}
|
||||
|
||||
private static Advisory CreateAdvisory(
|
||||
string advisoryKey,
|
||||
IEnumerable<string>? aliases = null,
|
||||
IEnumerable<AffectedPackage>? packages = null,
|
||||
IEnumerable<AdvisoryProvenance>? provenance = null)
|
||||
{
|
||||
return new Advisory(
|
||||
advisoryKey: advisoryKey,
|
||||
title: $"Advisory {advisoryKey}",
|
||||
summary: null,
|
||||
language: "en",
|
||||
published: DefaultPublished,
|
||||
modified: DefaultPublished,
|
||||
severity: "medium",
|
||||
exploitKnown: false,
|
||||
aliases: aliases ?? Array.Empty<string>(),
|
||||
references: Array.Empty<AdvisoryReference>(),
|
||||
affectedPackages: packages ?? Array.Empty<AffectedPackage>(),
|
||||
cvssMetrics: Array.Empty<CvssMetric>(),
|
||||
provenance: provenance ?? Array.Empty<AdvisoryProvenance>());
|
||||
}
|
||||
}
|
||||
var path = resolver.GetRelativePath(advisory);
|
||||
|
||||
Assert.Equal(Path.Combine("misc", "CUSTOM-2024-99.json"), path);
|
||||
}
|
||||
|
||||
private static Advisory CreateAdvisory(
|
||||
string advisoryKey,
|
||||
IEnumerable<string>? aliases = null,
|
||||
IEnumerable<AffectedPackage>? packages = null,
|
||||
IEnumerable<AdvisoryProvenance>? provenance = null)
|
||||
{
|
||||
return new Advisory(
|
||||
advisoryKey: advisoryKey,
|
||||
title: $"Advisory {advisoryKey}",
|
||||
summary: null,
|
||||
language: "en",
|
||||
published: DefaultPublished,
|
||||
modified: DefaultPublished,
|
||||
severity: "medium",
|
||||
exploitKnown: false,
|
||||
aliases: aliases ?? Array.Empty<string>(),
|
||||
references: Array.Empty<AdvisoryReference>(),
|
||||
affectedPackages: packages ?? Array.Empty<AffectedPackage>(),
|
||||
cvssMetrics: Array.Empty<CvssMetric>(),
|
||||
provenance: provenance ?? Array.Empty<AdvisoryProvenance>());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user