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
Policy Lint & Smoke / policy-lint (push) Has been cancelled
devportal-offline / build-offline (push) Has been cancelled
Mirror Thin Bundle Sign & Verify / mirror-sign (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
Policy Lint & Smoke / policy-lint (push) Has been cancelled
devportal-offline / build-offline (push) Has been cancelled
Mirror Thin Bundle Sign & Verify / mirror-sign (push) Has been cancelled
This commit is contained in:
@@ -7,6 +7,7 @@ namespace StellaOps.Policy.Engine.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Compiles policy DSL to canonical representation, signs it deterministically, and stores per revision.
|
||||
/// Captures AOC (Attestation of Compliance) metadata for policy revisions.
|
||||
/// </summary>
|
||||
internal sealed class PolicyBundleService
|
||||
{
|
||||
@@ -40,7 +41,9 @@ internal sealed class PolicyBundleService
|
||||
throw new ArgumentNullException(nameof(request));
|
||||
}
|
||||
|
||||
var compiledAt = _timeProvider.GetUtcNow();
|
||||
var compileResult = _compilationService.Compile(new PolicyCompileRequest(request.Dsl));
|
||||
|
||||
if (!compileResult.Success || compileResult.CanonicalRepresentation.IsDefaultOrEmpty)
|
||||
{
|
||||
return new PolicyBundleResponse(
|
||||
@@ -49,30 +52,55 @@ internal sealed class PolicyBundleService
|
||||
Signature: null,
|
||||
SizeBytes: 0,
|
||||
CreatedAt: null,
|
||||
Diagnostics: compileResult.Diagnostics);
|
||||
Diagnostics: compileResult.Diagnostics,
|
||||
AocMetadata: null);
|
||||
}
|
||||
|
||||
var payload = compileResult.CanonicalRepresentation.ToArray();
|
||||
var digest = compileResult.Digest ?? $"sha256:{ComputeSha256Hex(payload)}";
|
||||
var signature = Sign(digest, request.SigningKeyId);
|
||||
var artifactDigest = compileResult.Digest ?? $"sha256:{ComputeSha256Hex(payload)}";
|
||||
var sourceDigest = ComputeSourceDigest(request.Dsl.Source);
|
||||
var signature = Sign(artifactDigest, request.SigningKeyId);
|
||||
var createdAt = _timeProvider.GetUtcNow();
|
||||
|
||||
// Generate AOC metadata
|
||||
var compilationId = GenerateCompilationId(packId, version, compiledAt);
|
||||
var aocMetadata = CreateAocMetadata(
|
||||
compilationId,
|
||||
request.Dsl.Syntax,
|
||||
compiledAt,
|
||||
sourceDigest,
|
||||
artifactDigest,
|
||||
compileResult,
|
||||
request.Provenance);
|
||||
|
||||
var record = new PolicyBundleRecord(
|
||||
Digest: digest,
|
||||
Digest: artifactDigest,
|
||||
Signature: signature,
|
||||
Size: payload.Length,
|
||||
CreatedAt: createdAt,
|
||||
Payload: payload.ToImmutableArray());
|
||||
Payload: payload.ToImmutableArray(),
|
||||
AocMetadata: aocMetadata);
|
||||
|
||||
await _repository.StoreBundleAsync(packId, version, record, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var aocResponse = new PolicyAocMetadataResponse(
|
||||
CompilationId: aocMetadata.CompilationId,
|
||||
CompilerVersion: aocMetadata.CompilerVersion,
|
||||
CompiledAt: aocMetadata.CompiledAt,
|
||||
SourceDigest: aocMetadata.SourceDigest,
|
||||
ArtifactDigest: aocMetadata.ArtifactDigest,
|
||||
ComplexityScore: aocMetadata.ComplexityScore,
|
||||
RuleCount: aocMetadata.RuleCount,
|
||||
DurationMilliseconds: aocMetadata.DurationMilliseconds);
|
||||
|
||||
return new PolicyBundleResponse(
|
||||
Success: true,
|
||||
Digest: digest,
|
||||
Digest: artifactDigest,
|
||||
Signature: signature,
|
||||
SizeBytes: payload.Length,
|
||||
CreatedAt: createdAt,
|
||||
Diagnostics: compileResult.Diagnostics);
|
||||
Diagnostics: compileResult.Diagnostics,
|
||||
AocMetadata: aocResponse);
|
||||
}
|
||||
|
||||
private static string ComputeSha256Hex(byte[] payload)
|
||||
@@ -82,6 +110,14 @@ internal sealed class PolicyBundleService
|
||||
return Convert.ToHexString(hash).ToLowerInvariant();
|
||||
}
|
||||
|
||||
private static string ComputeSourceDigest(string source)
|
||||
{
|
||||
var bytes = Encoding.UTF8.GetBytes(source);
|
||||
Span<byte> hash = stackalloc byte[32];
|
||||
SHA256.HashData(bytes, hash);
|
||||
return $"sha256:{Convert.ToHexString(hash).ToLowerInvariant()}";
|
||||
}
|
||||
|
||||
private static string Sign(string digest, string? signingKeyId)
|
||||
{
|
||||
// Deterministic signature stub suitable for offline testing.
|
||||
@@ -89,4 +125,51 @@ internal sealed class PolicyBundleService
|
||||
var mac = HMACSHA256.HashData(Encoding.UTF8.GetBytes(key), Encoding.UTF8.GetBytes(digest));
|
||||
return $"sig:sha256:{Convert.ToHexString(mac).ToLowerInvariant()}";
|
||||
}
|
||||
|
||||
private static string GenerateCompilationId(string packId, int version, DateTimeOffset timestamp)
|
||||
{
|
||||
// Deterministic compilation ID based on pack, version, and timestamp
|
||||
var input = $"{packId}:{version}:{timestamp:O}";
|
||||
var bytes = Encoding.UTF8.GetBytes(input);
|
||||
Span<byte> hash = stackalloc byte[32];
|
||||
SHA256.HashData(bytes, hash);
|
||||
return $"comp-{Convert.ToHexString(hash).ToLowerInvariant()[..16]}";
|
||||
}
|
||||
|
||||
private static PolicyAocMetadata CreateAocMetadata(
|
||||
string compilationId,
|
||||
string compilerVersion,
|
||||
DateTimeOffset compiledAt,
|
||||
string sourceDigest,
|
||||
string artifactDigest,
|
||||
PolicyCompilationResultDto compileResult,
|
||||
PolicyProvenanceInput? provenanceInput)
|
||||
{
|
||||
var complexity = compileResult.Complexity;
|
||||
var statistics = compileResult.Statistics;
|
||||
|
||||
PolicyProvenance? provenance = null;
|
||||
if (provenanceInput is not null)
|
||||
{
|
||||
provenance = new PolicyProvenance(
|
||||
SourceType: provenanceInput.SourceType,
|
||||
SourceUrl: provenanceInput.SourceUrl,
|
||||
Submitter: provenanceInput.Submitter,
|
||||
CommitSha: provenanceInput.CommitSha,
|
||||
Branch: provenanceInput.Branch,
|
||||
IngestedAt: compiledAt);
|
||||
}
|
||||
|
||||
return new PolicyAocMetadata(
|
||||
CompilationId: compilationId,
|
||||
CompilerVersion: compilerVersion,
|
||||
CompiledAt: compiledAt,
|
||||
SourceDigest: sourceDigest,
|
||||
ArtifactDigest: artifactDigest,
|
||||
ComplexityScore: complexity?.Score ?? 0,
|
||||
RuleCount: statistics?.RuleCount ?? complexity?.RuleCount ?? 0,
|
||||
DurationMilliseconds: compileResult.DurationMilliseconds,
|
||||
Provenance: provenance,
|
||||
AttestationRef: null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,425 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StellaOps.Policy.Engine.Caching;
|
||||
using StellaOps.Policy.Engine.Domain;
|
||||
using StellaOps.Policy.Engine.Evaluation;
|
||||
using StellaOps.PolicyDsl;
|
||||
|
||||
namespace StellaOps.Policy.Engine.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Request for runtime policy evaluation over linkset/SBOM data.
|
||||
/// </summary>
|
||||
internal sealed record RuntimeEvaluationRequest(
|
||||
string PackId,
|
||||
int Version,
|
||||
string TenantId,
|
||||
string SubjectPurl,
|
||||
string AdvisoryId,
|
||||
PolicyEvaluationSeverity Severity,
|
||||
PolicyEvaluationAdvisory Advisory,
|
||||
PolicyEvaluationVexEvidence Vex,
|
||||
PolicyEvaluationSbom Sbom,
|
||||
PolicyEvaluationExceptions Exceptions,
|
||||
PolicyEvaluationReachability Reachability,
|
||||
DateTimeOffset? EvaluationTimestamp = null,
|
||||
bool BypassCache = false);
|
||||
|
||||
/// <summary>
|
||||
/// Response from runtime policy evaluation.
|
||||
/// </summary>
|
||||
internal sealed record RuntimeEvaluationResponse(
|
||||
string PackId,
|
||||
int Version,
|
||||
string PolicyDigest,
|
||||
string Status,
|
||||
string? Severity,
|
||||
string? RuleName,
|
||||
int? Priority,
|
||||
ImmutableDictionary<string, string> Annotations,
|
||||
ImmutableArray<string> Warnings,
|
||||
PolicyExceptionApplication? AppliedException,
|
||||
string CorrelationId,
|
||||
bool Cached,
|
||||
CacheSource CacheSource,
|
||||
long EvaluationDurationMs);
|
||||
|
||||
/// <summary>
|
||||
/// Runtime evaluator executing compiled policy plans over advisory/VEX linksets and SBOM asset metadata
|
||||
/// with deterministic caching (Redis) and fallback path.
|
||||
/// </summary>
|
||||
internal sealed class PolicyRuntimeEvaluationService
|
||||
{
|
||||
private readonly IPolicyPackRepository _repository;
|
||||
private readonly IPolicyEvaluationCache _cache;
|
||||
private readonly PolicyEvaluator _evaluator;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
private readonly ILogger<PolicyRuntimeEvaluationService> _logger;
|
||||
|
||||
private static readonly JsonSerializerOptions ContextSerializerOptions = new()
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
WriteIndented = false,
|
||||
};
|
||||
|
||||
public PolicyRuntimeEvaluationService(
|
||||
IPolicyPackRepository repository,
|
||||
IPolicyEvaluationCache cache,
|
||||
PolicyEvaluator evaluator,
|
||||
TimeProvider timeProvider,
|
||||
ILogger<PolicyRuntimeEvaluationService> logger)
|
||||
{
|
||||
_repository = repository ?? throw new ArgumentNullException(nameof(repository));
|
||||
_cache = cache ?? throw new ArgumentNullException(nameof(cache));
|
||||
_evaluator = evaluator ?? throw new ArgumentNullException(nameof(evaluator));
|
||||
_timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates a policy against the provided context with deterministic caching.
|
||||
/// </summary>
|
||||
public async Task<RuntimeEvaluationResponse> EvaluateAsync(
|
||||
RuntimeEvaluationRequest request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
|
||||
var startTimestamp = _timeProvider.GetTimestamp();
|
||||
var evaluationTimestamp = request.EvaluationTimestamp ?? _timeProvider.GetUtcNow();
|
||||
|
||||
// Load the compiled policy bundle
|
||||
var bundle = await _repository.GetBundleAsync(request.PackId, request.Version, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (bundle is null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Policy bundle not found for pack '{request.PackId}' version {request.Version}.");
|
||||
}
|
||||
|
||||
// Compute deterministic cache key
|
||||
var subjectDigest = ComputeSubjectDigest(request.TenantId, request.SubjectPurl, request.AdvisoryId);
|
||||
var contextDigest = ComputeContextDigest(request);
|
||||
var cacheKey = PolicyEvaluationCacheKey.Create(bundle.Digest, subjectDigest, contextDigest);
|
||||
|
||||
// Try cache lookup unless bypassed
|
||||
if (!request.BypassCache)
|
||||
{
|
||||
var cacheResult = await _cache.GetAsync(cacheKey, cancellationToken).ConfigureAwait(false);
|
||||
if (cacheResult.CacheHit && cacheResult.Entry is not null)
|
||||
{
|
||||
var duration = GetElapsedMilliseconds(startTimestamp);
|
||||
_logger.LogDebug(
|
||||
"Cache hit for evaluation {PackId}@{Version} subject {Subject} from {Source}",
|
||||
request.PackId, request.Version, request.SubjectPurl, cacheResult.Source);
|
||||
|
||||
return CreateResponseFromCache(
|
||||
request, bundle.Digest, cacheResult.Entry, cacheResult.Source, duration);
|
||||
}
|
||||
}
|
||||
|
||||
// Cache miss - perform evaluation
|
||||
var document = DeserializeCompiledPolicy(bundle.Payload);
|
||||
if (document is null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Failed to deserialize compiled policy for pack '{request.PackId}' version {request.Version}.");
|
||||
}
|
||||
|
||||
var context = new PolicyEvaluationContext(
|
||||
request.Severity,
|
||||
new PolicyEvaluationEnvironment(ImmutableDictionary<string, string>.Empty),
|
||||
request.Advisory,
|
||||
request.Vex,
|
||||
request.Sbom,
|
||||
request.Exceptions,
|
||||
request.Reachability,
|
||||
evaluationTimestamp);
|
||||
|
||||
var evalRequest = new Evaluation.PolicyEvaluationRequest(document, context);
|
||||
var result = _evaluator.Evaluate(evalRequest);
|
||||
|
||||
var correlationId = ComputeCorrelationId(bundle.Digest, subjectDigest, contextDigest);
|
||||
var expiresAt = evaluationTimestamp.AddMinutes(30);
|
||||
|
||||
// Store in cache
|
||||
var cacheEntry = new PolicyEvaluationCacheEntry(
|
||||
result.Status,
|
||||
result.Severity,
|
||||
result.RuleName,
|
||||
result.Priority,
|
||||
result.Annotations,
|
||||
result.Warnings,
|
||||
result.AppliedException?.ExceptionId,
|
||||
correlationId,
|
||||
evaluationTimestamp,
|
||||
expiresAt);
|
||||
|
||||
await _cache.SetAsync(cacheKey, cacheEntry, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var evalDuration = GetElapsedMilliseconds(startTimestamp);
|
||||
_logger.LogDebug(
|
||||
"Evaluated {PackId}@{Version} subject {Subject} in {Duration}ms - {Status}",
|
||||
request.PackId, request.Version, request.SubjectPurl, evalDuration, result.Status);
|
||||
|
||||
return new RuntimeEvaluationResponse(
|
||||
request.PackId,
|
||||
request.Version,
|
||||
bundle.Digest,
|
||||
result.Status,
|
||||
result.Severity,
|
||||
result.RuleName,
|
||||
result.Priority,
|
||||
result.Annotations,
|
||||
result.Warnings,
|
||||
result.AppliedException,
|
||||
correlationId,
|
||||
Cached: false,
|
||||
CacheSource: CacheSource.None,
|
||||
EvaluationDurationMs: evalDuration);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates multiple subjects in batch with caching.
|
||||
/// </summary>
|
||||
public async Task<IReadOnlyList<RuntimeEvaluationResponse>> EvaluateBatchAsync(
|
||||
IReadOnlyList<RuntimeEvaluationRequest> requests,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (requests.Count == 0)
|
||||
{
|
||||
return Array.Empty<RuntimeEvaluationResponse>();
|
||||
}
|
||||
|
||||
var results = new List<RuntimeEvaluationResponse>(requests.Count);
|
||||
|
||||
// Group by pack/version for bundle loading efficiency
|
||||
var groups = requests.GroupBy(r => (r.PackId, r.Version));
|
||||
|
||||
foreach (var group in groups)
|
||||
{
|
||||
var (packId, version) = group.Key;
|
||||
var bundle = await _repository.GetBundleAsync(packId, version, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (bundle is null)
|
||||
{
|
||||
foreach (var request in group)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"Policy bundle not found for pack '{PackId}' version {Version}, skipping evaluation",
|
||||
packId, version);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
var document = DeserializeCompiledPolicy(bundle.Payload);
|
||||
if (document is null)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"Failed to deserialize policy bundle for pack '{PackId}' version {Version}",
|
||||
packId, version);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Build cache keys for batch lookup
|
||||
var cacheKeys = new List<(RuntimeEvaluationRequest Request, PolicyEvaluationCacheKey Key)>();
|
||||
foreach (var request in group)
|
||||
{
|
||||
var subjectDigest = ComputeSubjectDigest(request.TenantId, request.SubjectPurl, request.AdvisoryId);
|
||||
var contextDigest = ComputeContextDigest(request);
|
||||
var key = PolicyEvaluationCacheKey.Create(bundle.Digest, subjectDigest, contextDigest);
|
||||
cacheKeys.Add((request, key));
|
||||
}
|
||||
|
||||
// Batch cache lookup
|
||||
var keyList = cacheKeys.Select(k => k.Key).ToList();
|
||||
var cacheResults = await _cache.GetBatchAsync(keyList, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var toEvaluate = new List<(RuntimeEvaluationRequest Request, PolicyEvaluationCacheKey Key)>();
|
||||
|
||||
// Process cache hits
|
||||
foreach (var (request, key) in cacheKeys)
|
||||
{
|
||||
if (!request.BypassCache && cacheResults.Found.TryGetValue(key, out var entry))
|
||||
{
|
||||
var response = CreateResponseFromCache(request, bundle.Digest, entry, CacheSource.InMemory, 0);
|
||||
results.Add(response);
|
||||
}
|
||||
else
|
||||
{
|
||||
toEvaluate.Add((request, key));
|
||||
}
|
||||
}
|
||||
|
||||
// Evaluate cache misses
|
||||
var entriesToCache = new Dictionary<PolicyEvaluationCacheKey, PolicyEvaluationCacheEntry>();
|
||||
|
||||
foreach (var (request, key) in toEvaluate)
|
||||
{
|
||||
var startTimestamp = _timeProvider.GetTimestamp();
|
||||
var evaluationTimestamp = request.EvaluationTimestamp ?? _timeProvider.GetUtcNow();
|
||||
|
||||
var context = new PolicyEvaluationContext(
|
||||
request.Severity,
|
||||
new PolicyEvaluationEnvironment(ImmutableDictionary<string, string>.Empty),
|
||||
request.Advisory,
|
||||
request.Vex,
|
||||
request.Sbom,
|
||||
request.Exceptions,
|
||||
request.Reachability,
|
||||
evaluationTimestamp);
|
||||
|
||||
var evalRequest = new Evaluation.PolicyEvaluationRequest(document, context);
|
||||
var result = _evaluator.Evaluate(evalRequest);
|
||||
|
||||
var correlationId = ComputeCorrelationId(bundle.Digest, key.SubjectDigest, key.ContextDigest);
|
||||
var expiresAt = evaluationTimestamp.AddMinutes(30);
|
||||
var duration = GetElapsedMilliseconds(startTimestamp);
|
||||
|
||||
var cacheEntry = new PolicyEvaluationCacheEntry(
|
||||
result.Status,
|
||||
result.Severity,
|
||||
result.RuleName,
|
||||
result.Priority,
|
||||
result.Annotations,
|
||||
result.Warnings,
|
||||
result.AppliedException?.ExceptionId,
|
||||
correlationId,
|
||||
evaluationTimestamp,
|
||||
expiresAt);
|
||||
|
||||
entriesToCache[key] = cacheEntry;
|
||||
|
||||
results.Add(new RuntimeEvaluationResponse(
|
||||
request.PackId,
|
||||
request.Version,
|
||||
bundle.Digest,
|
||||
result.Status,
|
||||
result.Severity,
|
||||
result.RuleName,
|
||||
result.Priority,
|
||||
result.Annotations,
|
||||
result.Warnings,
|
||||
result.AppliedException,
|
||||
correlationId,
|
||||
Cached: false,
|
||||
CacheSource: CacheSource.None,
|
||||
EvaluationDurationMs: duration));
|
||||
}
|
||||
|
||||
// Batch store cache entries
|
||||
if (entriesToCache.Count > 0)
|
||||
{
|
||||
await _cache.SetBatchAsync(entriesToCache, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private static RuntimeEvaluationResponse CreateResponseFromCache(
|
||||
RuntimeEvaluationRequest request,
|
||||
string policyDigest,
|
||||
PolicyEvaluationCacheEntry entry,
|
||||
CacheSource source,
|
||||
long durationMs)
|
||||
{
|
||||
PolicyExceptionApplication? appliedException = null;
|
||||
if (entry.ExceptionId is not null)
|
||||
{
|
||||
// Reconstruct minimal exception application from cache
|
||||
appliedException = new PolicyExceptionApplication(
|
||||
entry.ExceptionId,
|
||||
EffectId: "cached",
|
||||
EffectType: PolicyExceptionEffectType.Suppress,
|
||||
OriginalStatus: entry.Status,
|
||||
OriginalSeverity: entry.Severity,
|
||||
AppliedStatus: entry.Status,
|
||||
AppliedSeverity: entry.Severity,
|
||||
Metadata: ImmutableDictionary<string, string>.Empty);
|
||||
}
|
||||
|
||||
return new RuntimeEvaluationResponse(
|
||||
request.PackId,
|
||||
request.Version,
|
||||
policyDigest,
|
||||
entry.Status,
|
||||
entry.Severity,
|
||||
entry.RuleName,
|
||||
entry.Priority,
|
||||
entry.Annotations,
|
||||
entry.Warnings,
|
||||
appliedException,
|
||||
entry.CorrelationId,
|
||||
Cached: true,
|
||||
CacheSource: source,
|
||||
EvaluationDurationMs: durationMs);
|
||||
}
|
||||
|
||||
private static string ComputeSubjectDigest(string tenantId, string subjectPurl, string advisoryId)
|
||||
{
|
||||
var input = $"{tenantId}|{subjectPurl}|{advisoryId}";
|
||||
Span<byte> hash = stackalloc byte[32];
|
||||
SHA256.HashData(Encoding.UTF8.GetBytes(input), hash);
|
||||
return Convert.ToHexStringLower(hash);
|
||||
}
|
||||
|
||||
private static string ComputeContextDigest(RuntimeEvaluationRequest request)
|
||||
{
|
||||
// Create deterministic context representation
|
||||
var contextData = new
|
||||
{
|
||||
severity = request.Severity.Normalized,
|
||||
severityScore = request.Severity.Score,
|
||||
advisorySource = request.Advisory.Source,
|
||||
vexCount = request.Vex.Statements.Length,
|
||||
vexStatements = request.Vex.Statements.Select(s => $"{s.Status}:{s.Justification}").OrderBy(s => s).ToArray(),
|
||||
sbomTags = request.Sbom.Tags.OrderBy(t => t).ToArray(),
|
||||
exceptionCount = request.Exceptions.Instances.Length,
|
||||
reachability = request.Reachability.State,
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize(contextData, ContextSerializerOptions);
|
||||
Span<byte> hash = stackalloc byte[32];
|
||||
SHA256.HashData(Encoding.UTF8.GetBytes(json), hash);
|
||||
return Convert.ToHexStringLower(hash);
|
||||
}
|
||||
|
||||
private static string ComputeCorrelationId(string policyDigest, string subjectDigest, string contextDigest)
|
||||
{
|
||||
var input = $"{policyDigest}|{subjectDigest}|{contextDigest}";
|
||||
Span<byte> hash = stackalloc byte[32];
|
||||
SHA256.HashData(Encoding.UTF8.GetBytes(input), hash);
|
||||
return Convert.ToHexString(hash);
|
||||
}
|
||||
|
||||
private static PolicyIrDocument? DeserializeCompiledPolicy(ImmutableArray<byte> payload)
|
||||
{
|
||||
if (payload.IsDefaultOrEmpty)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var json = Encoding.UTF8.GetString(payload.AsSpan());
|
||||
return JsonSerializer.Deserialize<PolicyIrDocument>(json);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private long GetElapsedMilliseconds(long startTimestamp)
|
||||
{
|
||||
var elapsed = _timeProvider.GetElapsedTime(startTimestamp);
|
||||
return (long)elapsed.TotalMilliseconds;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user