175 lines
6.9 KiB
C#
175 lines
6.9 KiB
C#
using System.Collections.Immutable;
|
|
using System.Security.Cryptography;
|
|
using StellaOps.Policy;
|
|
|
|
namespace StellaOps.PolicyDsl;
|
|
|
|
/// <summary>
|
|
/// Compiles policy DSL source code into an intermediate representation.
|
|
/// </summary>
|
|
public sealed class PolicyCompiler
|
|
{
|
|
public PolicyCompilationResult Compile(string source)
|
|
{
|
|
if (source is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(source));
|
|
}
|
|
|
|
var parseResult = PolicyParser.Parse(source);
|
|
if (parseResult.Document is null)
|
|
{
|
|
return new PolicyCompilationResult(
|
|
Success: false,
|
|
Document: null,
|
|
Checksum: null,
|
|
CanonicalRepresentation: ImmutableArray<byte>.Empty,
|
|
Diagnostics: parseResult.Diagnostics);
|
|
}
|
|
|
|
if (parseResult.Diagnostics.Any(static issue => issue.Severity == PolicyIssueSeverity.Error))
|
|
{
|
|
return new PolicyCompilationResult(
|
|
Success: false,
|
|
Document: null,
|
|
Checksum: null,
|
|
CanonicalRepresentation: ImmutableArray<byte>.Empty,
|
|
Diagnostics: parseResult.Diagnostics);
|
|
}
|
|
|
|
var irDocument = BuildIntermediateRepresentation(parseResult.Document);
|
|
var canonical = PolicyIrSerializer.Serialize(irDocument);
|
|
var checksum = Convert.ToHexString(SHA256.HashData(canonical.AsSpan())).ToLowerInvariant();
|
|
|
|
return new PolicyCompilationResult(
|
|
Success: true,
|
|
Document: irDocument,
|
|
Checksum: checksum,
|
|
CanonicalRepresentation: canonical,
|
|
Diagnostics: parseResult.Diagnostics);
|
|
}
|
|
|
|
private static PolicyIrDocument BuildIntermediateRepresentation(PolicyDocumentNode node)
|
|
{
|
|
var metadata = node.Metadata
|
|
.OrderBy(static kvp => kvp.Key, StringComparer.Ordinal)
|
|
.ToImmutableSortedDictionary(static kvp => kvp.Key, kvp => ToIrLiteral(kvp.Value), StringComparer.Ordinal);
|
|
|
|
var settings = node.Settings
|
|
.OrderBy(static kvp => kvp.Key, StringComparer.Ordinal)
|
|
.ToImmutableSortedDictionary(static kvp => kvp.Key, kvp => ToIrLiteral(kvp.Value), StringComparer.Ordinal);
|
|
|
|
var profiles = ImmutableArray.CreateBuilder<PolicyIrProfile>(node.Profiles.Length);
|
|
foreach (var profile in node.Profiles)
|
|
{
|
|
var maps = ImmutableArray.CreateBuilder<PolicyIrProfileMap>();
|
|
var envs = ImmutableArray.CreateBuilder<PolicyIrProfileEnv>();
|
|
var scalars = ImmutableArray.CreateBuilder<PolicyIrProfileScalar>();
|
|
|
|
foreach (var item in profile.Items)
|
|
{
|
|
switch (item)
|
|
{
|
|
case PolicyProfileMapNode map:
|
|
maps.Add(new PolicyIrProfileMap(
|
|
map.Name,
|
|
map.Entries
|
|
.Select(entry => new PolicyIrProfileMapEntry(entry.Source, entry.Weight))
|
|
.ToImmutableArray()));
|
|
break;
|
|
case PolicyProfileEnvNode env:
|
|
envs.Add(new PolicyIrProfileEnv(
|
|
env.Name,
|
|
env.Entries
|
|
.Select(entry => new PolicyIrProfileEnvEntry(entry.Condition, entry.Weight))
|
|
.ToImmutableArray()));
|
|
break;
|
|
case PolicyProfileScalarNode scalar:
|
|
scalars.Add(new PolicyIrProfileScalar(scalar.Name, ToIrLiteral(scalar.Value)));
|
|
break;
|
|
}
|
|
}
|
|
|
|
profiles.Add(new PolicyIrProfile(
|
|
profile.Name,
|
|
maps.ToImmutable(),
|
|
envs.ToImmutable(),
|
|
scalars.ToImmutable()));
|
|
}
|
|
|
|
var rules = ImmutableArray.CreateBuilder<PolicyIrRule>(node.Rules.Length);
|
|
foreach (var rule in node.Rules)
|
|
{
|
|
var thenActions = ImmutableArray.CreateBuilder<PolicyIrAction>(rule.ThenActions.Length);
|
|
foreach (var action in rule.ThenActions)
|
|
{
|
|
var converted = ToIrAction(action);
|
|
if (converted is not null)
|
|
{
|
|
thenActions.Add(converted);
|
|
}
|
|
}
|
|
|
|
var elseActions = ImmutableArray.CreateBuilder<PolicyIrAction>(rule.ElseActions.Length);
|
|
foreach (var action in rule.ElseActions)
|
|
{
|
|
var converted = ToIrAction(action);
|
|
if (converted is not null)
|
|
{
|
|
elseActions.Add(converted);
|
|
}
|
|
}
|
|
|
|
rules.Add(new PolicyIrRule(
|
|
rule.Name,
|
|
rule.Priority,
|
|
rule.When,
|
|
thenActions.ToImmutable(),
|
|
elseActions.ToImmutable(),
|
|
rule.Because ?? string.Empty));
|
|
}
|
|
|
|
return new PolicyIrDocument(
|
|
node.Name,
|
|
node.Syntax,
|
|
metadata,
|
|
profiles.ToImmutable(),
|
|
settings,
|
|
rules.ToImmutable());
|
|
}
|
|
|
|
private static PolicyIrLiteral ToIrLiteral(PolicyLiteralValue value) => value switch
|
|
{
|
|
PolicyStringLiteral s => new PolicyIrStringLiteral(s.Value),
|
|
PolicyNumberLiteral n => new PolicyIrNumberLiteral(n.Value),
|
|
PolicyBooleanLiteral b => new PolicyIrBooleanLiteral(b.Value),
|
|
PolicyListLiteral list => new PolicyIrListLiteral(list.Items.Select(ToIrLiteral).ToImmutableArray()),
|
|
_ => new PolicyIrStringLiteral(string.Empty),
|
|
};
|
|
|
|
private static PolicyIrAction? ToIrAction(PolicyActionNode action) => action switch
|
|
{
|
|
PolicyAssignmentActionNode assign => new PolicyIrAssignmentAction(assign.Target.Segments, assign.Value),
|
|
PolicyAnnotateActionNode annotate => new PolicyIrAnnotateAction(annotate.Target.Segments, annotate.Value),
|
|
PolicyIgnoreActionNode ignore => new PolicyIrIgnoreAction(ignore.Until, ignore.Because),
|
|
PolicyEscalateActionNode escalate => new PolicyIrEscalateAction(escalate.To, escalate.When),
|
|
PolicyRequireVexActionNode require => new PolicyIrRequireVexAction(
|
|
require.Conditions
|
|
.OrderBy(static kvp => kvp.Key, StringComparer.Ordinal)
|
|
.ToImmutableSortedDictionary(static kvp => kvp.Key, kvp => kvp.Value, StringComparer.Ordinal)),
|
|
PolicyWarnActionNode warn => new PolicyIrWarnAction(warn.Message),
|
|
PolicyDeferActionNode defer => new PolicyIrDeferAction(defer.Until),
|
|
_ => null,
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Result of compiling a policy DSL source.
|
|
/// </summary>
|
|
public sealed record PolicyCompilationResult(
|
|
bool Success,
|
|
PolicyIrDocument? Document,
|
|
string? Checksum,
|
|
ImmutableArray<byte> CanonicalRepresentation,
|
|
ImmutableArray<PolicyIssue> Diagnostics);
|