feat: Add documentation and task tracking for Sprints 508 to 514 in Ops & Offline

- Created detailed markdown files for Sprints 508 (Ops Offline Kit), 509 (Samples), 510 (AirGap), 511 (Api), 512 (Bench), 513 (Provenance), and 514 (Sovereign Crypto Enablement) outlining tasks, dependencies, and owners.
- Introduced a comprehensive Reachability Evidence Delivery Guide to streamline the reachability signal process.
- Implemented unit tests for Advisory AI to block known injection patterns and redact secrets.
- Added AuthoritySenderConstraintHelper to manage sender constraints in OpenIddict transactions.
This commit is contained in:
master
2025-11-08 23:18:28 +02:00
parent 536f6249a6
commit ae69b1a8a1
187 changed files with 4326 additions and 3196 deletions

View File

@@ -1,8 +1,7 @@
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Cryptography;
using System.Text;
using System.Text;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Mongo2Go;
@@ -13,9 +12,10 @@ using StellaOps.Concelier.Connector.Common.Fetch;
using StellaOps.Concelier.Connector.Common.Http;
using StellaOps.Concelier.Core.Aoc;
using StellaOps.Concelier.Core.Linksets;
using StellaOps.Concelier.RawModels;
using StellaOps.Concelier.Storage.Mongo;
using StellaOps.Concelier.Storage.Mongo.Documents;
using StellaOps.Concelier.RawModels;
using StellaOps.Concelier.Storage.Mongo;
using StellaOps.Concelier.Storage.Mongo.Documents;
using StellaOps.Cryptography;
namespace StellaOps.Concelier.Connector.Common.Tests;
@@ -23,14 +23,16 @@ public sealed class SourceFetchServiceGuardTests : IAsyncLifetime
{
private readonly MongoDbRunner _runner;
private readonly IMongoDatabase _database;
private readonly RawDocumentStorage _rawStorage;
private readonly RawDocumentStorage _rawStorage;
private readonly ICryptoHash _hash;
public SourceFetchServiceGuardTests()
{
_runner = MongoDbRunner.Start(singleNodeReplSet: true);
var client = new MongoClient(_runner.ConnectionString);
_database = client.GetDatabase($"source-fetch-guard-{Guid.NewGuid():N}");
_rawStorage = new RawDocumentStorage(_database);
_rawStorage = new RawDocumentStorage(_database);
_hash = CryptoHashFactory.CreateDefault();
}
[Fact]
@@ -53,17 +55,18 @@ public sealed class SourceFetchServiceGuardTests : IAsyncLifetime
var linksetMapper = new NoopAdvisoryLinksetMapper();
var service = new SourceFetchService(
httpClientFactory,
_rawStorage,
documentStore,
NullLogger<SourceFetchService>.Instance,
jitter,
guard,
linksetMapper,
TimeProvider.System,
httpOptions,
storageOptions);
var service = new SourceFetchService(
httpClientFactory,
_rawStorage,
documentStore,
NullLogger<SourceFetchService>.Instance,
jitter,
guard,
linksetMapper,
_hash,
TimeProvider.System,
httpOptions,
storageOptions);
var request = new SourceFetchRequest("client", "vndr.msrc", new Uri("https://example.test/advisories/ADV-1234"))
{
@@ -82,7 +85,7 @@ public sealed class SourceFetchServiceGuardTests : IAsyncLifetime
Assert.Equal("tenant-default", guard.LastDocument!.Tenant);
Assert.Equal("msrc", guard.LastDocument.Source.Vendor);
Assert.Equal("ADV-1234", guard.LastDocument.Upstream.UpstreamId);
var expectedHash = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(responsePayload))).ToLowerInvariant();
var expectedHash = _hash.ComputeHashHex(Encoding.UTF8.GetBytes(responsePayload), HashAlgorithms.Sha256);
Assert.Equal(expectedHash, guard.LastDocument.Upstream.ContentHash);
Assert.NotNull(documentStore.LastRecord);
Assert.True(documentStore.UpsertCount > 0);
@@ -114,17 +117,18 @@ public sealed class SourceFetchServiceGuardTests : IAsyncLifetime
var linksetMapper = new NoopAdvisoryLinksetMapper();
var service = new SourceFetchService(
httpClientFactory,
_rawStorage,
documentStore,
NullLogger<SourceFetchService>.Instance,
jitter,
guard,
linksetMapper,
TimeProvider.System,
httpOptions,
storageOptions);
var service = new SourceFetchService(
httpClientFactory,
_rawStorage,
documentStore,
NullLogger<SourceFetchService>.Instance,
jitter,
guard,
linksetMapper,
_hash,
TimeProvider.System,
httpOptions,
storageOptions);
var request = new SourceFetchRequest("client", "nvd", new Uri("https://example.test/data/XYZ"))
{

View File

@@ -11,6 +11,7 @@ using StellaOps.Concelier.Connector.Common.Fetch;
using StellaOps.Concelier.Connector.Common.State;
using StellaOps.Concelier.Storage.Mongo;
using StellaOps.Concelier.Storage.Mongo.Documents;
using StellaOps.Cryptography;
namespace StellaOps.Concelier.Connector.Common.Tests;
@@ -23,6 +24,7 @@ public sealed class SourceStateSeedProcessorTests : IAsyncLifetime
private readonly RawDocumentStorage _rawStorage;
private readonly MongoSourceStateRepository _stateRepository;
private readonly FakeTimeProvider _timeProvider;
private readonly ICryptoHash _hash;
public SourceStateSeedProcessorTests()
{
@@ -33,6 +35,7 @@ public sealed class SourceStateSeedProcessorTests : IAsyncLifetime
_rawStorage = new RawDocumentStorage(_database);
_stateRepository = new MongoSourceStateRepository(_database, NullLogger<MongoSourceStateRepository>.Instance);
_timeProvider = new FakeTimeProvider(new DateTimeOffset(2025, 10, 28, 12, 0, 0, TimeSpan.Zero));
_hash = CryptoHashFactory.CreateDefault();
}
[Fact]
@@ -199,6 +202,7 @@ public sealed class SourceStateSeedProcessorTests : IAsyncLifetime
_documentStore,
_rawStorage,
_stateRepository,
_hash,
_timeProvider,
NullLogger<SourceStateSeedProcessor>.Instance);

