Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 45 additions & 10 deletions src/DockerUpdateGuard/Images/VulnerabilityEnrichmentService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,33 @@ private static List<VulnerabilityAdvisoryData> DeduplicateAdvisories(IReadOnlyLi
.ToList();
}

/// <summary>
/// Select the finding that represents a finding identity, preferring the active row and otherwise the most recently resolved one
/// </summary>
/// <param name="findings">Findings sharing the same identity key</param>
/// <returns>Representative finding</returns>
private static VulnerabilityFinding SelectRepresentativeFinding(IEnumerable<VulnerabilityFinding> findings)
{
return findings.OrderByDescending(entity => entity.IsActive)
.ThenByDescending(entity => entity.ResolvedAtUtc)
.ThenBy(entity => entity.DetectedAtUtc)
.First();
}

/// <summary>
/// Reactivate a resolved finding because its advisory was reported again
/// </summary>
/// <param name="finding">Finding to reactivate</param>
/// <param name="scanRun">Scan run that reported the advisory again</param>
private static void ReactivateFinding(VulnerabilityFinding finding, ScanRun scanRun)
{
finding.IsActive = true;
finding.ResolvedAtUtc = null;
finding.ResolvedByScanRunId = null;
finding.ScanRunId = scanRun.Id;
finding.DetectedAtUtc = DateTimeOffset.UtcNow;
}

/// <summary>
/// Copy the mutable advisory values onto a finding
/// </summary>
Expand Down Expand Up @@ -182,22 +209,30 @@ private async Task<int> UpsertVulnerabilityFindingsAsync(ScanRun scanRun,
IReadOnlyList<VulnerabilityAdvisoryData> advisories,
CancellationToken cancellationToken)
{
var activeFindings = await _dbContext.VulnerabilityFindings.Where(entity => entity.ImageVersionId == image.Id && entity.IsActive)
.ToListAsync(cancellationToken)
.ConfigureAwait(false);

var activeFindingLookup = activeFindings.GroupBy(entity => CreateFindingKey(entity.AdvisoryId, entity.AffectedPackage))
.ToDictionary(findingGroup => findingGroup.Key,
findingGroup => findingGroup.OrderBy(entity => entity.DetectedAtUtc)
.First());
var existingFindings = await _dbContext.VulnerabilityFindings.Where(entity => entity.ImageVersionId == image.Id)
.ToListAsync(cancellationToken)
.ConfigureAwait(false);

// Rows that were active before this run are the only candidates for the stale pass below
var previouslyActiveFindings = existingFindings.Where(entity => entity.IsActive)
.ToList();

var findingLookup = existingFindings.GroupBy(entity => CreateFindingKey(entity.AdvisoryId, entity.AffectedPackage))
.ToDictionary(findingGroup => findingGroup.Key,
SelectRepresentativeFinding);
var source = MapSource(_optionsMonitor.CurrentValue.Vulnerabilities.Provider);
var dedupedAdvisories = DeduplicateAdvisories(advisories);
var matchedFindingIds = new HashSet<Guid>();

foreach (var advisory in dedupedAdvisories)
{
if (activeFindingLookup.TryGetValue(CreateFindingKey(advisory.AdvisoryId, advisory.AffectedPackage), out var existingFinding))
if (findingLookup.TryGetValue(CreateFindingKey(advisory.AdvisoryId, advisory.AffectedPackage), out var existingFinding))
{
if (existingFinding.IsActive == false)
{
ReactivateFinding(existingFinding, scanRun);
}

UpdateFindingFromAdvisory(existingFinding, advisory, source);
matchedFindingIds.Add(existingFinding.Id);

Expand All @@ -216,7 +251,7 @@ private async Task<int> UpsertVulnerabilityFindingsAsync(ScanRun scanRun,
_dbContext.VulnerabilityFindings.Add(newFinding);
}

foreach (var staleFinding in activeFindings.Where(entity => matchedFindingIds.Contains(entity.Id) == false))
foreach (var staleFinding in previouslyActiveFindings.Where(entity => matchedFindingIds.Contains(entity.Id) == false))
{
staleFinding.IsActive = false;
staleFinding.ResolvedAtUtc = DateTimeOffset.UtcNow;
Expand Down
Loading
Loading