Refactor code structure for improved readability and maintainability; optimize performance in key functions.
This commit is contained in:
@@ -0,0 +1,164 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Text.Json;
|
||||
using StellaOps.Attestor.ProofChain.Predicates;
|
||||
using StellaOps.Attestor.ProofChain.Statements;
|
||||
using StellaOps.Scanner.SmartDiff.Detection;
|
||||
|
||||
namespace StellaOps.Scanner.SmartDiff.Attestation;
|
||||
|
||||
/// <summary>
|
||||
/// Build request for delta verdict attestations.
|
||||
/// </summary>
|
||||
public sealed record DeltaVerdictBuildRequest
|
||||
{
|
||||
public required string BeforeRevisionId { get; init; }
|
||||
public required string AfterRevisionId { get; init; }
|
||||
public required string BeforeImageDigest { get; init; }
|
||||
public required string AfterImageDigest { get; init; }
|
||||
public required IReadOnlyList<MaterialRiskChangeResult> Changes { get; init; }
|
||||
public DateTimeOffset? ComparedAt { get; init; }
|
||||
public string? BeforeVerdictDigest { get; init; }
|
||||
public string? AfterVerdictDigest { get; init; }
|
||||
public AttestationReference? BeforeProofSpine { get; init; }
|
||||
public AttestationReference? AfterProofSpine { get; init; }
|
||||
public string? BeforeGraphRevisionId { get; init; }
|
||||
public string? AfterGraphRevisionId { get; init; }
|
||||
public string? BeforeImageName { get; init; }
|
||||
public string? AfterImageName { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds delta verdict predicate and statement payloads.
|
||||
/// </summary>
|
||||
public sealed class DeltaVerdictBuilder
|
||||
{
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
public DeltaVerdictBuilder(TimeProvider? timeProvider = null)
|
||||
{
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
public DeltaVerdictPredicate BuildPredicate(DeltaVerdictBuildRequest request)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
ArgumentNullException.ThrowIfNull(request.Changes);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(request.BeforeRevisionId);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(request.AfterRevisionId);
|
||||
|
||||
var comparedAt = request.ComparedAt ?? _timeProvider.GetUtcNow();
|
||||
var changeEntries = BuildChangeEntries(request.Changes);
|
||||
var hasMaterialChange = request.Changes.Any(c => c.HasMaterialChange);
|
||||
var priorityScore = request.Changes
|
||||
.Where(c => c.HasMaterialChange)
|
||||
.Sum(c => c.PriorityScore);
|
||||
|
||||
return new DeltaVerdictPredicate
|
||||
{
|
||||
BeforeRevisionId = request.BeforeRevisionId,
|
||||
AfterRevisionId = request.AfterRevisionId,
|
||||
HasMaterialChange = hasMaterialChange,
|
||||
PriorityScore = priorityScore,
|
||||
Changes = changeEntries,
|
||||
BeforeVerdictDigest = request.BeforeVerdictDigest,
|
||||
AfterVerdictDigest = request.AfterVerdictDigest,
|
||||
BeforeProofSpine = request.BeforeProofSpine,
|
||||
AfterProofSpine = request.AfterProofSpine,
|
||||
BeforeGraphRevisionId = request.BeforeGraphRevisionId,
|
||||
AfterGraphRevisionId = request.AfterGraphRevisionId,
|
||||
ComparedAt = comparedAt
|
||||
};
|
||||
}
|
||||
|
||||
public DeltaVerdictStatement BuildStatement(DeltaVerdictBuildRequest request)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(request.BeforeImageDigest);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(request.AfterImageDigest);
|
||||
|
||||
var predicate = BuildPredicate(request);
|
||||
|
||||
return new DeltaVerdictStatement
|
||||
{
|
||||
Subject =
|
||||
[
|
||||
BuildSubject(request.BeforeImageDigest, request.BeforeImageName),
|
||||
BuildSubject(request.AfterImageDigest, request.AfterImageName)
|
||||
],
|
||||
Predicate = predicate
|
||||
};
|
||||
}
|
||||
|
||||
private static ImmutableArray<DeltaVerdictChange> BuildChangeEntries(IReadOnlyList<MaterialRiskChangeResult> changes)
|
||||
{
|
||||
if (changes.Count == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
var entries = new List<DeltaVerdictChange>();
|
||||
|
||||
foreach (var change in changes)
|
||||
{
|
||||
if (!change.HasMaterialChange || change.Changes.IsDefaultOrEmpty)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var detail in change.Changes)
|
||||
{
|
||||
entries.Add(new DeltaVerdictChange
|
||||
{
|
||||
Rule = ToJsonEnum(detail.Rule),
|
||||
FindingKey = new DeltaFindingKey
|
||||
{
|
||||
VulnId = change.FindingKey.VulnId,
|
||||
Purl = change.FindingKey.ComponentPurl
|
||||
},
|
||||
Direction = ToJsonEnum(detail.Direction),
|
||||
ChangeType = ToJsonEnum(detail.ChangeType),
|
||||
Reason = detail.Reason,
|
||||
PreviousValue = detail.PreviousValue,
|
||||
CurrentValue = detail.CurrentValue,
|
||||
Weight = detail.Weight
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return entries
|
||||
.OrderBy(e => e.FindingKey.VulnId, StringComparer.Ordinal)
|
||||
.ThenBy(e => e.FindingKey.Purl, StringComparer.Ordinal)
|
||||
.ThenBy(e => e.Rule, StringComparer.Ordinal)
|
||||
.ThenBy(e => e.ChangeType, StringComparer.Ordinal)
|
||||
.ThenBy(e => e.Direction, StringComparer.Ordinal)
|
||||
.ThenBy(e => e.Reason, StringComparer.Ordinal)
|
||||
.ToImmutableArray();
|
||||
}
|
||||
|
||||
private static Subject BuildSubject(string digest, string? name)
|
||||
{
|
||||
var (algorithm, value) = SplitDigest(digest);
|
||||
return new Subject
|
||||
{
|
||||
Name = name ?? digest,
|
||||
Digest = new Dictionary<string, string> { [algorithm] = value }
|
||||
};
|
||||
}
|
||||
|
||||
private static (string Algorithm, string Value) SplitDigest(string digest)
|
||||
{
|
||||
var colonIndex = digest.IndexOf(':');
|
||||
if (colonIndex <= 0 || colonIndex == digest.Length - 1)
|
||||
{
|
||||
return ("sha256", digest);
|
||||
}
|
||||
|
||||
return (digest[..colonIndex], digest[(colonIndex + 1)..]);
|
||||
}
|
||||
|
||||
private static string ToJsonEnum<TEnum>(TEnum value) where TEnum : struct, Enum
|
||||
{
|
||||
var json = JsonSerializer.Serialize(value);
|
||||
return json.Trim('"');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
using StellaOps.Attestor.ProofChain.Predicates;
|
||||
using StellaOps.Scanner.Storage.Oci;
|
||||
|
||||
namespace StellaOps.Scanner.SmartDiff.Attestation;
|
||||
|
||||
public sealed record DeltaVerdictOciPublishRequest
|
||||
{
|
||||
public required string Reference { get; init; }
|
||||
public required string BeforeImageDigest { get; init; }
|
||||
public required string AfterImageDigest { get; init; }
|
||||
public required byte[] DsseEnvelopeBytes { get; init; }
|
||||
public string? AttestationDigest { get; init; }
|
||||
}
|
||||
|
||||
public sealed class DeltaVerdictOciPublisher
|
||||
{
|
||||
private readonly OciArtifactPusher _pusher;
|
||||
|
||||
public DeltaVerdictOciPublisher(OciArtifactPusher pusher)
|
||||
{
|
||||
_pusher = pusher ?? throw new ArgumentNullException(nameof(pusher));
|
||||
}
|
||||
|
||||
public Task<OciArtifactPushResult> PushAsync(
|
||||
DeltaVerdictOciPublishRequest request,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
|
||||
var annotations = new Dictionary<string, string>(StringComparer.Ordinal)
|
||||
{
|
||||
[OciAnnotations.StellaPredicateType] = DeltaVerdictPredicate.PredicateType,
|
||||
[OciAnnotations.BaseDigest] = request.BeforeImageDigest,
|
||||
[OciAnnotations.StellaBeforeDigest] = request.BeforeImageDigest,
|
||||
[OciAnnotations.StellaAfterDigest] = request.AfterImageDigest
|
||||
};
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(request.AttestationDigest))
|
||||
{
|
||||
annotations[OciAnnotations.StellaAttestationDigest] = request.AttestationDigest!;
|
||||
}
|
||||
|
||||
var pushRequest = new OciArtifactPushRequest
|
||||
{
|
||||
Reference = request.Reference,
|
||||
ArtifactType = OciMediaTypes.DeltaVerdictPredicate,
|
||||
SubjectDigest = request.AfterImageDigest,
|
||||
Layers =
|
||||
[
|
||||
new OciLayerContent
|
||||
{
|
||||
Content = request.DsseEnvelopeBytes,
|
||||
MediaType = OciMediaTypes.DsseEnvelope
|
||||
}
|
||||
],
|
||||
Annotations = annotations
|
||||
};
|
||||
|
||||
return _pusher.PushAsync(pushRequest, cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -46,7 +46,8 @@ public sealed record SmartDiffSarifInput(
|
||||
IReadOnlyList<HardeningRegression> HardeningRegressions,
|
||||
IReadOnlyList<VexCandidate> VexCandidates,
|
||||
IReadOnlyList<ReachabilityChange> ReachabilityChanges,
|
||||
VcsInfo? VcsInfo = null);
|
||||
VcsInfo? VcsInfo = null,
|
||||
string? DeltaVerdictReference = null);
|
||||
|
||||
/// <summary>
|
||||
/// VCS information for SARIF provenance.
|
||||
@@ -244,7 +245,7 @@ public sealed class SarifOutputGenerator
|
||||
// Material risk changes
|
||||
foreach (var change in input.MaterialChanges)
|
||||
{
|
||||
results.Add(CreateMaterialChangeResult(change));
|
||||
results.Add(CreateMaterialChangeResult(change, input.DeltaVerdictReference));
|
||||
}
|
||||
|
||||
// Hardening regressions
|
||||
@@ -277,7 +278,7 @@ public sealed class SarifOutputGenerator
|
||||
return [.. results];
|
||||
}
|
||||
|
||||
private static SarifResult CreateMaterialChangeResult(MaterialRiskChange change)
|
||||
private static SarifResult CreateMaterialChangeResult(MaterialRiskChange change, string? deltaVerdictReference)
|
||||
{
|
||||
var level = change.Direction == RiskDirection.Increased ? SarifLevel.Warning : SarifLevel.Note;
|
||||
var message = $"Material risk change for {change.VulnId} in {change.ComponentPurl}: {change.Reason}";
|
||||
@@ -288,6 +289,13 @@ public sealed class SarifOutputGenerator
|
||||
ArtifactLocation: new SarifArtifactLocation(Uri: change.FilePath))))
|
||||
: (ImmutableArray<SarifLocation>?)null;
|
||||
|
||||
var properties = deltaVerdictReference is null
|
||||
? null
|
||||
: ImmutableSortedDictionary.CreateRange(StringComparer.Ordinal, new[]
|
||||
{
|
||||
KeyValuePair.Create("deltaVerdictRef", (object)deltaVerdictReference)
|
||||
});
|
||||
|
||||
return new SarifResult(
|
||||
RuleId: "SDIFF001",
|
||||
Level: level,
|
||||
@@ -297,7 +305,8 @@ public sealed class SarifOutputGenerator
|
||||
{
|
||||
KeyValuePair.Create("purl", change.ComponentPurl),
|
||||
KeyValuePair.Create("vulnId", change.VulnId),
|
||||
}));
|
||||
}),
|
||||
Properties: properties);
|
||||
}
|
||||
|
||||
private static SarifResult CreateHardeningRegressionResult(HardeningRegression regression)
|
||||
|
||||
@@ -5,4 +5,9 @@
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\\..\\..\\Attestor\\__Libraries\\StellaOps.Attestor.ProofChain\\StellaOps.Attestor.ProofChain.csproj" />
|
||||
<ProjectReference Include="..\\StellaOps.Scanner.Storage.Oci\\StellaOps.Scanner.Storage.Oci.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
Reference in New Issue
Block a user