Restructure solution layout by module

This commit is contained in:
master
2025-10-28 15:10:40 +02:00
parent 95daa159c4
commit d870da18ce
4103 changed files with 192899 additions and 187024 deletions

View File

@@ -0,0 +1,257 @@
using System.Diagnostics.Metrics;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Text;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using MongoDB.Bson;
using MongoDB.Driver;
using StellaOps.Concelier.Models;
using StellaOps.Concelier.Connector.Common.Fetch;
using StellaOps.Concelier.Connector.Common.Testing;
using StellaOps.Concelier.Connector.Cve.Configuration;
using StellaOps.Concelier.Connector.Cve.Internal;
using StellaOps.Concelier.Testing;
using StellaOps.Concelier.Storage.Mongo;
using StellaOps.Concelier.Storage.Mongo.Advisories;
using StellaOps.Concelier.Storage.Mongo.Documents;
using StellaOps.Concelier.Storage.Mongo.Dtos;
using Xunit.Abstractions;
namespace StellaOps.Concelier.Connector.Cve.Tests;
[Collection("mongo-fixture")]
public sealed class CveConnectorTests : IAsyncLifetime
{
private readonly MongoIntegrationFixture _fixture;
private readonly ITestOutputHelper _output;
private ConnectorTestHarness? _harness;
public CveConnectorTests(MongoIntegrationFixture fixture, ITestOutputHelper output)
{
_fixture = fixture;
_output = output;
}
[Fact]
public async Task FetchParseMap_EmitsCanonicalAdvisory()
{
var initialTime = new DateTimeOffset(2024, 10, 1, 0, 0, 0, TimeSpan.Zero);
await EnsureHarnessAsync(initialTime);
var harness = _harness!;
var since = initialTime - TimeSpan.FromDays(30);
var listUri = new Uri($"https://cve.test/api/cve?time_modified.gte={Uri.EscapeDataString(since.ToString("O"))}&time_modified.lte={Uri.EscapeDataString(initialTime.ToString("O"))}&page=1&size=5");
harness.Handler.AddJsonResponse(listUri, ReadFixture("Fixtures/cve-list.json"));
harness.Handler.SetFallback(request =>
{
if (request.RequestUri is null)
{
return new HttpResponseMessage(HttpStatusCode.NotFound);
}
if (request.RequestUri.AbsoluteUri.Equals("https://cve.test/api/cve/CVE-2024-0001", StringComparison.OrdinalIgnoreCase))
{
return new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(ReadFixture("Fixtures/cve-CVE-2024-0001.json"), Encoding.UTF8, "application/json")
};
}
return new HttpResponseMessage(HttpStatusCode.NotFound);
});
var metrics = new Dictionary<string, long>(StringComparer.Ordinal);
using var listener = new MeterListener
{
InstrumentPublished = (instrument, meterListener) =>
{
if (instrument.Meter.Name == CveDiagnostics.MeterName)
{
meterListener.EnableMeasurementEvents(instrument);
}
}
};
listener.SetMeasurementEventCallback<long>((instrument, value, tags, state) =>
{
if (metrics.TryGetValue(instrument.Name, out var existing))
{
metrics[instrument.Name] = existing + value;
}
else
{
metrics[instrument.Name] = value;
}
});
listener.Start();
var connector = new CveConnectorPlugin().Create(harness.ServiceProvider);
await connector.FetchAsync(harness.ServiceProvider, CancellationToken.None);
await connector.ParseAsync(harness.ServiceProvider, CancellationToken.None);
await connector.MapAsync(harness.ServiceProvider, CancellationToken.None);
listener.Dispose();
var advisoryStore = harness.ServiceProvider.GetRequiredService<IAdvisoryStore>();
var advisory = await advisoryStore.FindAsync("CVE-2024-0001", CancellationToken.None);
Assert.NotNull(advisory);
var snapshot = SnapshotSerializer.ToSnapshot(advisory!).Replace("\r\n", "\n").TrimEnd();
var expected = ReadFixture("Fixtures/expected-CVE-2024-0001.json").Replace("\r\n", "\n").TrimEnd();
if (!string.Equals(expected, snapshot, StringComparison.Ordinal))
{
var actualPath = Path.Combine(AppContext.BaseDirectory, "Fixtures", "expected-CVE-2024-0001.actual.json");
Directory.CreateDirectory(Path.GetDirectoryName(actualPath)!);
File.WriteAllText(actualPath, snapshot);
}
Assert.Equal(expected, snapshot);
harness.Handler.AssertNoPendingResponses();
_output.WriteLine("CVE connector smoke metrics:");
foreach (var entry in metrics.OrderBy(static pair => pair.Key, StringComparer.Ordinal))
{
_output.WriteLine($" {entry.Key} = {entry.Value}");
}
}
[Fact]
public async Task FetchWithoutCredentials_SeedsFromDirectory()
{
var initialTime = new DateTimeOffset(2025, 1, 1, 0, 0, 0, TimeSpan.Zero);
var projectRoot = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "..", "..", ".."));
var repositoryRoot = Path.GetFullPath(Path.Combine(projectRoot, "..", ".."));
var seedDirectory = Path.Combine(repositoryRoot, "seed-data", "cve", "2025-10-15");
Assert.True(Directory.Exists(seedDirectory), $"Seed directory '{seedDirectory}' was not found.");
await using var harness = new ConnectorTestHarness(_fixture, initialTime, CveOptions.HttpClientName);
await harness.EnsureServiceProviderAsync(services =>
{
services.AddLogging(builder =>
{
builder.ClearProviders();
builder.AddProvider(new TestOutputLoggerProvider(_output, LogLevel.Information));
builder.SetMinimumLevel(LogLevel.Information);
});
services.AddCveConnector(options =>
{
options.BaseEndpoint = new Uri("https://cve.test/api/", UriKind.Absolute);
options.SeedDirectory = seedDirectory;
options.PageSize = 5;
options.MaxPagesPerFetch = 1;
options.InitialBackfill = TimeSpan.FromDays(30);
options.RequestDelay = TimeSpan.Zero;
});
});
var connector = new CveConnectorPlugin().Create(harness.ServiceProvider);
await connector.FetchAsync(harness.ServiceProvider, CancellationToken.None);
Assert.Empty(harness.Handler.Requests);
var advisoryStore = harness.ServiceProvider.GetRequiredService<IAdvisoryStore>();
var advisories = await advisoryStore.GetRecentAsync(10, CancellationToken.None);
var keys = advisories.Select(advisory => advisory.AdvisoryKey).ToArray();
Assert.Contains("CVE-2024-0001", keys);
Assert.Contains("CVE-2024-4567", keys);
}
private async Task EnsureHarnessAsync(DateTimeOffset initialTime)
{
if (_harness is not null)
{
return;
}
var harness = new ConnectorTestHarness(_fixture, initialTime, CveOptions.HttpClientName);
await harness.EnsureServiceProviderAsync(services =>
{
services.AddLogging(builder =>
{
builder.ClearProviders();
builder.AddProvider(new TestOutputLoggerProvider(_output, LogLevel.Information));
builder.SetMinimumLevel(LogLevel.Information);
});
services.AddCveConnector(options =>
{
options.BaseEndpoint = new Uri("https://cve.test/api/", UriKind.Absolute);
options.ApiOrg = "test-org";
options.ApiUser = "test-user";
options.ApiKey = "test-key";
options.InitialBackfill = TimeSpan.FromDays(30);
options.PageSize = 5;
options.MaxPagesPerFetch = 2;
options.RequestDelay = TimeSpan.Zero;
});
});
_harness = harness;
}
private static string ReadFixture(string relativePath)
{
var path = Path.Combine(AppContext.BaseDirectory, relativePath);
return File.ReadAllText(path);
}
public async Task InitializeAsync()
{
await Task.CompletedTask;
}
public async Task DisposeAsync()
{
if (_harness is not null)
{
await _harness.DisposeAsync();
}
}
private sealed class TestOutputLoggerProvider : ILoggerProvider
{
private readonly ITestOutputHelper _output;
private readonly LogLevel _minLevel;
public TestOutputLoggerProvider(ITestOutputHelper output, LogLevel minLevel)
{
_output = output;
_minLevel = minLevel;
}
public ILogger CreateLogger(string categoryName) => new TestOutputLogger(_output, _minLevel);
public void Dispose()
{
}
private sealed class TestOutputLogger : ILogger
{
private readonly ITestOutputHelper _output;
private readonly LogLevel _minLevel;
public TestOutputLogger(ITestOutputHelper output, LogLevel minLevel)
{
_output = output;
_minLevel = minLevel;
}
public IDisposable BeginScope<TState>(TState state) where TState : notnull => NullLogger.Instance.BeginScope(state);
public bool IsEnabled(LogLevel logLevel) => logLevel >= _minLevel;
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
{
if (IsEnabled(logLevel))
{
_output.WriteLine(formatter(state, exception));
}
}
}
}
}

