feat: Add Scanner CI runner and related artifacts
- Implemented `run-scanner-ci.sh` to build and run tests for the Scanner solution with a warmed NuGet cache. - Created `excititor-vex-traces.json` dashboard for monitoring Excititor VEX observations. - Added Docker Compose configuration for the OTLP span sink in `docker-compose.spansink.yml`. - Configured OpenTelemetry collector in `otel-spansink.yaml` to receive and process traces. - Developed `run-spansink.sh` script to run the OTLP span sink for Excititor traces. - Introduced `FileSystemRiskBundleObjectStore` for storing risk bundle artifacts in the filesystem. - Built `RiskBundleBuilder` for creating risk bundles with associated metadata and providers. - Established `RiskBundleJob` to execute the risk bundle creation and storage process. - Defined models for risk bundle inputs, entries, and manifests in `RiskBundleModels.cs`. - Implemented signing functionality for risk bundle manifests with `HmacRiskBundleManifestSigner`. - Created unit tests for `RiskBundleBuilder`, `RiskBundleJob`, and signing functionality to ensure correctness. - Added filesystem artifact reader tests to validate manifest parsing and artifact listing. - Included test manifests for egress scenarios in the task runner tests. - Developed timeline query service tests to verify tenant and event ID handling.
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
using System.IO.Compression;
|
||||
using StellaOps.ExportCenter.RiskBundles;
|
||||
|
||||
namespace StellaOps.ExportCenter.Tests;
|
||||
|
||||
public sealed class RiskBundleBuilderTests
|
||||
{
|
||||
[Fact]
|
||||
public void Build_WritesManifestAndFiles_Deterministically()
|
||||
{
|
||||
using var temp = new TempDir();
|
||||
var kev = temp.WriteFile("kev.json", "{\"cve\":\"CVE-0001\"}");
|
||||
var epss = temp.WriteFile("epss.csv", "cve,score\nCVE-0001,0.12\n");
|
||||
|
||||
var request = new RiskBundleBuildRequest(
|
||||
BundleId: Guid.Parse("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"),
|
||||
Providers: new[]
|
||||
{
|
||||
new RiskBundleProviderInput("cisa-kev", kev, "CISA KEV"),
|
||||
new RiskBundleProviderInput("first-epss", epss, "FIRST EPSS")
|
||||
});
|
||||
|
||||
var builder = new RiskBundleBuilder();
|
||||
var cancellation = TestContext.Current.CancellationToken;
|
||||
var result = builder.Build(request, cancellation);
|
||||
|
||||
Assert.Equal(2, result.Manifest.Providers.Count);
|
||||
Assert.Equal(new[] { "cisa-kev", "first-epss" }, result.Manifest.Providers.Select(p => p.ProviderId));
|
||||
|
||||
// Manifest hash stable
|
||||
var second = builder.Build(request, cancellation);
|
||||
Assert.Equal(result.RootHash, second.RootHash);
|
||||
|
||||
// Bundle contains manifest and provider files
|
||||
using var gzip = new GZipStream(new MemoryStream(result.BundleStream.ToArray()), CompressionMode.Decompress, leaveOpen: false);
|
||||
using var tar = new System.Formats.Tar.TarReader(gzip, leaveOpen: false);
|
||||
var entries = new List<string>();
|
||||
System.Formats.Tar.TarEntry? entry;
|
||||
while ((entry = tar.GetNextEntry()) is not null)
|
||||
{
|
||||
entries.Add(entry.Name);
|
||||
}
|
||||
|
||||
Assert.Contains("manifests/provider-manifest.json", entries);
|
||||
Assert.Contains("providers/cisa-kev/snapshot", entries);
|
||||
Assert.Contains("providers/first-epss/snapshot", entries);
|
||||
}
|
||||
|
||||
private sealed class TempDir : IDisposable
|
||||
{
|
||||
public string Path { get; } = System.IO.Path.Combine(System.IO.Path.GetTempPath(), "riskbundle-tests-" + Guid.NewGuid().ToString("N"));
|
||||
|
||||
public TempDir()
|
||||
{
|
||||
Directory.CreateDirectory(Path);
|
||||
}
|
||||
|
||||
public string WriteFile(string name, string contents)
|
||||
{
|
||||
var full = System.IO.Path.Combine(Path, name);
|
||||
File.WriteAllText(full, contents);
|
||||
return full;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
try { Directory.Delete(Path, recursive: true); } catch { /* ignore */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using StellaOps.ExportCenter.RiskBundles;
|
||||
|
||||
namespace StellaOps.ExportCenter.Tests;
|
||||
|
||||
public sealed class RiskBundleJobTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_StoresManifestAndBundle()
|
||||
{
|
||||
using var temp = new TempDir();
|
||||
var providerPath = temp.WriteFile("kev.json", "{}\n");
|
||||
|
||||
var buildRequest = new RiskBundleBuildRequest(
|
||||
Guid.NewGuid(),
|
||||
Providers: new[] { new RiskBundleProviderInput("cisa-kev", providerPath, "CISA KEV") });
|
||||
|
||||
var job = new RiskBundleJob(
|
||||
new RiskBundleBuilder(),
|
||||
new HmacRiskBundleManifestSigner("secret", "risk-key"),
|
||||
new InMemoryObjectStore(),
|
||||
NullLogger<RiskBundleJob>.Instance);
|
||||
|
||||
var outcome = await job.ExecuteAsync(new RiskBundleJobRequest(buildRequest), TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Equal("risk-bundles/provider-manifest.json", outcome.ManifestStorage.StorageKey);
|
||||
Assert.Equal("risk-bundles/provider-manifest.dsse", outcome.ManifestSignatureStorage.StorageKey);
|
||||
Assert.Equal("risk-bundles/risk-bundle.tar.gz", outcome.BundleStorage.StorageKey);
|
||||
Assert.False(string.IsNullOrWhiteSpace(outcome.ManifestJson));
|
||||
Assert.False(string.IsNullOrWhiteSpace(outcome.ManifestSignatureJson));
|
||||
}
|
||||
|
||||
private sealed class InMemoryObjectStore : IRiskBundleObjectStore
|
||||
{
|
||||
private readonly Dictionary<string, byte[]> _store = new(StringComparer.Ordinal);
|
||||
|
||||
public Task<RiskBundleStorageMetadata> StoreAsync(RiskBundleObjectStoreOptions options, Stream content, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var ms = new MemoryStream();
|
||||
content.CopyTo(ms);
|
||||
_store[options.StorageKey] = ms.ToArray();
|
||||
return Task.FromResult(new RiskBundleStorageMetadata(options.StorageKey, ms.Length, options.ContentType));
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class TempDir : IDisposable
|
||||
{
|
||||
public string Path { get; } = System.IO.Path.Combine(System.IO.Path.GetTempPath(), "riskbundle-job-" + Guid.NewGuid().ToString("N"));
|
||||
|
||||
public TempDir()
|
||||
{
|
||||
Directory.CreateDirectory(Path);
|
||||
}
|
||||
|
||||
public string WriteFile(string name, string contents)
|
||||
{
|
||||
var full = System.IO.Path.Combine(Path, name);
|
||||
File.WriteAllText(full, contents);
|
||||
return full;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
try { Directory.Delete(Path, recursive: true); } catch { /* ignore */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using System.Text.Json;
|
||||
using StellaOps.ExportCenter.RiskBundles;
|
||||
|
||||
namespace StellaOps.ExportCenter.Tests;
|
||||
|
||||
public class RiskBundleSignerTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task SignAsync_ProducesDsseEnvelope()
|
||||
{
|
||||
var signer = new HmacRiskBundleManifestSigner("secret-key", "test-key");
|
||||
const string manifest = "{\"foo\":1}";
|
||||
|
||||
var doc = await signer.SignAsync(manifest, TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Equal("application/stellaops.risk-bundle.provider-manifest+json", doc.PayloadType);
|
||||
Assert.NotNull(doc.Payload);
|
||||
Assert.Single(doc.Signatures);
|
||||
Assert.Equal("test-key", doc.Signatures[0].KeyId);
|
||||
|
||||
// ensure payload decodes to original manifest
|
||||
var payloadBytes = Convert.FromBase64String(doc.Payload);
|
||||
var decoded = System.Text.Encoding.UTF8.GetString(payloadBytes);
|
||||
Assert.Equal(manifest, decoded);
|
||||
}
|
||||
}
|
||||
@@ -53,7 +53,7 @@
|
||||
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<ItemGroup>
|
||||
|
||||
|
||||
|
||||
@@ -116,12 +116,13 @@
|
||||
|
||||
|
||||
|
||||
<ProjectReference Include="..\StellaOps.ExportCenter.Core\StellaOps.ExportCenter.Core.csproj"/>
|
||||
|
||||
|
||||
|
||||
|
||||
<ProjectReference Include="..\StellaOps.ExportCenter.Infrastructure\StellaOps.ExportCenter.Infrastructure.csproj"/>
|
||||
<ProjectReference Include="..\StellaOps.ExportCenter.Core\StellaOps.ExportCenter.Core.csproj"/>
|
||||
|
||||
|
||||
|
||||
|
||||
<ProjectReference Include="..\StellaOps.ExportCenter.Infrastructure\StellaOps.ExportCenter.Infrastructure.csproj"/>
|
||||
<ProjectReference Include="..\..\StellaOps.ExportCenter.RiskBundles\StellaOps.ExportCenter.RiskBundles.csproj" />
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user