up
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
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
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
using System.Net.Http.Json;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using StellaOps.Concelier.Core.Linksets;
|
||||
using StellaOps.Scanner.WebService.Options;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace StellaOps.Scanner.WebService.Services;
|
||||
|
||||
internal sealed class ConcelierHttpLinksetQueryService : IAdvisoryLinksetQueryService
|
||||
{
|
||||
private readonly HttpClient _client;
|
||||
private readonly ConcelierLinksetOptions _options;
|
||||
|
||||
private static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.Web)
|
||||
{
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||
PropertyNameCaseInsensitive = true
|
||||
};
|
||||
|
||||
public ConcelierHttpLinksetQueryService(HttpClient client, IOptions<ConcelierLinksetOptions> options)
|
||||
{
|
||||
_client = client ?? throw new ArgumentNullException(nameof(client));
|
||||
_options = options?.Value ?? throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
public async Task<AdvisoryLinksetQueryResult> QueryAsync(AdvisoryLinksetQueryOptions options, CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (options.AdvisoryIds is null)
|
||||
{
|
||||
return new AdvisoryLinksetQueryResult(ImmutableArray<AdvisoryLinkset>.Empty, null, false);
|
||||
}
|
||||
|
||||
var results = ImmutableArray.CreateBuilder<AdvisoryLinkset>();
|
||||
foreach (var advisoryId in options.AdvisoryIds)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(advisoryId))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var path = $"/v1/lnm/linksets/{Uri.EscapeDataString(advisoryId)}?tenant={Uri.EscapeDataString(options.Tenant)}&includeConflicts=true&includeObservations=false&includeTimeline=false";
|
||||
try
|
||||
{
|
||||
using var response = await _client.GetAsync(path, cancellationToken).ConfigureAwait(false);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var payload = await response.Content.ReadFromJsonAsync<LinksetDetailResponse>(SerializerOptions, cancellationToken).ConfigureAwait(false);
|
||||
if (payload?.Linksets is null || payload.Linksets.Length == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var linkset in payload.Linksets)
|
||||
{
|
||||
results.Add(Map(linkset));
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// swallow and continue; caller will see partial results
|
||||
}
|
||||
}
|
||||
|
||||
var linksets = results.ToImmutable();
|
||||
return new AdvisoryLinksetQueryResult(linksets, null, false);
|
||||
}
|
||||
|
||||
private static AdvisoryLinkset Map(LinksetDto dto)
|
||||
{
|
||||
var normalized = dto.Normalized is null
|
||||
? null
|
||||
: new AdvisoryLinksetNormalized(
|
||||
dto.Normalized.Purls,
|
||||
dto.Normalized.Cpes,
|
||||
dto.Normalized.Versions,
|
||||
dto.Normalized.Ranges,
|
||||
dto.Normalized.Severities);
|
||||
|
||||
var conflicts = dto.Conflicts is null
|
||||
? null
|
||||
: dto.Conflicts.Select(c => new AdvisoryLinksetConflict(c.Field, c.Reason ?? string.Empty, c.Values, c.SourceIds)).ToList();
|
||||
|
||||
return new AdvisoryLinkset(
|
||||
TenantId: dto.Tenant ?? string.Empty,
|
||||
Source: dto.Source ?? string.Empty,
|
||||
AdvisoryId: dto.AdvisoryId ?? string.Empty,
|
||||
ObservationIds: dto.ObservationIds?.ToImmutableArray() ?? ImmutableArray<string>.Empty,
|
||||
Normalized: normalized,
|
||||
Provenance: null,
|
||||
Confidence: dto.Confidence,
|
||||
Conflicts: conflicts,
|
||||
CreatedAt: dto.CreatedAt ?? DateTimeOffset.MinValue,
|
||||
BuiltByJobId: dto.BuiltByJobId);
|
||||
}
|
||||
|
||||
private sealed record LinksetDetailResponse([property: JsonPropertyName("linksets")] LinksetDto[] Linksets);
|
||||
|
||||
private sealed record LinksetDto
|
||||
{
|
||||
[JsonPropertyName("advisoryId")]
|
||||
public string? AdvisoryId { get; init; }
|
||||
|
||||
[JsonPropertyName("source")]
|
||||
public string? Source { get; init; }
|
||||
|
||||
[JsonPropertyName("tenant")]
|
||||
public string? Tenant { get; init; }
|
||||
|
||||
[JsonPropertyName("confidence")]
|
||||
public double? Confidence { get; init; }
|
||||
|
||||
[JsonPropertyName("createdAt")]
|
||||
public DateTimeOffset? CreatedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("builtByJobId")]
|
||||
public string? BuiltByJobId { get; init; }
|
||||
|
||||
[JsonPropertyName("observationIds")]
|
||||
public string[]? ObservationIds { get; init; }
|
||||
|
||||
[JsonPropertyName("normalized")]
|
||||
public LinksetNormalizedDto? Normalized { get; init; }
|
||||
|
||||
[JsonPropertyName("conflicts")]
|
||||
public LinksetConflictDto[]? Conflicts { get; init; }
|
||||
}
|
||||
|
||||
private sealed record LinksetNormalizedDto
|
||||
{
|
||||
[JsonPropertyName("purls")]
|
||||
public IReadOnlyList<string>? Purls { get; init; }
|
||||
|
||||
[JsonPropertyName("cpes")]
|
||||
public IReadOnlyList<string>? Cpes { get; init; }
|
||||
|
||||
[JsonPropertyName("versions")]
|
||||
public IReadOnlyList<string>? Versions { get; init; }
|
||||
|
||||
[JsonPropertyName("ranges")]
|
||||
public IReadOnlyList<Dictionary<string, object?>>? Ranges { get; init; }
|
||||
|
||||
[JsonPropertyName("severities")]
|
||||
public IReadOnlyList<Dictionary<string, object?>>? Severities { get; init; }
|
||||
}
|
||||
|
||||
private sealed record LinksetConflictDto
|
||||
{
|
||||
[JsonPropertyName("field")]
|
||||
public string Field { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("reason")]
|
||||
public string? Reason { get; init; }
|
||||
|
||||
[JsonPropertyName("values")]
|
||||
public IReadOnlyList<string>? Values { get; init; }
|
||||
|
||||
[JsonPropertyName("sourceIds")]
|
||||
public IReadOnlyList<string>? SourceIds { get; init; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
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
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using System.Collections.Immutable;
|
||||
using StellaOps.Concelier.Core.Linksets;
|
||||
|
||||
namespace StellaOps.Scanner.WebService.Services;
|
||||
|
||||
internal sealed class NullAdvisoryLinksetQueryService : IAdvisoryLinksetQueryService
|
||||
{
|
||||
public Task<AdvisoryLinksetQueryResult> QueryAsync(AdvisoryLinksetQueryOptions options, CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
return Task.FromResult(new AdvisoryLinksetQueryResult(ImmutableArray<AdvisoryLinkset>.Empty, null, false));
|
||||
}
|
||||
}
|
||||
@@ -7,13 +7,14 @@ using System.Linq;
|
||||
using System.Globalization;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Policy;
|
||||
using StellaOps.Scanner.Storage.Catalog;
|
||||
using StellaOps.Scanner.Storage.Repositories;
|
||||
using StellaOps.Scanner.WebService.Options;
|
||||
using StellaOps.Zastava.Core.Contracts;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Policy;
|
||||
using StellaOps.Scanner.Storage.Catalog;
|
||||
using StellaOps.Scanner.Storage.Repositories;
|
||||
using StellaOps.Scanner.WebService.Contracts;
|
||||
using StellaOps.Scanner.WebService.Options;
|
||||
using StellaOps.Zastava.Core.Contracts;
|
||||
using RuntimePolicyVerdict = StellaOps.Zastava.Core.Contracts.PolicyVerdict;
|
||||
using CanonicalPolicyVerdict = StellaOps.Policy.PolicyVerdict;
|
||||
using CanonicalPolicyVerdictStatus = StellaOps.Policy.PolicyVerdictStatus;
|
||||
@@ -35,35 +36,38 @@ internal sealed class RuntimePolicyService : IRuntimePolicyService
|
||||
|
||||
private readonly LinkRepository _linkRepository;
|
||||
private readonly ArtifactRepository _artifactRepository;
|
||||
private readonly RuntimeEventRepository _runtimeEventRepository;
|
||||
private readonly PolicySnapshotStore _policySnapshotStore;
|
||||
private readonly PolicyPreviewService _policyPreviewService;
|
||||
private readonly IOptionsMonitor<ScannerWebServiceOptions> _optionsMonitor;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
private readonly IRuntimeAttestationVerifier _attestationVerifier;
|
||||
private readonly ILogger<RuntimePolicyService> _logger;
|
||||
private readonly RuntimeEventRepository _runtimeEventRepository;
|
||||
private readonly PolicySnapshotStore _policySnapshotStore;
|
||||
private readonly PolicyPreviewService _policyPreviewService;
|
||||
private readonly ILinksetResolver _linksetResolver;
|
||||
private readonly IOptionsMonitor<ScannerWebServiceOptions> _optionsMonitor;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
private readonly IRuntimeAttestationVerifier _attestationVerifier;
|
||||
private readonly ILogger<RuntimePolicyService> _logger;
|
||||
|
||||
public RuntimePolicyService(
|
||||
LinkRepository linkRepository,
|
||||
ArtifactRepository artifactRepository,
|
||||
RuntimeEventRepository runtimeEventRepository,
|
||||
PolicySnapshotStore policySnapshotStore,
|
||||
PolicyPreviewService policyPreviewService,
|
||||
IOptionsMonitor<ScannerWebServiceOptions> optionsMonitor,
|
||||
TimeProvider timeProvider,
|
||||
IRuntimeAttestationVerifier attestationVerifier,
|
||||
ILogger<RuntimePolicyService> logger)
|
||||
{
|
||||
_linkRepository = linkRepository ?? throw new ArgumentNullException(nameof(linkRepository));
|
||||
_artifactRepository = artifactRepository ?? throw new ArgumentNullException(nameof(artifactRepository));
|
||||
_runtimeEventRepository = runtimeEventRepository ?? throw new ArgumentNullException(nameof(runtimeEventRepository));
|
||||
_policySnapshotStore = policySnapshotStore ?? throw new ArgumentNullException(nameof(policySnapshotStore));
|
||||
_policyPreviewService = policyPreviewService ?? throw new ArgumentNullException(nameof(policyPreviewService));
|
||||
_optionsMonitor = optionsMonitor ?? throw new ArgumentNullException(nameof(optionsMonitor));
|
||||
_timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider));
|
||||
_attestationVerifier = attestationVerifier ?? throw new ArgumentNullException(nameof(attestationVerifier));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
LinkRepository linkRepository,
|
||||
ArtifactRepository artifactRepository,
|
||||
RuntimeEventRepository runtimeEventRepository,
|
||||
PolicySnapshotStore policySnapshotStore,
|
||||
PolicyPreviewService policyPreviewService,
|
||||
ILinksetResolver linksetResolver,
|
||||
IOptionsMonitor<ScannerWebServiceOptions> optionsMonitor,
|
||||
TimeProvider timeProvider,
|
||||
IRuntimeAttestationVerifier attestationVerifier,
|
||||
ILogger<RuntimePolicyService> logger)
|
||||
{
|
||||
_linkRepository = linkRepository ?? throw new ArgumentNullException(nameof(linkRepository));
|
||||
_artifactRepository = artifactRepository ?? throw new ArgumentNullException(nameof(artifactRepository));
|
||||
_runtimeEventRepository = runtimeEventRepository ?? throw new ArgumentNullException(nameof(runtimeEventRepository));
|
||||
_policySnapshotStore = policySnapshotStore ?? throw new ArgumentNullException(nameof(policySnapshotStore));
|
||||
_policyPreviewService = policyPreviewService ?? throw new ArgumentNullException(nameof(policyPreviewService));
|
||||
_linksetResolver = linksetResolver ?? throw new ArgumentNullException(nameof(linksetResolver));
|
||||
_optionsMonitor = optionsMonitor ?? throw new ArgumentNullException(nameof(optionsMonitor));
|
||||
_timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider));
|
||||
_attestationVerifier = attestationVerifier ?? throw new ArgumentNullException(nameof(attestationVerifier));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
public async Task<RuntimePolicyEvaluationResult> EvaluateAsync(RuntimePolicyEvaluationRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -118,13 +122,14 @@ internal sealed class RuntimePolicyService : IRuntimePolicyService
|
||||
heuristicReasons.Add("policy.snapshot.missing");
|
||||
}
|
||||
|
||||
ImmutableArray<CanonicalPolicyVerdict> projectedVerdicts = ImmutableArray<CanonicalPolicyVerdict>.Empty;
|
||||
ImmutableArray<PolicyIssue> issues = ImmutableArray<PolicyIssue>.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
if (!findings.IsDefaultOrEmpty && findings.Length > 0)
|
||||
{
|
||||
ImmutableArray<CanonicalPolicyVerdict> projectedVerdicts = ImmutableArray<CanonicalPolicyVerdict>.Empty;
|
||||
ImmutableArray<PolicyIssue> issues = ImmutableArray<PolicyIssue>.Empty;
|
||||
IReadOnlyList<LinksetSummaryDto> linksets = Array.Empty<LinksetSummaryDto>();
|
||||
|
||||
try
|
||||
{
|
||||
if (!findings.IsDefaultOrEmpty && findings.Length > 0)
|
||||
{
|
||||
var previewRequest = new PolicyPreviewRequest(
|
||||
image,
|
||||
findings,
|
||||
@@ -133,14 +138,15 @@ internal sealed class RuntimePolicyService : IRuntimePolicyService
|
||||
ProposedPolicy: null);
|
||||
|
||||
var preview = await _policyPreviewService.PreviewAsync(previewRequest, cancellationToken).ConfigureAwait(false);
|
||||
issues = preview.Issues;
|
||||
if (!preview.Diffs.IsDefaultOrEmpty)
|
||||
{
|
||||
projectedVerdicts = preview.Diffs.Select(diff => diff.Projected).ToImmutableArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex) when (!cancellationToken.IsCancellationRequested)
|
||||
issues = preview.Issues;
|
||||
if (!preview.Diffs.IsDefaultOrEmpty)
|
||||
{
|
||||
projectedVerdicts = preview.Diffs.Select(diff => diff.Projected).ToImmutableArray();
|
||||
linksets = await _linksetResolver.ResolveAsync(projectedVerdicts, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex) when (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
_logger.LogWarning(ex, "Runtime policy preview failed for image {ImageDigest}; falling back to heuristic evaluation.", image);
|
||||
}
|
||||
@@ -151,12 +157,13 @@ internal sealed class RuntimePolicyService : IRuntimePolicyService
|
||||
var decision = await BuildDecisionAsync(
|
||||
image,
|
||||
metadata,
|
||||
heuristicReasons,
|
||||
projectedVerdicts,
|
||||
issues,
|
||||
policyDigest,
|
||||
buildIdObservation?.BuildIds,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
heuristicReasons,
|
||||
projectedVerdicts,
|
||||
issues,
|
||||
policyDigest,
|
||||
linksets,
|
||||
buildIdObservation?.BuildIds,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
results[image] = decision;
|
||||
|
||||
@@ -279,12 +286,13 @@ internal sealed class RuntimePolicyService : IRuntimePolicyService
|
||||
private async Task<RuntimePolicyImageDecision> BuildDecisionAsync(
|
||||
string imageDigest,
|
||||
RuntimeImageMetadata metadata,
|
||||
List<string> heuristicReasons,
|
||||
ImmutableArray<CanonicalPolicyVerdict> projectedVerdicts,
|
||||
ImmutableArray<PolicyIssue> issues,
|
||||
string? policyDigest,
|
||||
IReadOnlyList<string>? buildIds,
|
||||
CancellationToken cancellationToken)
|
||||
List<string> heuristicReasons,
|
||||
ImmutableArray<CanonicalPolicyVerdict> projectedVerdicts,
|
||||
ImmutableArray<PolicyIssue> issues,
|
||||
string? policyDigest,
|
||||
IReadOnlyList<LinksetSummaryDto> linksets,
|
||||
IReadOnlyList<string>? buildIds,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var reasons = new List<string>(heuristicReasons);
|
||||
|
||||
@@ -330,18 +338,19 @@ internal sealed class RuntimePolicyService : IRuntimePolicyService
|
||||
.Distinct(StringComparer.Ordinal)
|
||||
.ToArray();
|
||||
|
||||
return new RuntimePolicyImageDecision(
|
||||
overallVerdict,
|
||||
metadata.Signed,
|
||||
metadata.HasSbomReferrers,
|
||||
normalizedReasons,
|
||||
rekor,
|
||||
metadataPayload,
|
||||
confidence,
|
||||
quieted,
|
||||
quietedBy,
|
||||
buildIds);
|
||||
}
|
||||
return new RuntimePolicyImageDecision(
|
||||
overallVerdict,
|
||||
metadata.Signed,
|
||||
metadata.HasSbomReferrers,
|
||||
normalizedReasons,
|
||||
rekor,
|
||||
metadataPayload,
|
||||
confidence,
|
||||
quieted,
|
||||
quietedBy,
|
||||
buildIds,
|
||||
linksets);
|
||||
}
|
||||
|
||||
private RuntimePolicyVerdict MapVerdict(ImmutableArray<CanonicalPolicyVerdict> projectedVerdicts, IReadOnlyList<string> heuristicReasons)
|
||||
{
|
||||
@@ -501,17 +510,18 @@ internal sealed record RuntimePolicyEvaluationResult(
|
||||
string? PolicyRevision,
|
||||
IReadOnlyDictionary<string, RuntimePolicyImageDecision> Results);
|
||||
|
||||
internal sealed record RuntimePolicyImageDecision(
|
||||
RuntimePolicyVerdict PolicyVerdict,
|
||||
bool Signed,
|
||||
bool HasSbomReferrers,
|
||||
IReadOnlyList<string> Reasons,
|
||||
RuntimePolicyRekorReference? Rekor,
|
||||
IDictionary<string, object?>? Metadata,
|
||||
double Confidence,
|
||||
bool Quieted,
|
||||
string? QuietedBy,
|
||||
IReadOnlyList<string>? BuildIds);
|
||||
internal sealed record RuntimePolicyImageDecision(
|
||||
RuntimePolicyVerdict PolicyVerdict,
|
||||
bool Signed,
|
||||
bool HasSbomReferrers,
|
||||
IReadOnlyList<string> Reasons,
|
||||
RuntimePolicyRekorReference? Rekor,
|
||||
IDictionary<string, object?>? Metadata,
|
||||
double Confidence,
|
||||
bool Quieted,
|
||||
string? QuietedBy,
|
||||
IReadOnlyList<string>? BuildIds,
|
||||
IReadOnlyList<LinksetSummaryDto> Linksets);
|
||||
|
||||
internal sealed record RuntimePolicyRekorReference(string? Uuid, string? Url, bool? Verified);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user