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:
@@ -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"))
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user