UI work to fill SBOM sourcing management gap. UI planning remaining functionality exposure. Work on CI/Tests stabilization
Introduces CGS determinism test runs to CI workflows for Windows, macOS, Linux, Alpine, and Debian, fulfilling CGS-008 cross-platform requirements. Updates local-ci scripts to support new smoke steps, test timeouts, progress intervals, and project slicing for improved test isolation and diagnostics.
This commit is contained in:
210
src/__Libraries/StellaOps.Verdict/PolicyLockGenerator.cs
Normal file
210
src/__Libraries/StellaOps.Verdict/PolicyLockGenerator.cs
Normal file
@@ -0,0 +1,210 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// PolicyLockGenerator.cs
|
||||
// Sprint: SPRINT_20251229_001_001_BE_cgs_infrastructure (CGS-006)
|
||||
// Task: Implement PolicyLock generator
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace StellaOps.Verdict;
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of policy lock generator.
|
||||
/// Freezes policy rules for deterministic verdict computation.
|
||||
/// </summary>
|
||||
public sealed class PolicyLockGenerator : IPolicyLockGenerator
|
||||
{
|
||||
private readonly ILogger<PolicyLockGenerator> _logger;
|
||||
private const string SchemaVersion = "1.0";
|
||||
private const string EngineVersion = "1.0.0";
|
||||
|
||||
// TODO: Inject actual policy repository when available
|
||||
// private readonly IPolicyRepository _policyRepository;
|
||||
|
||||
public PolicyLockGenerator(ILogger<PolicyLockGenerator> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async ValueTask<PolicyLock> GenerateAsync(
|
||||
string policyId,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
_logger.LogInformation("Generating policy lock for policy {PolicyId}", policyId);
|
||||
|
||||
// TODO: Query current policy configuration from PolicyRepository
|
||||
// For now, generate a placeholder lock
|
||||
var ruleHashes = await GenerateCurrentRuleHashesAsync(policyId, ct);
|
||||
|
||||
var policyLock = new PolicyLock(
|
||||
SchemaVersion: SchemaVersion,
|
||||
PolicyVersion: $"{policyId}-{DateTimeOffset.UtcNow:yyyyMMddHHmmss}",
|
||||
RuleHashes: ruleHashes,
|
||||
EngineVersion: EngineVersion,
|
||||
GeneratedAt: DateTimeOffset.UtcNow
|
||||
);
|
||||
|
||||
_logger.LogInformation(
|
||||
"Generated policy lock {Version} with {RuleCount} rules",
|
||||
policyLock.PolicyVersion,
|
||||
policyLock.RuleHashes.Count);
|
||||
|
||||
return policyLock;
|
||||
}
|
||||
|
||||
public async ValueTask<PolicyLock> GenerateForVersionAsync(
|
||||
string policyId,
|
||||
string version,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
_logger.LogInformation(
|
||||
"Generating policy lock for policy {PolicyId} version {Version}",
|
||||
policyId,
|
||||
version);
|
||||
|
||||
// TODO: Query specific policy version from PolicyRepository
|
||||
// For now, generate a placeholder lock
|
||||
var ruleHashes = await GenerateRuleHashesForVersionAsync(policyId, version, ct);
|
||||
|
||||
var policyLock = new PolicyLock(
|
||||
SchemaVersion: SchemaVersion,
|
||||
PolicyVersion: version,
|
||||
RuleHashes: ruleHashes,
|
||||
EngineVersion: EngineVersion,
|
||||
GeneratedAt: DateTimeOffset.UtcNow
|
||||
);
|
||||
|
||||
return policyLock;
|
||||
}
|
||||
|
||||
public ValueTask<PolicyLockValidation> ValidateAsync(
|
||||
PolicyLock policyLock,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
_logger.LogDebug("Validating policy lock {Version}", policyLock.PolicyVersion);
|
||||
|
||||
// Basic validation
|
||||
var errors = new List<string>();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(policyLock.SchemaVersion))
|
||||
errors.Add("SchemaVersion is required");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(policyLock.PolicyVersion))
|
||||
errors.Add("PolicyVersion is required");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(policyLock.EngineVersion))
|
||||
errors.Add("EngineVersion is required");
|
||||
|
||||
if (policyLock.RuleHashes.Count == 0)
|
||||
errors.Add("At least one rule hash is required");
|
||||
|
||||
if (policyLock.GeneratedAt > DateTimeOffset.UtcNow.AddMinutes(5))
|
||||
errors.Add("GeneratedAt timestamp is in the future");
|
||||
|
||||
// TODO: Validate rule hashes against stored policy configurations
|
||||
// For now, just basic validation
|
||||
var mismatched = new List<string>();
|
||||
foreach (var (ruleId, hash) in policyLock.RuleHashes)
|
||||
{
|
||||
if (!IsValidHash(hash))
|
||||
{
|
||||
mismatched.Add(ruleId);
|
||||
errors.Add($"Invalid hash format for rule {ruleId}");
|
||||
}
|
||||
}
|
||||
|
||||
var isValid = errors.Count == 0;
|
||||
var errorMessage = errors.Count > 0 ? string.Join("; ", errors) : null;
|
||||
|
||||
var result = new PolicyLockValidation(
|
||||
IsValid: isValid,
|
||||
ErrorMessage: errorMessage,
|
||||
MismatchedRules: mismatched
|
||||
);
|
||||
|
||||
if (isValid)
|
||||
_logger.LogDebug("Policy lock {Version} is valid", policyLock.PolicyVersion);
|
||||
else
|
||||
_logger.LogWarning("Policy lock {Version} validation failed: {Error}", policyLock.PolicyVersion, errorMessage);
|
||||
|
||||
return ValueTask.FromResult(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate rule hashes for current policy configuration.
|
||||
/// </summary>
|
||||
private async ValueTask<IReadOnlyDictionary<string, string>> GenerateCurrentRuleHashesAsync(
|
||||
string policyId,
|
||||
CancellationToken ct)
|
||||
{
|
||||
await Task.CompletedTask; // Placeholder for async policy fetch
|
||||
|
||||
// TODO: Fetch actual rules from PolicyRepository
|
||||
// For now, generate placeholder rules
|
||||
var rules = new Dictionary<string, string>
|
||||
{
|
||||
["rule.cve.severity.high"] = ComputeRuleHash("high-severity-rule", "v1.0"),
|
||||
["rule.cve.severity.critical"] = ComputeRuleHash("critical-severity-rule", "v1.0"),
|
||||
["rule.vex.consensus.threshold"] = ComputeRuleHash("vex-consensus-0.8", "v1.0"),
|
||||
["rule.reachability.direct"] = ComputeRuleHash("direct-reach-rule", "v1.0"),
|
||||
["rule.reachability.transitive"] = ComputeRuleHash("transitive-reach-rule", "v1.0")
|
||||
};
|
||||
|
||||
return rules;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate rule hashes for a specific policy version.
|
||||
/// </summary>
|
||||
private async ValueTask<IReadOnlyDictionary<string, string>> GenerateRuleHashesForVersionAsync(
|
||||
string policyId,
|
||||
string version,
|
||||
CancellationToken ct)
|
||||
{
|
||||
await Task.CompletedTask; // Placeholder for async policy fetch
|
||||
|
||||
// TODO: Fetch specific version rules from PolicyRepository
|
||||
// For now, return the same as current (placeholder)
|
||||
return await GenerateCurrentRuleHashesAsync(policyId, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compute deterministic hash for a rule.
|
||||
/// Uses SHA256 of canonical JSON representation.
|
||||
/// </summary>
|
||||
private static string ComputeRuleHash(string ruleDefinition, string ruleVersion)
|
||||
{
|
||||
var canonical = JsonSerializer.Serialize(new
|
||||
{
|
||||
definition = ruleDefinition,
|
||||
version = ruleVersion
|
||||
}, new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = false,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||
});
|
||||
|
||||
var bytes = Encoding.UTF8.GetBytes(canonical);
|
||||
var hash = SHA256.HashData(bytes);
|
||||
|
||||
return $"sha256:{Convert.ToHexString(hash).ToLowerInvariant()}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate hash format (sha256:hex).
|
||||
/// </summary>
|
||||
private static bool IsValidHash(string hash)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(hash))
|
||||
return false;
|
||||
|
||||
if (!hash.StartsWith("sha256:", StringComparison.Ordinal))
|
||||
return false;
|
||||
|
||||
var hex = hash["sha256:".Length..];
|
||||
return hex.Length == 64 && hex.All(c => char.IsAsciiHexDigit(c));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user