sprints enhancements

This commit is contained in:
StellaOps Bot
2025-12-25 19:52:30 +02:00
parent ef6ac36323
commit b8b2d83f4a
138 changed files with 25133 additions and 594 deletions

View File

@@ -20,8 +20,7 @@ using StellaOps.Concelier.Connector.Osv.Configuration;
using StellaOps.Concelier.Connector.Osv.Internal;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage.Advisories;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Core.Canonical;
using StellaOps.Plugin;
using StellaOps.Cryptography;
@@ -41,6 +40,7 @@ public sealed class OsvConnector : IFeedConnector
private readonly IDtoStore _dtoStore;
private readonly IAdvisoryStore _advisoryStore;
private readonly ISourceStateRepository _stateRepository;
private readonly ICanonicalAdvisoryService? _canonicalService;
private readonly OsvOptions _options;
private readonly TimeProvider _timeProvider;
private readonly ILogger<OsvConnector> _logger;
@@ -58,7 +58,8 @@ public sealed class OsvConnector : IFeedConnector
OsvDiagnostics diagnostics,
ICryptoHash hash,
TimeProvider? timeProvider,
ILogger<OsvConnector> logger)
ILogger<OsvConnector> logger,
ICanonicalAdvisoryService? canonicalService = null)
{
_httpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory));
_rawDocumentStorage = rawDocumentStorage ?? throw new ArgumentNullException(nameof(rawDocumentStorage));
@@ -66,6 +67,7 @@ public sealed class OsvConnector : IFeedConnector
_dtoStore = dtoStore ?? throw new ArgumentNullException(nameof(dtoStore));
_advisoryStore = advisoryStore ?? throw new ArgumentNullException(nameof(advisoryStore));
_stateRepository = stateRepository ?? throw new ArgumentNullException(nameof(stateRepository));
_canonicalService = canonicalService; // Optional - canonical ingest
_options = (options ?? throw new ArgumentNullException(nameof(options))).Value ?? throw new ArgumentNullException(nameof(options));
_diagnostics = diagnostics ?? throw new ArgumentNullException(nameof(diagnostics));
_hash = hash ?? throw new ArgumentNullException(nameof(hash));
@@ -287,6 +289,12 @@ public sealed class OsvConnector : IFeedConnector
await _advisoryStore.UpsertAsync(advisory, cancellationToken).ConfigureAwait(false);
await _documentStore.UpdateStatusAsync(document.Id, DocumentStatuses.Mapped, cancellationToken).ConfigureAwait(false);
// Ingest to canonical advisory service if available
if (_canonicalService is not null)
{
await IngestToCanonicalAsync(osvDto, advisory, payloadJson, document.FetchedAt, cancellationToken).ConfigureAwait(false);
}
pendingMappings.Remove(documentId);
}
@@ -518,4 +526,91 @@ public sealed class OsvConnector : IFeedConnector
var safeId = vulnerabilityId.Replace(' ', '-');
return $"https://osv-vulnerabilities.storage.googleapis.com/{ecosystem}/{safeId}.json";
}
/// <summary>
/// Ingests OSV advisory to canonical advisory service for deduplication.
/// Creates one RawAdvisory per affected package.
/// </summary>
private async Task IngestToCanonicalAsync(
OsvVulnerabilityDto dto,
Advisory advisory,
string rawPayloadJson,
DateTimeOffset fetchedAt,
CancellationToken cancellationToken)
{
if (_canonicalService is null || dto.Affected is null || dto.Affected.Count == 0)
{
return;
}
// Find primary CVE from aliases
var cve = advisory.Aliases
.FirstOrDefault(a => a.StartsWith("CVE-", StringComparison.OrdinalIgnoreCase))
?? dto.Id; // Fall back to OSV ID if no CVE
// Extract CWE weaknesses
var weaknesses = advisory.Cwes
.Where(w => w.Identifier.StartsWith("CWE-", StringComparison.OrdinalIgnoreCase))
.Select(w => w.Identifier)
.ToList();
// Create one RawAdvisory per affected package
foreach (var affected in advisory.AffectedPackages)
{
if (string.IsNullOrWhiteSpace(affected.Identifier))
{
continue;
}
// Build version range JSON
string? versionRangeJson = null;
if (affected.VersionRanges.Length > 0)
{
var firstRange = affected.VersionRanges[0];
var rangeObj = new
{
introduced = firstRange.IntroducedVersion,
@fixed = firstRange.FixedVersion,
last_affected = firstRange.LastAffectedVersion
};
versionRangeJson = JsonSerializer.Serialize(rangeObj, SerializerOptions);
}
var rawAdvisory = new RawAdvisory
{
SourceAdvisoryId = dto.Id,
Cve = cve,
AffectsKey = affected.Identifier,
VersionRangeJson = versionRangeJson,
Weaknesses = weaknesses,
PatchLineage = null, // OSV doesn't have patch lineage
Severity = advisory.Severity,
Title = advisory.Title,
Summary = advisory.Summary,
VendorStatus = VendorStatus.Affected,
RawPayloadJson = rawPayloadJson,
FetchedAt = fetchedAt
};
try
{
var result = await _canonicalService.IngestAsync(SourceName, rawAdvisory, cancellationToken).ConfigureAwait(false);
if (_logger.IsEnabled(LogLevel.Debug))
{
_logger.LogDebug(
"Canonical ingest for {OsvId}/{AffectsKey}: {Decision} (canonical={CanonicalId})",
dto.Id, affected.Identifier, result.Decision, result.CanonicalId);
}
}
catch (Exception ex)
{
_logger.LogWarning(
ex,
"Failed to ingest {OsvId}/{AffectsKey} to canonical service",
dto.Id, affected.Identifier);
// Don't fail the mapping operation for canonical ingest failures
}
}
}
}