Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
devportal-offline / build-offline (push) Has been cancelled
182 lines
6.8 KiB
C#
182 lines
6.8 KiB
C#
using System.Collections.Immutable;
|
|
using System.Globalization;
|
|
using Microsoft.Extensions.Logging;
|
|
using StellaOps.Concelier.Core.Linksets;
|
|
using StellaOps.Scanner.Surface.Env;
|
|
using StellaOps.Scanner.WebService.Contracts;
|
|
|
|
namespace StellaOps.Scanner.WebService.Services;
|
|
|
|
internal interface ILinksetResolver
|
|
{
|
|
Task<IReadOnlyList<LinksetSummaryDto>> ResolveAsync(IEnumerable<PolicyPreviewFindingDto>? findings, CancellationToken cancellationToken);
|
|
Task<IReadOnlyList<LinksetSummaryDto>> ResolveAsync(IEnumerable<StellaOps.Policy.PolicyVerdict> verdicts, CancellationToken cancellationToken);
|
|
Task<IReadOnlyList<LinksetSummaryDto>> ResolveByAdvisoryIdsAsync(IEnumerable<string> advisoryIds, CancellationToken cancellationToken);
|
|
}
|
|
|
|
internal sealed class LinksetResolver : ILinksetResolver
|
|
{
|
|
private readonly IAdvisoryLinksetQueryService _queryService;
|
|
private readonly ISurfaceEnvironment _surfaceEnvironment;
|
|
private readonly ILogger<LinksetResolver> _logger;
|
|
|
|
public LinksetResolver(
|
|
IAdvisoryLinksetQueryService queryService,
|
|
ISurfaceEnvironment surfaceEnvironment,
|
|
ILogger<LinksetResolver> logger)
|
|
{
|
|
_queryService = queryService ?? throw new ArgumentNullException(nameof(queryService));
|
|
_surfaceEnvironment = surfaceEnvironment ?? throw new ArgumentNullException(nameof(surfaceEnvironment));
|
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
|
}
|
|
|
|
public Task<IReadOnlyList<LinksetSummaryDto>> ResolveAsync(IEnumerable<PolicyPreviewFindingDto>? findings, CancellationToken cancellationToken)
|
|
{
|
|
var advisoryIds = findings?
|
|
.SelectMany(f => new[] { f?.Id, f?.Cve })
|
|
.Where(id => !string.IsNullOrWhiteSpace(id))
|
|
.Select(id => id!.Trim())
|
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
|
.ToArray() ?? Array.Empty<string>();
|
|
|
|
return ResolveInternalAsync(advisoryIds, cancellationToken);
|
|
}
|
|
|
|
public Task<IReadOnlyList<LinksetSummaryDto>> ResolveAsync(IEnumerable<StellaOps.Policy.PolicyVerdict> verdicts, CancellationToken cancellationToken)
|
|
{
|
|
var advisoryIds = verdicts?
|
|
.Where(v => !string.IsNullOrWhiteSpace(v.FindingId))
|
|
.Select(v => v.FindingId!.Trim())
|
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
|
.ToArray() ?? Array.Empty<string>();
|
|
|
|
return ResolveInternalAsync(advisoryIds, cancellationToken);
|
|
}
|
|
|
|
public Task<IReadOnlyList<LinksetSummaryDto>> ResolveByAdvisoryIdsAsync(IEnumerable<string> advisoryIds, CancellationToken cancellationToken)
|
|
{
|
|
var normalized = advisoryIds?
|
|
.Where(id => !string.IsNullOrWhiteSpace(id))
|
|
.Select(id => id.Trim())
|
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
|
.ToArray() ?? Array.Empty<string>();
|
|
|
|
return ResolveInternalAsync(normalized, cancellationToken);
|
|
}
|
|
|
|
private async Task<IReadOnlyList<LinksetSummaryDto>> ResolveInternalAsync(IReadOnlyList<string> advisoryIds, CancellationToken cancellationToken)
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
|
|
if (advisoryIds.Count == 0)
|
|
{
|
|
return Array.Empty<LinksetSummaryDto>();
|
|
}
|
|
|
|
var tenant = string.IsNullOrWhiteSpace(_surfaceEnvironment.Settings.Tenant)
|
|
? "default"
|
|
: _surfaceEnvironment.Settings.Tenant.Trim();
|
|
|
|
try
|
|
{
|
|
var options = new AdvisoryLinksetQueryOptions(tenant, advisoryIds, Sources: null, Limit: advisoryIds.Count);
|
|
var result = await _queryService.QueryAsync(options, cancellationToken).ConfigureAwait(false);
|
|
|
|
if (result.Linksets.IsDefaultOrEmpty)
|
|
{
|
|
return Array.Empty<LinksetSummaryDto>();
|
|
}
|
|
|
|
return result.Linksets
|
|
.Select(MapSummary)
|
|
.OrderBy(ls => ls.AdvisoryId, StringComparer.Ordinal)
|
|
.ToArray();
|
|
}
|
|
catch (Exception ex) when (!cancellationToken.IsCancellationRequested)
|
|
{
|
|
_logger.LogWarning(ex, "Failed to resolve linksets for {Count} advisories (tenant={Tenant}).", advisoryIds.Count, tenant);
|
|
return Array.Empty<LinksetSummaryDto>();
|
|
}
|
|
}
|
|
|
|
private static LinksetSummaryDto MapSummary(AdvisoryLinkset linkset)
|
|
{
|
|
var severities = linkset.Normalized?.Severities?.Select(MapSeverity).ToArray();
|
|
var conflicts = linkset.Conflicts?.Select(MapConflict).ToArray();
|
|
|
|
return new LinksetSummaryDto
|
|
{
|
|
AdvisoryId = linkset.AdvisoryId,
|
|
Source = linkset.Source,
|
|
Confidence = linkset.Confidence,
|
|
ObservationIds = linkset.ObservationIds.Length > 0 ? linkset.ObservationIds : null,
|
|
References = null,
|
|
Severities = severities?.Length > 0 ? severities : null,
|
|
Conflicts = conflicts?.Length > 0 ? conflicts : null
|
|
};
|
|
}
|
|
|
|
private static LinksetSeverityDto MapSeverity(Dictionary<string, object?> payload)
|
|
{
|
|
payload ??= new Dictionary<string, object?>(StringComparer.Ordinal);
|
|
|
|
string? GetString(string key)
|
|
=> payload.TryGetValue(key, out var value) ? value?.ToString() : null;
|
|
|
|
double? GetDouble(string key)
|
|
{
|
|
if (!payload.TryGetValue(key, out var value) || value is null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
if (value is double d)
|
|
{
|
|
return d;
|
|
}
|
|
|
|
if (value is float f)
|
|
{
|
|
return Convert.ToDouble(f, CultureInfo.InvariantCulture);
|
|
}
|
|
|
|
if (double.TryParse(value.ToString(), NumberStyles.Float, CultureInfo.InvariantCulture, out var parsed))
|
|
{
|
|
return parsed;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
var labels = payload.TryGetValue("labels", out var labelsValue) && labelsValue is Dictionary<string, object?> labelsDict
|
|
? labelsDict.ToDictionary(kv => kv.Key, kv => kv.Value?.ToString() ?? string.Empty, StringComparer.Ordinal)
|
|
: null;
|
|
|
|
var raw = payload.Count == 0
|
|
? null
|
|
: payload.ToDictionary(kv => kv.Key, kv => kv.Value, StringComparer.Ordinal);
|
|
|
|
return new LinksetSeverityDto
|
|
{
|
|
Source = GetString("source"),
|
|
Type = GetString("type"),
|
|
Score = GetDouble("score"),
|
|
Vector = GetString("vector"),
|
|
Origin = GetString("origin"),
|
|
Labels = labels,
|
|
Raw = raw
|
|
};
|
|
}
|
|
|
|
private static LinksetConflictDto MapConflict(AdvisoryLinksetConflict conflict)
|
|
{
|
|
return new LinksetConflictDto
|
|
{
|
|
Field = conflict.Field,
|
|
Reason = conflict.Reason,
|
|
Values = conflict.Values,
|
|
SourceIds = conflict.SourceIds
|
|
};
|
|
}
|
|
}
|