View File

@@ -20,5 +20,6 @@
<ItemGroup>
<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.Cryptography/StellaOps.Cryptography.csproj" />
</ItemGroup>
</Project>

View File

@@ -10,10 +10,11 @@
<ProjectReference Include="../../__Libraries/StellaOps.Concelier.Connector.Common/StellaOps.Concelier.Connector.Common.csproj" />
<ProjectReference Include="../../__Libraries/StellaOps.Concelier.Models/StellaOps.Concelier.Models.csproj" />
<ProjectReference Include="../../__Libraries/StellaOps.Concelier.Storage.Mongo/StellaOps.Concelier.Storage.Mongo.csproj" />
<ProjectReference Include="../../../__Libraries/StellaOps.Cryptography.DependencyInjection/StellaOps.Cryptography.DependencyInjection.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="Fixtures\**\*">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
</Project>

View File

@@ -20,7 +20,8 @@ using StellaOps.Concelier.Connector.Distro.Ubuntu;
using StellaOps.Concelier.Connector.Distro.Ubuntu.Configuration;
using StellaOps.Concelier.Storage.Mongo;
using StellaOps.Concelier.Storage.Mongo.Advisories;
using StellaOps.Concelier.Testing;
using StellaOps.Concelier.Testing;
using StellaOps.Cryptography.DependencyInjection;
using Xunit;
namespace StellaOps.Concelier.Connector.Distro.Ubuntu.Tests;
@@ -94,16 +95,17 @@ public sealed class UbuntuConnectorTests : IAsyncLifetime
services.AddSingleton<TimeProvider>(_timeProvider);
services.AddSingleton(_handler);
services.AddMongoStorage(options =>
{
options.ConnectionString = _fixture.Runner.ConnectionString;
options.DatabaseName = _fixture.Database.DatabaseNamespace.DatabaseName;
options.CommandTimeout = TimeSpan.FromSeconds(5);
});
services.AddSourceCommon();
services.AddUbuntuConnector(options =>
{
services.AddMongoStorage(options =>
{
options.ConnectionString = _fixture.Runner.ConnectionString;
options.DatabaseName = _fixture.Database.DatabaseNamespace.DatabaseName;
options.CommandTimeout = TimeSpan.FromSeconds(5);
});
services.AddSourceCommon();
services.AddStellaOpsCrypto();
services.AddUbuntuConnector(options =>
{
options.NoticesEndpoint = new Uri("https://ubuntu.com/security/notices.json");
options.NoticeDetailBaseUri = new Uri("https://ubuntu.com/security/");
options.MaxNoticesPerFetch = 2;

View File

@@ -5,24 +5,25 @@ using System.Globalization;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Cryptography;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Net.Http.Headers;
using System.Text.Json;
using System.Text.RegularExpressions;
using MongoDB.Bson;
using StellaOps.Concelier.Models;
using StellaOps.Concelier.Connector.Common;
using StellaOps.Concelier.Connector.Osv;
using StellaOps.Concelier.Connector.Osv.Internal;
using StellaOps.Concelier.Storage.Mongo.Documents;
using StellaOps.Concelier.Storage.Mongo.Dtos;
using Xunit;
using StellaOps.Concelier.Connector.Common;
using StellaOps.Concelier.Connector.Osv;
using StellaOps.Concelier.Connector.Osv.Internal;
using StellaOps.Concelier.Storage.Mongo.Documents;
using StellaOps.Concelier.Storage.Mongo.Dtos;
using StellaOps.Cryptography;
using Xunit;
namespace StellaOps.Concelier.Connector.Osv.Tests;
public sealed class OsvGhsaParityRegressionTests
{
private static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.Web);
public sealed class OsvGhsaParityRegressionTests
{
private static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.Web);
private static readonly ICryptoHash Hash = CryptoHashFactory.CreateDefault();
// Curated GHSA identifiers spanning multiple ecosystems (PyPI, npm/go, Maven) for parity coverage.
private static readonly string[] GhsaIds =
@@ -560,7 +561,7 @@ public sealed class OsvGhsaParityRegressionTests
private static string ComputeSha256Hex(string payload)
{
var bytes = SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(payload));
var bytes = Hash.ComputeHash(System.Text.Encoding.UTF8.GetBytes(payload), HashAlgorithms.Sha256);
return Convert.ToHexString(bytes);
}

View File

@@ -10,10 +10,11 @@
<ProjectReference Include="../../__Libraries/StellaOps.Concelier.Connector.Common/StellaOps.Concelier.Connector.Common.csproj" />
<ProjectReference Include="../../__Libraries/StellaOps.Concelier.Connector.Osv/StellaOps.Concelier.Connector.Osv.csproj" />
<ProjectReference Include="../../__Libraries/StellaOps.Concelier.Storage.Mongo/StellaOps.Concelier.Storage.Mongo.csproj" />
<ProjectReference Include="../../../__Libraries/StellaOps.Cryptography/StellaOps.Cryptography.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="Fixtures\*.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
</Project>

View File

@@ -1,8 +1,7 @@
using System;
using System.Collections.Immutable;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Text;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
@@ -11,17 +10,19 @@ using StellaOps.Concelier.Core.Events;
using StellaOps.Concelier.Models;
using StellaOps.Concelier.Storage.Mongo.Conflicts;
using StellaOps.Concelier.Storage.Mongo.Events;
using StellaOps.Concelier.Storage.Mongo.Statements;
using StellaOps.Concelier.Testing;
using Xunit;
using StellaOps.Concelier.Storage.Mongo.Statements;
using StellaOps.Concelier.Testing;
using StellaOps.Cryptography;
using Xunit;
namespace StellaOps.Concelier.Storage.Mongo.Tests;
[Collection("mongo-fixture")]
public sealed class MongoAdvisoryEventRepositoryTests
{
private readonly IMongoDatabase _database;
private readonly MongoAdvisoryEventRepository _repository;
public sealed class MongoAdvisoryEventRepositoryTests
{
private readonly IMongoDatabase _database;
private readonly MongoAdvisoryEventRepository _repository;
private static readonly ICryptoHash Hash = CryptoHashFactory.CreateDefault();
public MongoAdvisoryEventRepositoryTests(MongoIntegrationFixture fixture)
{
@@ -36,7 +37,8 @@ public sealed class MongoAdvisoryEventRepositoryTests
{
var advisory = CreateSampleAdvisory("CVE-2025-7777", "Sample advisory");
var canonicalJson = CanonicalJsonSerializer.Serialize(advisory);
var hash = ImmutableArray.Create(SHA256.HashData(Encoding.UTF8.GetBytes(canonicalJson)));
var digest = Hash.ComputeHash(Encoding.UTF8.GetBytes(canonicalJson), HashAlgorithms.Sha256);
var hash = ImmutableArray.Create(digest);
var entry = new AdvisoryStatementEntry(
Guid.NewGuid(),
@@ -62,7 +64,8 @@ public sealed class MongoAdvisoryEventRepositoryTests
public async Task InsertAndFetchConflicts_PreservesDetails()
{
var detailJson = CanonicalJsonSerializer.Serialize(new ConflictPayload("severity", "mismatch"));
var hash = ImmutableArray.Create(SHA256.HashData(Encoding.UTF8.GetBytes(detailJson)));
var digest = Hash.ComputeHash(Encoding.UTF8.GetBytes(detailJson), HashAlgorithms.Sha256);
var hash = ImmutableArray.Create(digest);
var statementIds = ImmutableArray.Create(Guid.NewGuid(), Guid.NewGuid());
var entry = new AdvisoryConflictEntry(

View File

@@ -12,5 +12,6 @@
<ProjectReference Include="../../__Libraries/StellaOps.Concelier.Core/StellaOps.Concelier.Core.csproj" />
<ProjectReference Include="../../__Libraries/StellaOps.Concelier.Models/StellaOps.Concelier.Models.csproj" />
<ProjectReference Include="../../__Libraries/StellaOps.Concelier.Storage.Mongo/StellaOps.Concelier.Storage.Mongo.csproj" />
<ProjectReference Include="../../../__Libraries/StellaOps.Cryptography/StellaOps.Cryptography.csproj" />
</ItemGroup>
</Project>
</Project>

View File

@@ -10,6 +10,7 @@
<ProjectReference Include="../../__Libraries/StellaOps.Concelier.Storage.Mongo/StellaOps.Concelier.Storage.Mongo.csproj" />
<ProjectReference Include="../../StellaOps.Concelier.WebService/StellaOps.Concelier.WebService.csproj" />
<ProjectReference Include="../../../__Libraries/StellaOps.Plugin/StellaOps.Plugin.csproj" />
<ProjectReference Include="../../../__Libraries/StellaOps.Cryptography/StellaOps.Cryptography.csproj" />
<ProjectReference Include="../../__Analyzers/StellaOps.Concelier.Merge.Analyzers/StellaOps.Concelier.Merge.Analyzers.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false" />

View File

@@ -9,7 +9,6 @@ using System.Net;
using System.Net.Http.Json;
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using Microsoft.AspNetCore.Builder;
@@ -44,6 +43,7 @@ using Microsoft.IdentityModel.Protocols;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using StellaOps.Concelier.WebService.Diagnostics;
using Microsoft.IdentityModel.Tokens;
using StellaOps.Cryptography;
namespace StellaOps.Concelier.WebService.Tests;
@@ -411,10 +411,11 @@ public sealed class WebServiceEndpointsTests : IAsyncLifetime
tenant: "tenant-verify-violations",
vendor: "osv",
upstreamId: "GHSA-VERIFY-ERR",
contentHash: string.Empty,
contentHash: "sha256:verify-err",
raw: new BsonDocument
{
{ "id", "GHSA-VERIFY-ERR" }
{ "id", "GHSA-VERIFY-ERR" },
{ "severity", "critical" }
}));
using var client = _factory.CreateClient();
@@ -1492,16 +1493,16 @@ public sealed class WebServiceEndpointsTests : IAsyncLifetime
}
private static readonly DateTimeOffset DefaultIngestTimestamp = new(2025, 1, 1, 0, 0, 0, TimeSpan.Zero);
private static readonly ICryptoHash Hash = CryptoHashFactory.CreateDefault();
private static string ComputeContentHash(BsonDocument rawDocument)
{
using var sha256 = SHA256.Create();
var canonical = rawDocument.ToJson(new JsonWriterSettings
{
OutputMode = JsonOutputMode.RelaxedExtendedJson
});
var bytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(canonical));
return $"sha256:{Convert.ToHexString(bytes).ToLowerInvariant()}";
var digest = Hash.ComputeHashHex(Encoding.UTF8.GetBytes(canonical), HashAlgorithms.Sha256);
return $"sha256:{digest}";
}
private static string ComputeDeterministicContentHash(string upstreamId)
@@ -1522,9 +1523,8 @@ public sealed class WebServiceEndpointsTests : IAsyncLifetime
return value.Trim();
}
using var sha256 = SHA256.Create();
var bytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(raw.GetRawText()));
return $"sha256:{Convert.ToHexString(bytes).ToLowerInvariant()}";
var digest = Hash.ComputeHashHex(Encoding.UTF8.GetBytes(raw.GetRawText()), HashAlgorithms.Sha256);
return $"sha256:{digest}";
}
private sealed record ReplayResponse(