132 lines
4.5 KiB
C#
132 lines
4.5 KiB
C#
|
|
using StellaOps.Excititor.Core;
|
|
using StellaOps.Excititor.Core.Storage;
|
|
using StellaOps.Excititor.WebService.Contracts;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.Immutable;
|
|
using System.Globalization;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace StellaOps.Excititor.WebService.Services;
|
|
|
|
internal interface IVexEvidenceChunkService
|
|
{
|
|
Task<VexEvidenceChunkResult> QueryAsync(VexEvidenceChunkRequest request, CancellationToken cancellationToken);
|
|
}
|
|
|
|
internal sealed record VexEvidenceChunkRequest(
|
|
string Tenant,
|
|
string VulnerabilityId,
|
|
string ProductKey,
|
|
ImmutableHashSet<string> ProviderIds,
|
|
ImmutableHashSet<VexClaimStatus> Statuses,
|
|
DateTimeOffset? Since,
|
|
int Limit);
|
|
|
|
internal sealed record VexEvidenceChunkResult(
|
|
IReadOnlyList<VexEvidenceChunkResponse> Chunks,
|
|
bool Truncated,
|
|
int TotalCount,
|
|
DateTimeOffset GeneratedAtUtc);
|
|
|
|
internal sealed class VexEvidenceChunkService : IVexEvidenceChunkService
|
|
{
|
|
private readonly IVexClaimStore _claimStore;
|
|
private readonly TimeProvider _timeProvider;
|
|
|
|
public VexEvidenceChunkService(IVexClaimStore claimStore, TimeProvider timeProvider)
|
|
{
|
|
_claimStore = claimStore ?? throw new ArgumentNullException(nameof(claimStore));
|
|
_timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider));
|
|
}
|
|
|
|
public async Task<VexEvidenceChunkResult> QueryAsync(VexEvidenceChunkRequest request, CancellationToken cancellationToken)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(request);
|
|
|
|
var claims = await _claimStore
|
|
.FindAsync(request.VulnerabilityId, request.ProductKey, request.Since, cancellationToken)
|
|
.ConfigureAwait(false);
|
|
|
|
var filtered = claims
|
|
.Where(claim => MatchesProvider(claim, request.ProviderIds))
|
|
.Where(claim => MatchesStatus(claim, request.Statuses))
|
|
.OrderByDescending(claim => claim.LastSeen)
|
|
.ToList();
|
|
|
|
var total = filtered.Count;
|
|
if (filtered.Count > request.Limit)
|
|
{
|
|
filtered = filtered.Take(request.Limit).ToList();
|
|
}
|
|
|
|
var chunks = filtered
|
|
.Select(MapChunk)
|
|
.ToList();
|
|
|
|
return new VexEvidenceChunkResult(
|
|
chunks,
|
|
total > request.Limit,
|
|
total,
|
|
_timeProvider.GetUtcNow());
|
|
}
|
|
|
|
private static bool MatchesProvider(VexClaim claim, ImmutableHashSet<string> providers)
|
|
=> providers.Count == 0 || providers.Contains(claim.ProviderId, StringComparer.OrdinalIgnoreCase);
|
|
|
|
private static bool MatchesStatus(VexClaim claim, ImmutableHashSet<VexClaimStatus> statuses)
|
|
=> statuses.Count == 0 || statuses.Contains(claim.Status);
|
|
|
|
private static VexEvidenceChunkResponse MapChunk(VexClaim claim)
|
|
{
|
|
var observationId = string.Create(CultureInfo.InvariantCulture, $"{claim.ProviderId}:{claim.Document.Digest}");
|
|
var linksetId = string.Create(CultureInfo.InvariantCulture, $"{claim.VulnerabilityId}:{claim.Product.Key}");
|
|
|
|
var scope = new VexEvidenceChunkScope(
|
|
claim.Product.Key,
|
|
claim.Product.Name,
|
|
claim.Product.Version,
|
|
claim.Product.Purl,
|
|
claim.Product.Cpe,
|
|
claim.Product.ComponentIdentifiers);
|
|
|
|
var document = new VexEvidenceChunkDocument(
|
|
claim.Document.Digest,
|
|
claim.Document.Format.ToString().ToLowerInvariant(),
|
|
claim.Document.SourceUri.ToString(),
|
|
claim.Document.Revision);
|
|
|
|
var signature = claim.Document.Signature is null
|
|
? null
|
|
: new VexEvidenceChunkSignature(
|
|
claim.Document.Signature.Type,
|
|
claim.Document.Signature.Subject,
|
|
claim.Document.Signature.Issuer,
|
|
claim.Document.Signature.KeyId,
|
|
claim.Document.Signature.VerifiedAt,
|
|
claim.Document.Signature.TransparencyLogReference);
|
|
|
|
var scopeScore = claim.Confidence?.Score ?? claim.Signals?.Severity?.Score;
|
|
|
|
return new VexEvidenceChunkResponse(
|
|
observationId,
|
|
linksetId,
|
|
claim.VulnerabilityId,
|
|
claim.Product.Key,
|
|
claim.ProviderId,
|
|
claim.Status.ToString(),
|
|
claim.Justification?.ToString(),
|
|
claim.Detail,
|
|
scopeScore,
|
|
claim.FirstSeen,
|
|
claim.LastSeen,
|
|
scope,
|
|
document,
|
|
signature,
|
|
claim.AdditionalMetadata);
|
|
}
|
|
}
|