up
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using StellaOps.Signals.Models;
|
||||
|
||||
namespace StellaOps.Signals.Services;
|
||||
|
||||
internal static class ReachabilityFactDigestCalculator
|
||||
{
|
||||
private static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.Web)
|
||||
{
|
||||
WriteIndented = false
|
||||
};
|
||||
|
||||
public static string Compute(ReachabilityFactDocument fact)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(fact);
|
||||
|
||||
var canonical = new CanonicalReachabilityFact(
|
||||
CallgraphId: fact.CallgraphId ?? string.Empty,
|
||||
SubjectKey: fact.SubjectKey ?? string.Empty,
|
||||
Subject: new CanonicalSubject(
|
||||
fact.Subject?.ImageDigest ?? string.Empty,
|
||||
fact.Subject?.Component ?? string.Empty,
|
||||
fact.Subject?.Version ?? string.Empty,
|
||||
fact.Subject?.ScanId ?? string.Empty),
|
||||
EntryPoints: NormalizeList(fact.EntryPoints),
|
||||
States: NormalizeStates(fact.States),
|
||||
RuntimeFacts: NormalizeRuntimeFacts(fact.RuntimeFacts),
|
||||
Metadata: NormalizeMetadata(fact.Metadata),
|
||||
Score: fact.Score,
|
||||
UnknownsCount: fact.UnknownsCount,
|
||||
UnknownsPressure: fact.UnknownsPressure,
|
||||
ComputedAt: fact.ComputedAt);
|
||||
|
||||
var json = JsonSerializer.Serialize(canonical, SerializerOptions);
|
||||
Span<byte> hash = stackalloc byte[SHA256.HashSizeInBytes];
|
||||
SHA256.HashData(Encoding.UTF8.GetBytes(json), hash);
|
||||
return "sha256:" + Convert.ToHexString(hash).ToLowerInvariant();
|
||||
}
|
||||
|
||||
private static List<string> NormalizeList(IEnumerable<string>? values) =>
|
||||
values?
|
||||
.Where(v => !string.IsNullOrWhiteSpace(v))
|
||||
.Select(v => v.Trim())
|
||||
.Distinct(StringComparer.Ordinal)
|
||||
.OrderBy(v => v, StringComparer.Ordinal)
|
||||
.ToList() ?? new List<string>();
|
||||
|
||||
private static List<CanonicalState> NormalizeStates(IEnumerable<ReachabilityStateDocument>? states)
|
||||
{
|
||||
if (states is null)
|
||||
{
|
||||
return new List<CanonicalState>();
|
||||
}
|
||||
|
||||
return states
|
||||
.OrderBy(s => s.Target, StringComparer.Ordinal)
|
||||
.Select(state => new CanonicalState(
|
||||
Target: state.Target ?? string.Empty,
|
||||
Reachable: state.Reachable,
|
||||
Confidence: state.Confidence,
|
||||
Bucket: state.Bucket ?? "unknown",
|
||||
Weight: state.Weight,
|
||||
Score: state.Score,
|
||||
Path: NormalizeList(state.Path),
|
||||
RuntimeHits: NormalizeList(state.Evidence?.RuntimeHits),
|
||||
BlockedEdges: NormalizeList(state.Evidence?.BlockedEdges)))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private static List<CanonicalRuntimeFact> NormalizeRuntimeFacts(IEnumerable<RuntimeFactDocument>? facts)
|
||||
{
|
||||
if (facts is null)
|
||||
{
|
||||
return new List<CanonicalRuntimeFact>();
|
||||
}
|
||||
|
||||
return facts
|
||||
.Select(f => new CanonicalRuntimeFact(
|
||||
SymbolId: f.SymbolId ?? string.Empty,
|
||||
CodeId: f.CodeId,
|
||||
SymbolDigest: f.SymbolDigest,
|
||||
Purl: f.Purl,
|
||||
BuildId: f.BuildId,
|
||||
LoaderBase: f.LoaderBase,
|
||||
ProcessId: f.ProcessId,
|
||||
ProcessName: f.ProcessName,
|
||||
SocketAddress: f.SocketAddress,
|
||||
ContainerId: f.ContainerId,
|
||||
EvidenceUri: f.EvidenceUri,
|
||||
HitCount: f.HitCount,
|
||||
ObservedAt: f.ObservedAt,
|
||||
Metadata: NormalizeMetadata(f.Metadata)))
|
||||
.OrderBy(f => f.SymbolId, StringComparer.Ordinal)
|
||||
.ThenBy(f => f.CodeId, StringComparer.Ordinal)
|
||||
.ThenBy(f => f.LoaderBase, StringComparer.Ordinal)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private static SortedDictionary<string, string?> NormalizeMetadata(IDictionary<string, string?>? metadata)
|
||||
{
|
||||
var normalized = new SortedDictionary<string, string?>(StringComparer.Ordinal);
|
||||
if (metadata is null)
|
||||
{
|
||||
return normalized;
|
||||
}
|
||||
|
||||
foreach (var kvp in metadata)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(kvp.Key))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
normalized[kvp.Key.Trim()] = kvp.Value?.Trim();
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private sealed record CanonicalReachabilityFact(
|
||||
string CallgraphId,
|
||||
string SubjectKey,
|
||||
CanonicalSubject Subject,
|
||||
List<string> EntryPoints,
|
||||
List<CanonicalState> States,
|
||||
List<CanonicalRuntimeFact> RuntimeFacts,
|
||||
SortedDictionary<string, string?> Metadata,
|
||||
double Score,
|
||||
int UnknownsCount,
|
||||
double UnknownsPressure,
|
||||
DateTimeOffset ComputedAt);
|
||||
|
||||
private sealed record CanonicalSubject(
|
||||
string ImageDigest,
|
||||
string Component,
|
||||
string Version,
|
||||
string ScanId);
|
||||
|
||||
private sealed record CanonicalState(
|
||||
string Target,
|
||||
bool Reachable,
|
||||
double Confidence,
|
||||
string Bucket,
|
||||
double Weight,
|
||||
double Score,
|
||||
List<string> Path,
|
||||
List<string> RuntimeHits,
|
||||
List<string> BlockedEdges);
|
||||
|
||||
private sealed record CanonicalRuntimeFact(
|
||||
string SymbolId,
|
||||
string? CodeId,
|
||||
string? SymbolDigest,
|
||||
string? Purl,
|
||||
string? BuildId,
|
||||
string? LoaderBase,
|
||||
int? ProcessId,
|
||||
string? ProcessName,
|
||||
string? SocketAddress,
|
||||
string? ContainerId,
|
||||
string? EvidenceUri,
|
||||
int HitCount,
|
||||
DateTimeOffset? ObservedAt,
|
||||
SortedDictionary<string, string?> Metadata);
|
||||
}
|
||||
Reference in New Issue
Block a user