View File

@@ -0,0 +1,72 @@
{
"dataType": "CVE_RECORD",
"dataVersion": "5.0",
"cveMetadata": {
"cveId": "CVE-2024-0001",
"assignerShortName": "ExampleOrg",
"state": "PUBLISHED",
"dateReserved": "2024-01-01T00:00:00Z",
"datePublished": "2024-09-10T12:00:00Z",
"dateUpdated": "2024-09-15T12:00:00Z"
},
"containers": {
"cna": {
"title": "Example Product Remote Code Execution",
"descriptions": [
{
"lang": "en",
"value": "An example vulnerability allowing remote attackers to execute arbitrary code."
}
],
"affected": [
{
"vendor": "ExampleVendor",
"product": "ExampleProduct",
"platform": "linux",
"defaultStatus": "affected",
"versions": [
{
"status": "affected",
"version": "1.0.0",
"lessThan": "1.2.0",
"versionType": "semver"
},
{
"status": "unaffected",
"version": "1.2.0",
"versionType": "semver"
}
]
}
],
"references": [
{
"url": "https://example.com/security/advisory",
"name": "Vendor Advisory",
"tags": [
"vendor-advisory"
]
},
{
"url": "https://cve.example.com/CVE-2024-0001",
"tags": [
"third-party-advisory"
]
}
],
"metrics": [
{
"cvssV3_1": {
"version": "3.1",
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
"baseScore": 9.8,
"baseSeverity": "CRITICAL"
}
}
],
"aliases": [
"GHSA-xxxx-yyyy-zzzz"
]
}
}
}

