Refactor code structure and optimize performance across multiple modules
This commit is contained in:
@@ -0,0 +1,769 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StellaOps.Policy.TrustLattice;
|
||||
|
||||
namespace StellaOps.AdvisoryAI.PolicyStudio;
|
||||
|
||||
/// <summary>
|
||||
/// Interface for compiling AI-generated rules into versioned, signed policy bundles.
|
||||
/// Sprint: SPRINT_20251226_017_AI_policy_copilot
|
||||
/// Task: POLICY-13
|
||||
/// </summary>
|
||||
public interface IPolicyBundleCompiler
|
||||
{
|
||||
/// <summary>
|
||||
/// Compiles lattice rules into a policy bundle.
|
||||
/// </summary>
|
||||
Task<PolicyCompilationResult> CompileAsync(
|
||||
PolicyCompilationRequest request,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Validates a compiled policy bundle.
|
||||
/// </summary>
|
||||
Task<PolicyValidationReport> ValidateAsync(
|
||||
PolicyBundle bundle,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Signs a compiled policy bundle.
|
||||
/// </summary>
|
||||
Task<SignedPolicyBundle> SignAsync(
|
||||
PolicyBundle bundle,
|
||||
PolicySigningOptions options,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request to compile rules into a policy bundle.
|
||||
/// </summary>
|
||||
public sealed record PolicyCompilationRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// Rules to compile.
|
||||
/// </summary>
|
||||
public required IReadOnlyList<LatticeRule> Rules { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Test cases to include.
|
||||
/// </summary>
|
||||
public IReadOnlyList<PolicyTestCase>? TestCases { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Policy bundle name.
|
||||
/// </summary>
|
||||
public required string Name { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Policy version.
|
||||
/// </summary>
|
||||
public string Version { get; init; } = "1.0.0";
|
||||
|
||||
/// <summary>
|
||||
/// Target policy pack ID (if extending existing).
|
||||
/// </summary>
|
||||
public string? TargetPolicyPack { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Trust roots to include.
|
||||
/// </summary>
|
||||
public IReadOnlyList<TrustRoot>? TrustRoots { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Trust requirements.
|
||||
/// </summary>
|
||||
public TrustRequirements? TrustRequirements { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether to validate before compilation.
|
||||
/// </summary>
|
||||
public bool ValidateBeforeCompile { get; init; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to run test cases.
|
||||
/// </summary>
|
||||
public bool RunTests { get; init; } = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of policy compilation.
|
||||
/// </summary>
|
||||
public sealed record PolicyCompilationResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether compilation was successful.
|
||||
/// </summary>
|
||||
public required bool Success { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Compiled policy bundle.
|
||||
/// </summary>
|
||||
public PolicyBundle? Bundle { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Compilation errors.
|
||||
/// </summary>
|
||||
public IReadOnlyList<string> Errors { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Compilation warnings.
|
||||
/// </summary>
|
||||
public IReadOnlyList<string> Warnings { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Validation report.
|
||||
/// </summary>
|
||||
public PolicyValidationReport? ValidationReport { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Test run report.
|
||||
/// </summary>
|
||||
public PolicyTestReport? TestReport { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Compilation timestamp (UTC ISO-8601).
|
||||
/// </summary>
|
||||
public required string CompiledAt { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Bundle digest.
|
||||
/// </summary>
|
||||
public string? BundleDigest { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validation report for a policy bundle.
|
||||
/// </summary>
|
||||
public sealed record PolicyValidationReport
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether validation passed.
|
||||
/// </summary>
|
||||
public required bool Valid { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Syntax valid.
|
||||
/// </summary>
|
||||
public bool SyntaxValid { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Semantics valid.
|
||||
/// </summary>
|
||||
public bool SemanticsValid { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Syntax errors.
|
||||
/// </summary>
|
||||
public IReadOnlyList<string> SyntaxErrors { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Semantic warnings.
|
||||
/// </summary>
|
||||
public IReadOnlyList<string> SemanticWarnings { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Rule conflicts detected.
|
||||
/// </summary>
|
||||
public IReadOnlyList<RuleConflict> Conflicts { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Coverage estimate (0.0 - 1.0).
|
||||
/// </summary>
|
||||
public double Coverage { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test report for a policy bundle.
|
||||
/// </summary>
|
||||
public sealed record PolicyTestReport
|
||||
{
|
||||
/// <summary>
|
||||
/// Total tests run.
|
||||
/// </summary>
|
||||
public int TotalTests { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Tests passed.
|
||||
/// </summary>
|
||||
public int Passed { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Tests failed.
|
||||
/// </summary>
|
||||
public int Failed { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Pass rate (0.0 - 1.0).
|
||||
/// </summary>
|
||||
public double PassRate => TotalTests > 0 ? (double)Passed / TotalTests : 0;
|
||||
|
||||
/// <summary>
|
||||
/// Failure details.
|
||||
/// </summary>
|
||||
public IReadOnlyList<TestFailure> Failures { get; init; } = [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test failure detail.
|
||||
/// </summary>
|
||||
public sealed record TestFailure
|
||||
{
|
||||
public required string TestId { get; init; }
|
||||
public required string RuleId { get; init; }
|
||||
public required string Description { get; init; }
|
||||
public required string Expected { get; init; }
|
||||
public required string Actual { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Options for signing a policy bundle.
|
||||
/// </summary>
|
||||
public sealed record PolicySigningOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Key ID to use for signing.
|
||||
/// </summary>
|
||||
public string? KeyId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Crypto scheme (eidas, fips, gost, sm).
|
||||
/// </summary>
|
||||
public string? CryptoScheme { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Signer identity.
|
||||
/// </summary>
|
||||
public string? SignerIdentity { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Include timestamp.
|
||||
/// </summary>
|
||||
public bool IncludeTimestamp { get; init; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Timestamping authority URL.
|
||||
/// </summary>
|
||||
public string? TimestampAuthority { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signed policy bundle.
|
||||
/// </summary>
|
||||
public sealed record SignedPolicyBundle
|
||||
{
|
||||
/// <summary>
|
||||
/// The policy bundle.
|
||||
/// </summary>
|
||||
public required PolicyBundle Bundle { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Bundle content hash.
|
||||
/// </summary>
|
||||
public required string ContentDigest { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Signature bytes (base64).
|
||||
/// </summary>
|
||||
public required string Signature { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Signing algorithm used.
|
||||
/// </summary>
|
||||
public required string Algorithm { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Key ID used for signing.
|
||||
/// </summary>
|
||||
public string? KeyId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Signer identity.
|
||||
/// </summary>
|
||||
public string? SignerIdentity { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Signature timestamp (UTC ISO-8601).
|
||||
/// </summary>
|
||||
public string? SignedAt { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Timestamp token (if requested).
|
||||
/// </summary>
|
||||
public string? TimestampToken { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Certificate chain (PEM).
|
||||
/// </summary>
|
||||
public string? CertificateChain { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compiles AI-generated rules into versioned, signed policy bundles.
|
||||
/// Sprint: SPRINT_20251226_017_AI_policy_copilot
|
||||
/// Task: POLICY-13
|
||||
/// </summary>
|
||||
public sealed class PolicyBundleCompiler : IPolicyBundleCompiler
|
||||
{
|
||||
private static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.Web)
|
||||
{
|
||||
WriteIndented = false,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
|
||||
};
|
||||
|
||||
private readonly IPolicyRuleGenerator _ruleGenerator;
|
||||
private readonly IPolicyBundleSigner? _signer;
|
||||
private readonly ILogger<PolicyBundleCompiler> _logger;
|
||||
|
||||
public PolicyBundleCompiler(
|
||||
IPolicyRuleGenerator ruleGenerator,
|
||||
IPolicyBundleSigner? signer,
|
||||
ILogger<PolicyBundleCompiler> logger)
|
||||
{
|
||||
_ruleGenerator = ruleGenerator ?? throw new ArgumentNullException(nameof(ruleGenerator));
|
||||
_signer = signer;
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
public async Task<PolicyCompilationResult> CompileAsync(
|
||||
PolicyCompilationRequest request,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
_logger.LogInformation("Compiling policy bundle '{Name}' with {RuleCount} rules",
|
||||
request.Name, request.Rules.Count);
|
||||
|
||||
var errors = new List<string>();
|
||||
var warnings = new List<string>();
|
||||
PolicyValidationReport? validationReport = null;
|
||||
PolicyTestReport? testReport = null;
|
||||
|
||||
// Step 1: Validate rules if requested
|
||||
if (request.ValidateBeforeCompile)
|
||||
{
|
||||
var validationResult = await _ruleGenerator.ValidateAsync(
|
||||
request.Rules, null, cancellationToken);
|
||||
|
||||
validationReport = new PolicyValidationReport
|
||||
{
|
||||
Valid = validationResult.Valid,
|
||||
SyntaxValid = validationResult.Valid,
|
||||
SemanticsValid = validationResult.Conflicts.Count == 0,
|
||||
Conflicts = validationResult.Conflicts,
|
||||
SemanticWarnings = validationResult.UnreachableConditions.Concat(validationResult.PotentialLoops).ToList(),
|
||||
Coverage = validationResult.Coverage
|
||||
};
|
||||
|
||||
if (!validationResult.Valid)
|
||||
{
|
||||
errors.AddRange(validationResult.Conflicts.Select(c =>
|
||||
$"Rule conflict: {c.Description}"));
|
||||
errors.AddRange(validationResult.UnreachableConditions);
|
||||
errors.AddRange(validationResult.PotentialLoops);
|
||||
}
|
||||
|
||||
warnings.AddRange(validationResult.UnreachableConditions);
|
||||
}
|
||||
|
||||
// Step 2: Run tests if requested
|
||||
if (request.RunTests && request.TestCases?.Count > 0)
|
||||
{
|
||||
testReport = RunTests(request.Rules, request.TestCases);
|
||||
|
||||
if (testReport.Failed > 0)
|
||||
{
|
||||
warnings.Add($"{testReport.Failed} of {testReport.TotalTests} tests failed");
|
||||
}
|
||||
}
|
||||
|
||||
// Check for blocking errors
|
||||
if (errors.Count > 0)
|
||||
{
|
||||
return new PolicyCompilationResult
|
||||
{
|
||||
Success = false,
|
||||
Errors = errors,
|
||||
Warnings = warnings,
|
||||
ValidationReport = validationReport,
|
||||
TestReport = testReport,
|
||||
CompiledAt = DateTime.UtcNow.ToString("O")
|
||||
};
|
||||
}
|
||||
|
||||
// Step 3: Build the policy bundle
|
||||
var bundle = BuildBundle(request);
|
||||
|
||||
// Step 4: Compute bundle digest
|
||||
var bundleDigest = ComputeBundleDigest(bundle);
|
||||
|
||||
_logger.LogInformation("Compiled policy bundle '{Name}' v{Version} with digest {Digest}",
|
||||
bundle.Name, bundle.Version, bundleDigest);
|
||||
|
||||
return new PolicyCompilationResult
|
||||
{
|
||||
Success = true,
|
||||
Bundle = bundle,
|
||||
Errors = errors,
|
||||
Warnings = warnings,
|
||||
ValidationReport = validationReport,
|
||||
TestReport = testReport,
|
||||
CompiledAt = DateTime.UtcNow.ToString("O"),
|
||||
BundleDigest = bundleDigest
|
||||
};
|
||||
}
|
||||
|
||||
public Task<PolicyValidationReport> ValidateAsync(
|
||||
PolicyBundle bundle,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var syntaxErrors = new List<string>();
|
||||
var semanticWarnings = new List<string>();
|
||||
var conflicts = new List<RuleConflict>();
|
||||
|
||||
// Validate trust roots
|
||||
foreach (var root in bundle.TrustRoots)
|
||||
{
|
||||
if (root.ExpiresAt.HasValue && root.ExpiresAt.Value < DateTimeOffset.UtcNow)
|
||||
{
|
||||
semanticWarnings.Add($"Trust root '{root.Principal.Id}' has expired");
|
||||
}
|
||||
}
|
||||
|
||||
// Validate custom rules
|
||||
foreach (var rule in bundle.CustomRules)
|
||||
{
|
||||
if (string.IsNullOrEmpty(rule.Name))
|
||||
{
|
||||
syntaxErrors.Add($"Rule is missing a name");
|
||||
}
|
||||
}
|
||||
|
||||
// Check for rule conflicts
|
||||
var rules = bundle.CustomRules.ToList();
|
||||
for (int i = 0; i < rules.Count; i++)
|
||||
{
|
||||
for (int j = i + 1; j < rules.Count; j++)
|
||||
{
|
||||
// Simple overlap check based on atom patterns
|
||||
if (HasOverlappingAtoms(rules[i], rules[j]))
|
||||
{
|
||||
conflicts.Add(new RuleConflict
|
||||
{
|
||||
RuleId1 = rules[i].Name,
|
||||
RuleId2 = rules[j].Name,
|
||||
Description = "Rules may have overlapping conditions",
|
||||
SuggestedResolution = "Review rule priorities",
|
||||
Severity = "warning"
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Task.FromResult(new PolicyValidationReport
|
||||
{
|
||||
Valid = syntaxErrors.Count == 0,
|
||||
SyntaxValid = syntaxErrors.Count == 0,
|
||||
SemanticsValid = conflicts.Count == 0,
|
||||
SyntaxErrors = syntaxErrors,
|
||||
SemanticWarnings = semanticWarnings,
|
||||
Conflicts = conflicts,
|
||||
Coverage = EstimateCoverage(bundle)
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<SignedPolicyBundle> SignAsync(
|
||||
PolicyBundle bundle,
|
||||
PolicySigningOptions options,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var contentDigest = ComputeBundleDigest(bundle);
|
||||
|
||||
if (_signer is null)
|
||||
{
|
||||
_logger.LogWarning("No signer configured, returning unsigned bundle");
|
||||
return new SignedPolicyBundle
|
||||
{
|
||||
Bundle = bundle,
|
||||
ContentDigest = contentDigest,
|
||||
Signature = string.Empty,
|
||||
Algorithm = "none",
|
||||
SignedAt = DateTime.UtcNow.ToString("O")
|
||||
};
|
||||
}
|
||||
|
||||
var signature = await _signer.SignAsync(contentDigest, options, cancellationToken);
|
||||
|
||||
_logger.LogInformation("Signed policy bundle '{Name}' with key {KeyId}",
|
||||
bundle.Name, options.KeyId);
|
||||
|
||||
return new SignedPolicyBundle
|
||||
{
|
||||
Bundle = bundle,
|
||||
ContentDigest = contentDigest,
|
||||
Signature = signature.SignatureBase64,
|
||||
Algorithm = signature.Algorithm,
|
||||
KeyId = options.KeyId,
|
||||
SignerIdentity = options.SignerIdentity,
|
||||
SignedAt = DateTime.UtcNow.ToString("O"),
|
||||
CertificateChain = signature.CertificateChain
|
||||
};
|
||||
}
|
||||
|
||||
private PolicyBundle BuildBundle(PolicyCompilationRequest request)
|
||||
{
|
||||
// Convert LatticeRules to SelectionRules
|
||||
var customRules = request.Rules.Select(ConvertToSelectionRule).ToList();
|
||||
|
||||
return new PolicyBundle
|
||||
{
|
||||
Id = $"bundle:{ComputeHash(request.Name)[..12]}",
|
||||
Name = request.Name,
|
||||
Version = request.Version,
|
||||
TrustRoots = request.TrustRoots ?? [],
|
||||
TrustRequirements = request.TrustRequirements ?? new TrustRequirements(),
|
||||
CustomRules = customRules,
|
||||
ConflictResolution = ConflictResolution.ReportConflict,
|
||||
AssumeReachableWhenUnknown = true
|
||||
};
|
||||
}
|
||||
|
||||
private static SelectionRule ConvertToSelectionRule(LatticeRule rule)
|
||||
{
|
||||
// Map disposition string to Disposition enum
|
||||
var disposition = rule.Disposition.ToLowerInvariant() switch
|
||||
{
|
||||
"block" or "exploitable" => Disposition.Exploitable,
|
||||
"allow" or "resolved" => Disposition.Resolved,
|
||||
"resolved_with_pedigree" => Disposition.ResolvedWithPedigree,
|
||||
"not_affected" => Disposition.NotAffected,
|
||||
"false_positive" => Disposition.FalsePositive,
|
||||
"warn" or "in_triage" or _ => Disposition.InTriage
|
||||
};
|
||||
|
||||
// Build condition function from lattice expression
|
||||
var condition = BuildConditionFromExpression(rule.LatticeExpression);
|
||||
|
||||
return new SelectionRule
|
||||
{
|
||||
Name = rule.Name,
|
||||
Priority = rule.Priority,
|
||||
Disposition = disposition,
|
||||
ConditionDescription = rule.LatticeExpression,
|
||||
Condition = condition,
|
||||
ExplanationTemplate = rule.Description
|
||||
};
|
||||
}
|
||||
|
||||
private static Func<IReadOnlyDictionary<SecurityAtom, K4Value>, bool> BuildConditionFromExpression(string latticeExpression)
|
||||
{
|
||||
// Parse lattice expression and build condition function
|
||||
// This is a simplified parser - production would use proper expression parsing
|
||||
var expr = latticeExpression.ToUpperInvariant();
|
||||
|
||||
return atoms =>
|
||||
{
|
||||
// Check for negated atoms first
|
||||
if (expr.Contains("¬REACHABLE") || expr.Contains("NOT REACHABLE") || expr.Contains("!REACHABLE"))
|
||||
{
|
||||
if (atoms.TryGetValue(SecurityAtom.Reachable, out var r) && r != K4Value.False)
|
||||
return false;
|
||||
}
|
||||
else if (expr.Contains("REACHABLE"))
|
||||
{
|
||||
if (atoms.TryGetValue(SecurityAtom.Reachable, out var r) && r != K4Value.True)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (expr.Contains("¬PRESENT") || expr.Contains("NOT PRESENT") || expr.Contains("!PRESENT"))
|
||||
{
|
||||
if (atoms.TryGetValue(SecurityAtom.Present, out var p) && p != K4Value.False)
|
||||
return false;
|
||||
}
|
||||
else if (expr.Contains("PRESENT"))
|
||||
{
|
||||
if (atoms.TryGetValue(SecurityAtom.Present, out var p) && p != K4Value.True)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (expr.Contains("¬APPLIES") || expr.Contains("NOT APPLIES") || expr.Contains("!APPLIES"))
|
||||
{
|
||||
if (atoms.TryGetValue(SecurityAtom.Applies, out var a) && a != K4Value.False)
|
||||
return false;
|
||||
}
|
||||
else if (expr.Contains("APPLIES"))
|
||||
{
|
||||
if (atoms.TryGetValue(SecurityAtom.Applies, out var a) && a != K4Value.True)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (expr.Contains("MITIGATED"))
|
||||
{
|
||||
if (atoms.TryGetValue(SecurityAtom.Mitigated, out var m) && m != K4Value.True)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (expr.Contains("FIXED"))
|
||||
{
|
||||
if (atoms.TryGetValue(SecurityAtom.Fixed, out var f) && f != K4Value.True)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (expr.Contains("MISATTRIBUTED"))
|
||||
{
|
||||
if (atoms.TryGetValue(SecurityAtom.Misattributed, out var m) && m != K4Value.True)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extract referenced atoms from a lattice expression for overlap detection.
|
||||
/// </summary>
|
||||
private static HashSet<SecurityAtom> ExtractAtomsFromExpression(string expression)
|
||||
{
|
||||
var atoms = new HashSet<SecurityAtom>();
|
||||
var expr = expression.ToUpperInvariant();
|
||||
|
||||
if (expr.Contains("REACHABLE")) atoms.Add(SecurityAtom.Reachable);
|
||||
if (expr.Contains("PRESENT")) atoms.Add(SecurityAtom.Present);
|
||||
if (expr.Contains("APPLIES")) atoms.Add(SecurityAtom.Applies);
|
||||
if (expr.Contains("MITIGATED")) atoms.Add(SecurityAtom.Mitigated);
|
||||
if (expr.Contains("FIXED")) atoms.Add(SecurityAtom.Fixed);
|
||||
if (expr.Contains("MISATTRIBUTED")) atoms.Add(SecurityAtom.Misattributed);
|
||||
|
||||
return atoms;
|
||||
}
|
||||
|
||||
private PolicyTestReport RunTests(
|
||||
IReadOnlyList<LatticeRule> rules,
|
||||
IReadOnlyList<PolicyTestCase> testCases)
|
||||
{
|
||||
var failures = new List<TestFailure>();
|
||||
var passed = 0;
|
||||
|
||||
foreach (var test in testCases)
|
||||
{
|
||||
// Find all target rules for this test
|
||||
var targetRules = rules.Where(r => test.TargetRuleIds.Contains(r.RuleId)).ToList();
|
||||
if (targetRules.Count == 0)
|
||||
{
|
||||
failures.Add(new TestFailure
|
||||
{
|
||||
TestId = test.TestCaseId,
|
||||
RuleId = string.Join(",", test.TargetRuleIds),
|
||||
Description = "Target rules not found",
|
||||
Expected = test.ExpectedDisposition,
|
||||
Actual = "not_found"
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// Evaluate the test against the rules
|
||||
var result = EvaluateTest(targetRules, test);
|
||||
if (result == test.ExpectedDisposition)
|
||||
{
|
||||
passed++;
|
||||
}
|
||||
else
|
||||
{
|
||||
failures.Add(new TestFailure
|
||||
{
|
||||
TestId = test.TestCaseId,
|
||||
RuleId = string.Join(",", test.TargetRuleIds),
|
||||
Description = test.Description,
|
||||
Expected = test.ExpectedDisposition,
|
||||
Actual = result
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return new PolicyTestReport
|
||||
{
|
||||
TotalTests = testCases.Count,
|
||||
Passed = passed,
|
||||
Failed = failures.Count,
|
||||
Failures = failures
|
||||
};
|
||||
}
|
||||
|
||||
private static string EvaluateTest(IReadOnlyList<LatticeRule> rules, PolicyTestCase test)
|
||||
{
|
||||
// Simplified test evaluation - find highest priority matching rule
|
||||
// In production, use proper lattice engine with full atom evaluation
|
||||
var bestMatch = rules.OrderBy(r => r.Priority).FirstOrDefault();
|
||||
return bestMatch?.Disposition ?? "unknown";
|
||||
}
|
||||
|
||||
private static bool HasOverlappingAtoms(SelectionRule rule1, SelectionRule rule2)
|
||||
{
|
||||
// Extract atoms from condition descriptions (which contain the lattice expressions)
|
||||
var atoms1 = ExtractAtomsFromExpression(rule1.ConditionDescription);
|
||||
var atoms2 = ExtractAtomsFromExpression(rule2.ConditionDescription);
|
||||
return atoms1.Overlaps(atoms2);
|
||||
}
|
||||
|
||||
private static double EstimateCoverage(PolicyBundle bundle)
|
||||
{
|
||||
// Count distinct atoms referenced across all rules
|
||||
var atomsCovered = bundle.CustomRules
|
||||
.SelectMany(r => ExtractAtomsFromExpression(r.ConditionDescription))
|
||||
.Distinct()
|
||||
.Count();
|
||||
|
||||
// 6 possible security atoms, estimate coverage as percentage
|
||||
return Math.Min(1.0, (double)atomsCovered / 6.0);
|
||||
}
|
||||
|
||||
private static string ComputeBundleDigest(PolicyBundle bundle)
|
||||
{
|
||||
var json = JsonSerializer.Serialize(bundle, SerializerOptions);
|
||||
var bytes = SHA256.HashData(Encoding.UTF8.GetBytes(json));
|
||||
return $"sha256:{Convert.ToHexStringLower(bytes)}";
|
||||
}
|
||||
|
||||
private static string ComputeHash(string content)
|
||||
{
|
||||
var bytes = SHA256.HashData(Encoding.UTF8.GetBytes(content));
|
||||
return Convert.ToHexStringLower(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interface for signing policy bundles.
|
||||
/// </summary>
|
||||
public interface IPolicyBundleSigner
|
||||
{
|
||||
/// <summary>
|
||||
/// Signs content and returns signature.
|
||||
/// </summary>
|
||||
Task<PolicySignature> SignAsync(
|
||||
string contentDigest,
|
||||
PolicySigningOptions options,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Policy signature result.
|
||||
/// </summary>
|
||||
public sealed record PolicySignature
|
||||
{
|
||||
/// <summary>
|
||||
/// Signature bytes (base64).
|
||||
/// </summary>
|
||||
public required string SignatureBase64 { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Signing algorithm.
|
||||
/// </summary>
|
||||
public required string Algorithm { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Certificate chain (PEM).
|
||||
/// </summary>
|
||||
public string? CertificateChain { get; init; }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user