Refactor code structure for improved readability and maintainability; optimize performance in key functions.
This commit is contained in:
@@ -3,6 +3,9 @@ using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using StellaOps.Policy;
|
||||
using StellaOps.Policy.Confidence.Models;
|
||||
using StellaOps.Policy.Exceptions.Models;
|
||||
using StellaOps.Policy.Unknowns.Models;
|
||||
using StellaOps.PolicyDsl;
|
||||
|
||||
namespace StellaOps.Policy.Engine.Evaluation;
|
||||
@@ -18,9 +21,13 @@ internal sealed record PolicyEvaluationContext(
|
||||
PolicyEvaluationVexEvidence Vex,
|
||||
PolicyEvaluationSbom Sbom,
|
||||
PolicyEvaluationExceptions Exceptions,
|
||||
ImmutableArray<Unknown> Unknowns,
|
||||
ImmutableArray<ExceptionObject> ExceptionObjects,
|
||||
PolicyEvaluationReachability Reachability,
|
||||
PolicyEvaluationEntropy Entropy,
|
||||
DateTimeOffset? EvaluationTimestamp = null)
|
||||
DateTimeOffset? EvaluationTimestamp = null,
|
||||
string? PolicyDigest = null,
|
||||
bool? ProvenanceAttested = null)
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the evaluation timestamp for deterministic time-based operations.
|
||||
@@ -39,8 +46,25 @@ internal sealed record PolicyEvaluationContext(
|
||||
PolicyEvaluationVexEvidence vex,
|
||||
PolicyEvaluationSbom sbom,
|
||||
PolicyEvaluationExceptions exceptions,
|
||||
DateTimeOffset? evaluationTimestamp = null)
|
||||
: this(severity, environment, advisory, vex, sbom, exceptions, PolicyEvaluationReachability.Unknown, PolicyEvaluationEntropy.Unknown, evaluationTimestamp)
|
||||
ImmutableArray<Unknown>? unknowns = null,
|
||||
ImmutableArray<ExceptionObject>? exceptionObjects = null,
|
||||
DateTimeOffset? evaluationTimestamp = null,
|
||||
string? policyDigest = null,
|
||||
bool? provenanceAttested = null)
|
||||
: this(
|
||||
severity,
|
||||
environment,
|
||||
advisory,
|
||||
vex,
|
||||
sbom,
|
||||
exceptions,
|
||||
unknowns ?? ImmutableArray<Unknown>.Empty,
|
||||
exceptionObjects ?? ImmutableArray<ExceptionObject>.Empty,
|
||||
PolicyEvaluationReachability.Unknown,
|
||||
PolicyEvaluationEntropy.Unknown,
|
||||
evaluationTimestamp,
|
||||
policyDigest,
|
||||
provenanceAttested)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -100,7 +124,11 @@ internal sealed record PolicyEvaluationResult(
|
||||
int? Priority,
|
||||
ImmutableDictionary<string, string> Annotations,
|
||||
ImmutableArray<string> Warnings,
|
||||
PolicyExceptionApplication? AppliedException)
|
||||
PolicyExceptionApplication? AppliedException,
|
||||
ConfidenceScore? Confidence,
|
||||
PolicyFailureReason? FailureReason = null,
|
||||
string? FailureMessage = null,
|
||||
BudgetStatusSummary? UnknownBudgetStatus = null)
|
||||
{
|
||||
public static PolicyEvaluationResult CreateDefault(string? severity) => new(
|
||||
Matched: false,
|
||||
@@ -110,7 +138,13 @@ internal sealed record PolicyEvaluationResult(
|
||||
Priority: null,
|
||||
Annotations: ImmutableDictionary<string, string>.Empty,
|
||||
Warnings: ImmutableArray<string>.Empty,
|
||||
AppliedException: null);
|
||||
AppliedException: null,
|
||||
Confidence: null);
|
||||
}
|
||||
|
||||
internal enum PolicyFailureReason
|
||||
{
|
||||
UnknownBudgetExceeded
|
||||
}
|
||||
|
||||
internal sealed record PolicyEvaluationExceptions(
|
||||
|
||||
@@ -3,7 +3,15 @@ using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Policy;
|
||||
using StellaOps.Policy.Confidence.Configuration;
|
||||
using StellaOps.Policy.Confidence.Models;
|
||||
using StellaOps.Policy.Confidence.Services;
|
||||
using StellaOps.Policy.Unknowns.Models;
|
||||
using StellaOps.Policy.Unknowns.Services;
|
||||
using StellaOps.PolicyDsl;
|
||||
|
||||
namespace StellaOps.Policy.Engine.Evaluation;
|
||||
@@ -13,6 +21,19 @@ namespace StellaOps.Policy.Engine.Evaluation;
|
||||
/// </summary>
|
||||
internal sealed class PolicyEvaluator
|
||||
{
|
||||
private readonly IConfidenceCalculator _confidenceCalculator;
|
||||
private readonly IUnknownBudgetService? _budgetService;
|
||||
|
||||
public PolicyEvaluator(
|
||||
IConfidenceCalculator? confidenceCalculator = null,
|
||||
IUnknownBudgetService? budgetService = null)
|
||||
{
|
||||
_confidenceCalculator = confidenceCalculator
|
||||
?? new ConfidenceCalculator(
|
||||
new StaticOptionsMonitor<ConfidenceWeightOptions>(new ConfidenceWeightOptions()));
|
||||
_budgetService = budgetService;
|
||||
}
|
||||
|
||||
public PolicyEvaluationResult Evaluate(PolicyEvaluationRequest request)
|
||||
{
|
||||
if (request is null)
|
||||
@@ -59,13 +80,18 @@ internal sealed class PolicyEvaluator
|
||||
Priority: rule.Priority,
|
||||
Annotations: runtime.Annotations.ToImmutableDictionary(StringComparer.OrdinalIgnoreCase),
|
||||
Warnings: runtime.Warnings.ToImmutableArray(),
|
||||
AppliedException: null);
|
||||
AppliedException: null,
|
||||
Confidence: null);
|
||||
|
||||
return ApplyExceptions(request, baseResult);
|
||||
var result = ApplyExceptions(request, baseResult);
|
||||
var budgeted = ApplyUnknownBudget(request.Context, result);
|
||||
return ApplyConfidence(request.Context, budgeted);
|
||||
}
|
||||
|
||||
var defaultResult = PolicyEvaluationResult.CreateDefault(request.Context.Severity.Normalized);
|
||||
return ApplyExceptions(request, defaultResult);
|
||||
var defaultWithExceptions = ApplyExceptions(request, defaultResult);
|
||||
var budgetedDefault = ApplyUnknownBudget(request.Context, defaultWithExceptions);
|
||||
return ApplyConfidence(request.Context, budgetedDefault);
|
||||
}
|
||||
|
||||
private static void ApplyAction(
|
||||
@@ -417,4 +443,314 @@ internal sealed class PolicyEvaluator
|
||||
AppliedException = application,
|
||||
};
|
||||
}
|
||||
|
||||
private PolicyEvaluationResult ApplyUnknownBudget(PolicyEvaluationContext context, PolicyEvaluationResult baseResult)
|
||||
{
|
||||
if (_budgetService is null || context.Unknowns.IsDefaultOrEmpty)
|
||||
{
|
||||
return baseResult;
|
||||
}
|
||||
|
||||
var environment = ResolveEnvironmentName(context.Environment);
|
||||
var budgetResult = _budgetService.CheckBudgetWithEscalation(
|
||||
environment,
|
||||
context.Unknowns,
|
||||
context.ExceptionObjects);
|
||||
var status = _budgetService.GetBudgetStatus(environment, context.Unknowns);
|
||||
|
||||
var annotations = baseResult.Annotations.ToBuilder();
|
||||
annotations["unknownBudget.environment"] = environment;
|
||||
annotations["unknownBudget.total"] = budgetResult.TotalUnknowns.ToString(CultureInfo.InvariantCulture);
|
||||
annotations["unknownBudget.action"] = budgetResult.RecommendedAction.ToString();
|
||||
if (budgetResult.TotalLimit.HasValue)
|
||||
{
|
||||
annotations["unknownBudget.totalLimit"] = budgetResult.TotalLimit.Value.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
annotations["unknownBudget.exceeded"] = (!budgetResult.IsWithinBudget).ToString();
|
||||
if (!string.IsNullOrWhiteSpace(budgetResult.Message))
|
||||
{
|
||||
annotations["unknownBudget.message"] = budgetResult.Message!;
|
||||
}
|
||||
|
||||
var warnings = baseResult.Warnings;
|
||||
if (!budgetResult.IsWithinBudget
|
||||
&& budgetResult.RecommendedAction is BudgetAction.Warn or BudgetAction.WarnUnlessException
|
||||
&& !string.IsNullOrWhiteSpace(budgetResult.Message))
|
||||
{
|
||||
warnings = warnings.Add(budgetResult.Message!);
|
||||
}
|
||||
|
||||
var result = baseResult with
|
||||
{
|
||||
Annotations = annotations.ToImmutable(),
|
||||
Warnings = warnings,
|
||||
UnknownBudgetStatus = status
|
||||
};
|
||||
|
||||
if (_budgetService.ShouldBlock(budgetResult))
|
||||
{
|
||||
result = result with
|
||||
{
|
||||
Status = "blocked",
|
||||
FailureReason = PolicyFailureReason.UnknownBudgetExceeded,
|
||||
FailureMessage = budgetResult.Message ?? "Unknown budget exceeded"
|
||||
};
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static string ResolveEnvironmentName(PolicyEvaluationEnvironment environment)
|
||||
{
|
||||
var name = environment.Get("name") ?? environment.Get("environment") ?? environment.Get("env");
|
||||
return string.IsNullOrWhiteSpace(name) ? "default" : name.Trim();
|
||||
}
|
||||
|
||||
private PolicyEvaluationResult ApplyConfidence(PolicyEvaluationContext context, PolicyEvaluationResult baseResult)
|
||||
{
|
||||
var input = BuildConfidenceInput(context, baseResult);
|
||||
var confidence = _confidenceCalculator.Calculate(input);
|
||||
return baseResult with { Confidence = confidence };
|
||||
}
|
||||
|
||||
private static ConfidenceInput BuildConfidenceInput(PolicyEvaluationContext context, PolicyEvaluationResult result)
|
||||
{
|
||||
return new ConfidenceInput
|
||||
{
|
||||
Reachability = BuildReachabilityEvidence(context.Reachability),
|
||||
Runtime = BuildRuntimeEvidence(context),
|
||||
Vex = BuildVexEvidence(context),
|
||||
Provenance = BuildProvenanceEvidence(context),
|
||||
Policy = BuildPolicyEvidence(context, result),
|
||||
Status = result.Status,
|
||||
EvaluationTimestamp = context.Now
|
||||
};
|
||||
}
|
||||
|
||||
private static ReachabilityEvidence? BuildReachabilityEvidence(PolicyEvaluationReachability reachability)
|
||||
{
|
||||
if (reachability.IsUnknown && string.IsNullOrWhiteSpace(reachability.EvidenceRef))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var state = reachability.IsReachable
|
||||
? (reachability.HasRuntimeEvidence ? ReachabilityState.ConfirmedReachable : ReachabilityState.StaticReachable)
|
||||
: reachability.IsUnreachable
|
||||
? (reachability.HasRuntimeEvidence ? ReachabilityState.ConfirmedUnreachable : ReachabilityState.StaticUnreachable)
|
||||
: ReachabilityState.Unknown;
|
||||
|
||||
var digests = string.IsNullOrWhiteSpace(reachability.EvidenceRef)
|
||||
? Array.Empty<string>()
|
||||
: new[] { reachability.EvidenceRef! };
|
||||
|
||||
return new ReachabilityEvidence
|
||||
{
|
||||
State = state,
|
||||
AnalysisConfidence = Clamp01(reachability.Confidence),
|
||||
GraphDigests = digests
|
||||
};
|
||||
}
|
||||
|
||||
private static RuntimeEvidence? BuildRuntimeEvidence(PolicyEvaluationContext context)
|
||||
{
|
||||
if (!context.Reachability.HasRuntimeEvidence)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var posture = context.Reachability.IsReachable || context.Reachability.IsUnreachable
|
||||
? RuntimePosture.Supports
|
||||
: RuntimePosture.Unknown;
|
||||
|
||||
return new RuntimeEvidence
|
||||
{
|
||||
Posture = posture,
|
||||
ObservationCount = 1,
|
||||
LastObserved = context.Now,
|
||||
SessionDigests = Array.Empty<string>()
|
||||
};
|
||||
}
|
||||
|
||||
private static VexEvidence? BuildVexEvidence(PolicyEvaluationContext context)
|
||||
{
|
||||
if (context.Vex.Statements.IsDefaultOrEmpty)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var issuer = string.IsNullOrWhiteSpace(context.Advisory.Source)
|
||||
? "unknown"
|
||||
: context.Advisory.Source;
|
||||
|
||||
var statements = context.Vex.Statements
|
||||
.Select(statement =>
|
||||
{
|
||||
var timestamp = statement.Timestamp ?? DateTimeOffset.MinValue;
|
||||
return new VexStatement
|
||||
{
|
||||
Status = MapVexStatus(statement.Status),
|
||||
Issuer = issuer,
|
||||
TrustScore = ComputeVexTrustScore(issuer, statement),
|
||||
Timestamp = timestamp,
|
||||
StatementDigest = ComputeVexDigest(issuer, statement, timestamp)
|
||||
};
|
||||
})
|
||||
.ToList();
|
||||
|
||||
return new VexEvidence { Statements = statements };
|
||||
}
|
||||
|
||||
private static ProvenanceEvidence? BuildProvenanceEvidence(PolicyEvaluationContext context)
|
||||
{
|
||||
var hasSbomComponents = !context.Sbom.Components.IsDefaultOrEmpty;
|
||||
if (context.ProvenanceAttested is null && !hasSbomComponents)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var level = context.ProvenanceAttested == true ? ProvenanceLevel.Signed : ProvenanceLevel.Unsigned;
|
||||
|
||||
return new ProvenanceEvidence
|
||||
{
|
||||
Level = level,
|
||||
SbomCompleteness = ComputeSbomCompleteness(context.Sbom),
|
||||
AttestationDigests = Array.Empty<string>()
|
||||
};
|
||||
}
|
||||
|
||||
private static PolicyEvidence BuildPolicyEvidence(PolicyEvaluationContext context, PolicyEvaluationResult result)
|
||||
{
|
||||
var ruleName = result.RuleName ?? "default";
|
||||
var matchStrength = result.Matched ? 0.9m : 0.6m;
|
||||
|
||||
return new PolicyEvidence
|
||||
{
|
||||
RuleName = ruleName,
|
||||
MatchStrength = Clamp01(matchStrength),
|
||||
EvaluationDigest = ComputePolicyEvaluationDigest(context.PolicyDigest, result)
|
||||
};
|
||||
}
|
||||
|
||||
private static VexStatus MapVexStatus(string status)
|
||||
{
|
||||
if (status.Equals("not_affected", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return VexStatus.NotAffected;
|
||||
}
|
||||
|
||||
if (status.Equals("fixed", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return VexStatus.Fixed;
|
||||
}
|
||||
|
||||
if (status.Equals("under_investigation", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return VexStatus.UnderInvestigation;
|
||||
}
|
||||
|
||||
if (status.Equals("affected", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return VexStatus.Affected;
|
||||
}
|
||||
|
||||
return VexStatus.UnderInvestigation;
|
||||
}
|
||||
|
||||
private static decimal ComputeVexTrustScore(string issuer, PolicyEvaluationVexStatement statement)
|
||||
{
|
||||
var score = issuer.Contains("vendor", StringComparison.OrdinalIgnoreCase)
|
||||
|| issuer.Contains("distro", StringComparison.OrdinalIgnoreCase)
|
||||
? 0.85m
|
||||
: 0.7m;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(statement.Justification))
|
||||
{
|
||||
score += 0.05m;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(statement.StatementId))
|
||||
{
|
||||
score += 0.05m;
|
||||
}
|
||||
|
||||
return Clamp01(score);
|
||||
}
|
||||
|
||||
private static string ComputeVexDigest(
|
||||
string issuer,
|
||||
PolicyEvaluationVexStatement statement,
|
||||
DateTimeOffset timestamp)
|
||||
{
|
||||
var input = $"{issuer}|{statement.Status}|{statement.Justification}|{statement.StatementId}|{timestamp:O}";
|
||||
Span<byte> hash = stackalloc byte[32];
|
||||
SHA256.HashData(Encoding.UTF8.GetBytes(input), hash);
|
||||
return $"sha256:{Convert.ToHexString(hash).ToLowerInvariant()}";
|
||||
}
|
||||
|
||||
private static decimal ComputeSbomCompleteness(PolicyEvaluationSbom sbom)
|
||||
{
|
||||
if (sbom.Components.IsDefaultOrEmpty)
|
||||
{
|
||||
return 0.4m;
|
||||
}
|
||||
|
||||
var count = sbom.Components.Length;
|
||||
return count switch
|
||||
{
|
||||
<= 5 => 0.6m,
|
||||
<= 20 => 0.75m,
|
||||
<= 100 => 0.85m,
|
||||
_ => 0.9m
|
||||
};
|
||||
}
|
||||
|
||||
private static string ComputePolicyEvaluationDigest(string? policyDigest, PolicyEvaluationResult result)
|
||||
{
|
||||
var input = string.Join(
|
||||
'|',
|
||||
policyDigest ?? "unknown",
|
||||
result.RuleName ?? "default",
|
||||
result.Status,
|
||||
result.Severity ?? "none",
|
||||
result.Priority?.ToString(CultureInfo.InvariantCulture) ?? "none");
|
||||
|
||||
Span<byte> hash = stackalloc byte[32];
|
||||
SHA256.HashData(Encoding.UTF8.GetBytes(input), hash);
|
||||
return $"sha256:{Convert.ToHexString(hash).ToLowerInvariant()}";
|
||||
}
|
||||
|
||||
private static decimal Clamp01(decimal value)
|
||||
{
|
||||
if (value <= 0m)
|
||||
{
|
||||
return 0m;
|
||||
}
|
||||
|
||||
if (value >= 1m)
|
||||
{
|
||||
return 1m;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private sealed class StaticOptionsMonitor<T> : IOptionsMonitor<T>
|
||||
{
|
||||
private readonly T _value;
|
||||
|
||||
public StaticOptionsMonitor(T value) => _value = value;
|
||||
|
||||
public T CurrentValue => _value;
|
||||
|
||||
public T Get(string? name) => _value;
|
||||
|
||||
public IDisposable OnChange(Action<T, string> listener) => NullDisposable.Instance;
|
||||
|
||||
private sealed class NullDisposable : IDisposable
|
||||
{
|
||||
public static readonly NullDisposable Instance = new();
|
||||
public void Dispose() { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user