View File

@@ -0,0 +1,18 @@
{
"dataType": "CVE_RECORD_LIST",
"dataVersion": "5.0",
"data": [
{
"cveMetadata": {
"cveId": "CVE-2024-0001",
"state": "PUBLISHED",
"dateUpdated": "2024-09-15T12:00:00Z"
}
}
],
"pagination": {
"page": 1,
"totalCount": 1,
"itemsPerPage": 5
}
}

View File

@@ -0,0 +1,221 @@
{
"advisoryKey": "CVE-2024-0001",
"affectedPackages": [
{
"type": "vendor",
"identifier": "examplevendor:exampleproduct",
"platform": "linux",
"versionRanges": [
{
"fixedVersion": "1.2.0",
"introducedVersion": "1.0.0",
"lastAffectedVersion": null,
"primitives": {
"evr": null,
"hasVendorExtensions": true,
"nevra": null,
"semVer": {
"constraintExpression": "version=1.0.0, < 1.2.0",
"exactValue": null,
"fixed": "1.2.0",
"fixedInclusive": false,
"introduced": "1.0.0",
"introducedInclusive": true,
"lastAffected": null,
"lastAffectedInclusive": true,
"style": "range"
},
"vendorExtensions": {
"vendor": "ExampleVendor",
"product": "ExampleProduct",
"platform": "linux",
"version": "1.0.0",
"lessThan": "1.2.0",
"versionType": "semver"
}
},
"provenance": {
"source": "cve",
"kind": "affected-range",
"value": "examplevendor:exampleproduct",
"decisionReason": null,
"recordedAt": "2024-10-01T00:00:00+00:00",
"fieldMask": []
},
"rangeExpression": "version=1.0.0, < 1.2.0",
"rangeKind": "semver"
},
{
"fixedVersion": "1.2.0",
"introducedVersion": "1.2.0",
"lastAffectedVersion": "1.2.0",
"primitives": {
"evr": null,
"hasVendorExtensions": true,
"nevra": null,
"semVer": {
"constraintExpression": "version=1.2.0",
"exactValue": null,
"fixed": "1.2.0",
"fixedInclusive": false,
"introduced": "1.2.0",
"introducedInclusive": true,
"lastAffected": "1.2.0",
"lastAffectedInclusive": true,
"style": "range"
},
"vendorExtensions": {
"vendor": "ExampleVendor",
"product": "ExampleProduct",
"platform": "linux",
"version": "1.2.0",
"versionType": "semver"
}
},
"provenance": {
"source": "cve",
"kind": "affected-range",
"value": "examplevendor:exampleproduct",
"decisionReason": null,
"recordedAt": "2024-10-01T00:00:00+00:00",
"fieldMask": []
},
"rangeExpression": "version=1.2.0",
"rangeKind": "semver"
}
],
"normalizedVersions": [
{
"scheme": "semver",
"type": "exact",
"min": null,
"minInclusive": null,
"max": null,
"maxInclusive": null,
"value": "1.2.0",
"notes": "cve:cve-2024-0001:examplevendor:exampleproduct"
},
{
"scheme": "semver",
"type": "range",
"min": "1.0.0",
"minInclusive": true,
"max": "1.2.0",
"maxInclusive": false,
"value": null,
"notes": "cve:cve-2024-0001:examplevendor:exampleproduct"
}
],
"statuses": [
{
"provenance": {
"source": "cve",
"kind": "affected-status",
"value": "examplevendor:exampleproduct",
"decisionReason": null,
"recordedAt": "2024-10-01T00:00:00+00:00",
"fieldMask": []
},
"status": "affected"
},
{
"provenance": {
"source": "cve",
"kind": "affected-status",
"value": "examplevendor:exampleproduct",
"decisionReason": null,
"recordedAt": "2024-10-01T00:00:00+00:00",
"fieldMask": []
},
"status": "not_affected"
}
],
"provenance": [
{
"source": "cve",
"kind": "affected",
"value": "examplevendor:exampleproduct",
"decisionReason": null,
"recordedAt": "2024-10-01T00:00:00+00:00",
"fieldMask": []
}
]
}
],
"aliases": [
"CVE-2024-0001",
"GHSA-xxxx-yyyy-zzzz"
],
"credits": [],
"cvssMetrics": [
{
"baseScore": 9.8,
"baseSeverity": "critical",
"provenance": {
"source": "cve",
"kind": "cvss",
"value": "cve/CVE-2024-0001",
"decisionReason": null,
"recordedAt": "2024-10-01T00:00:00+00:00",
"fieldMask": []
},
"vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
"version": "3.1"
}
],
"exploitKnown": false,
"language": "en",
"modified": "2024-09-15T12:00:00+00:00",
"provenance": [
{
"source": "cve",
"kind": "document",
"value": "cve/CVE-2024-0001",
"decisionReason": null,
"recordedAt": "2024-10-01T00:00:00+00:00",
"fieldMask": []
},
{
"source": "cve",
"kind": "mapping",
"value": "CVE-2024-0001",
"decisionReason": null,
"recordedAt": "2024-10-01T00:00:00+00:00",
"fieldMask": []
}
],
"published": "2024-09-10T12:00:00+00:00",
"references": [
{
"kind": "third-party-advisory",
"provenance": {
"source": "cve",
"kind": "reference",
"value": "https://cve.example.com/CVE-2024-0001",
"decisionReason": null,
"recordedAt": "2024-10-01T00:00:00+00:00",
"fieldMask": []
},
"sourceTag": null,
"summary": null,
"url": "https://cve.example.com/CVE-2024-0001"
},
{
"kind": "vendor-advisory",
"provenance": {
"source": "cve",
"kind": "reference",
"value": "https://example.com/security/advisory",
"decisionReason": null,
"recordedAt": "2024-10-01T00:00:00+00:00",
"fieldMask": []
},
"sourceTag": "Vendor Advisory",
"summary": null,
"url": "https://example.com/security/advisory"
}
],
"severity": "critical",
"summary": "An example vulnerability allowing remote attackers to execute arbitrary code.",
"title": "Example Product Remote Code Execution"
}

View File

@@ -0,0 +1,18 @@
<?xml version='1.0' encoding='utf-8'?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../../__Libraries/StellaOps.Concelier.Models/StellaOps.Concelier.Models.csproj" />
<ProjectReference Include="../../__Libraries/StellaOps.Concelier.Connector.Cve/StellaOps.Concelier.Connector.Cve.csproj" />
<ProjectReference Include="../../__Libraries/StellaOps.Concelier.Connector.Common/StellaOps.Concelier.Connector.Common.csproj" />
<ProjectReference Include="../../__Libraries/StellaOps.Concelier.Storage.Mongo/StellaOps.Concelier.Storage.Mongo.csproj" />
<ProjectReference Include="../../__Libraries/StellaOps.Concelier.Testing/StellaOps.Concelier.Testing.csproj" />
</ItemGroup>
<ItemGroup>
<None Include="Fixtures/*.json" CopyToOutputDirectory="Always" />
</ItemGroup>
</Project>