up
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Mirror Thin Bundle Sign & Verify / mirror-sign (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled

This commit is contained in:
StellaOps Bot
2025-11-26 07:47:08 +02:00
parent 56e2f64d07
commit 1c782897f7
184 changed files with 8991 additions and 649 deletions

View File

@@ -34,16 +34,20 @@ public sealed class PolicyEvaluationTests
source: "community",
tags: ImmutableArray.Create("reachability:indirect"));
var verdict = PolicyEvaluation.EvaluateFinding(document, config, finding);
var verdict = PolicyEvaluation.EvaluateFinding(document, config, finding, out var explanation);
Assert.Equal(PolicyVerdictStatus.Blocked, verdict.Status);
Assert.Equal(19.5, verdict.Score, 3);
var inputs = verdict.GetInputs();
Assert.Equal(50, inputs["severityWeight"]);
Assert.Equal(0.65, inputs["trustWeight"], 3);
Assert.Equal(0.6, inputs["reachabilityWeight"], 3);
Assert.Equal(19.5, inputs["baseScore"], 3);
Assert.Equal(PolicyVerdictStatus.Blocked, verdict.Status);
Assert.Equal(19.5, verdict.Score, 3);
var inputs = verdict.GetInputs();
Assert.Equal(50, inputs["severityWeight"]);
Assert.Equal(0.65, inputs["trustWeight"], 3);
Assert.Equal(0.6, inputs["reachabilityWeight"], 3);
Assert.Equal(19.5, inputs["baseScore"], 3);
Assert.NotNull(explanation);
Assert.Equal(PolicyVerdictStatus.Blocked, explanation!.Decision);
Assert.Equal("BlockMedium", explanation.RuleName);
}
[Fact]
@@ -79,17 +83,20 @@ public sealed class PolicyEvaluationTests
PolicySeverity.Critical,
tags: ImmutableArray.Create("reachability:entrypoint"));
var verdict = PolicyEvaluation.EvaluateFinding(document, config, finding);
var verdict = PolicyEvaluation.EvaluateFinding(document, config, finding, out var explanation);
Assert.Equal(PolicyVerdictStatus.Ignored, verdict.Status);
Assert.True(verdict.Quiet);
Assert.Equal("QuietIgnore", verdict.QuietedBy);
Assert.Equal(10, verdict.Score, 3);
var inputs = verdict.GetInputs();
Assert.Equal(90, inputs["baseScore"], 3);
Assert.Equal(config.IgnorePenalty, inputs["ignorePenalty"]);
Assert.Equal(config.QuietPenalty, inputs["quietPenalty"]);
Assert.Equal(PolicyVerdictStatus.Ignored, verdict.Status);
Assert.True(verdict.Quiet);
Assert.Equal("QuietIgnore", verdict.QuietedBy);
Assert.Equal(10, verdict.Score, 3);
var inputs = verdict.GetInputs();
Assert.Equal(90, inputs["baseScore"], 3);
Assert.Equal(config.IgnorePenalty, inputs["ignorePenalty"]);
Assert.Equal(config.QuietPenalty, inputs["quietPenalty"]);
Assert.NotNull(explanation);
Assert.Equal(PolicyVerdictStatus.Ignored, explanation!.Decision);
}
[Fact]
@@ -121,16 +128,19 @@ public sealed class PolicyEvaluationTests
PolicySeverity.Unknown,
tags: ImmutableArray.Create("reachability:unknown", "unknown-age-days:5"));
var verdict = PolicyEvaluation.EvaluateFinding(document, config, finding);
var verdict = PolicyEvaluation.EvaluateFinding(document, config, finding, out var explanation);
Assert.Equal(PolicyVerdictStatus.Blocked, verdict.Status);
Assert.Equal(30, verdict.Score, 3); // 60 * 1 * 0.5
Assert.Equal(0.55, verdict.UnknownConfidence ?? 0, 3);
Assert.Equal("medium", verdict.ConfidenceBand);
Assert.Equal(5, verdict.UnknownAgeDays ?? 0, 3);
var inputs = verdict.GetInputs();
Assert.Equal(0.55, inputs["unknownConfidence"], 3);
Assert.Equal(5, inputs["unknownAgeDays"], 3);
}
}
Assert.Equal(PolicyVerdictStatus.Blocked, verdict.Status);
Assert.Equal(30, verdict.Score, 3); // 60 * 1 * 0.5
Assert.Equal(0.55, verdict.UnknownConfidence ?? 0, 3);
Assert.Equal("medium", verdict.ConfidenceBand);
Assert.Equal(5, verdict.UnknownAgeDays ?? 0, 3);
var inputs = verdict.GetInputs();
Assert.Equal(0.55, inputs["unknownConfidence"], 3);
Assert.Equal(5, inputs["unknownAgeDays"], 3);
Assert.NotNull(explanation);
Assert.Equal(PolicyVerdictStatus.Blocked, explanation!.Decision);
}
}

View File

@@ -162,7 +162,7 @@ rules:
Assert.True(snapshot!.Document.Rules[0].Action.Quiet);
Assert.Null(snapshot.Document.Rules[0].Action.RequireVex);
Assert.Equal(PolicyActionType.Ignore, snapshot.Document.Rules[0].Action.Type);
var manualVerdict = PolicyEvaluation.EvaluateFinding(snapshot.Document, snapshot.ScoringConfig, PolicyFinding.Create("finding-quiet", PolicySeverity.Low));
var manualVerdict = PolicyEvaluation.EvaluateFinding(snapshot.Document, snapshot.ScoringConfig, PolicyFinding.Create("finding-quiet", PolicySeverity.Low), out _);
Assert.Equal(PolicyVerdictStatus.Warned, manualVerdict.Status);
var service = new PolicyPreviewService(store, NullLogger<PolicyPreviewService>.Instance);

View File

@@ -0,0 +1,55 @@
using System;
using System.IO;
using System.Threading.Tasks;
using FluentAssertions;
using StellaOps.Policy;
using Xunit;
namespace StellaOps.Policy.Tests;
public class PolicyValidationCliTests
{
[Fact]
public async Task RunAsync_EmitsCanonicalDigest_OnValidPolicy()
{
var tmp = Path.GetTempFileName();
try
{
await File.WriteAllTextAsync(tmp, """
{
"apiVersion": "spl.stellaops/v1",
"kind": "Policy",
"metadata": { "name": "demo" },
"spec": {
"defaultEffect": "deny",
"statements": [
{ "id": "ALLOW", "effect": "allow", "match": { "resource": "*", "actions": ["read"] } }
]
}
}
""");
var options = new PolicyValidationCliOptions
{
Inputs = new[] { tmp },
OutputJson = false,
Strict = false,
};
using var output = new StringWriter();
using var error = new StringWriter();
var cli = new PolicyValidationCli(output, error);
var exit = await cli.RunAsync(options);
exit.Should().Be(0);
var text = output.ToString();
text.Should().Contain("OK");
text.Should().Contain("canonical.spl.digest:");
}
finally
{
File.Delete(tmp);
}
}
}

View File

@@ -0,0 +1,90 @@
using StellaOps.Policy;
using Xunit;
namespace StellaOps.Policy.Tests;
public class SplCanonicalizerTests
{
[Fact]
public void Canonicalize_SortsStatementsActionsAndConditions()
{
const string input = """
{
"kind": "Policy",
"apiVersion": "spl.stellaops/v1",
"spec": {
"statements": [
{
"effect": "deny",
"id": "B-2",
"match": {
"resource": "/accounts/*",
"actions": ["delete", "read"]
}
},
{
"description": "desc",
"effect": "allow",
"id": "A-1",
"match": {
"actions": ["write", "read"],
"resource": "/accounts/*",
"conditions": [
{"operator": "gte", "value": 2, "field": "tier"},
{"field": "env", "value": "prod", "operator": "eq"}
]
},
"audit": {"severity": "warn", "message": "audit msg"}
}
],
"defaultEffect": "deny"
},
"metadata": {
"labels": {"env": "prod"},
"annotations": {"a": "1"},
"name": "demo"
}
}
""";
var canonical = SplCanonicalizer.CanonicalizeToString(input);
const string expected = "{\"apiVersion\":\"spl.stellaops/v1\",\"kind\":\"Policy\",\"metadata\":{\"annotations\":{\"a\":\"1\"},\"labels\":{\"env\":\"prod\"},\"name\":\"demo\"},\"spec\":{\"defaultEffect\":\"deny\",\"statements\":[{\"audit\":{\"message\":\"audit msg\",\"severity\":\"warn\"},\"description\":\"desc\",\"effect\":\"allow\",\"id\":\"A-1\",\"match\":{\"actions\":[\"read\",\"write\"],\"conditions\":[{\"field\":\"env\",\"operator\":\"eq\",\"value\":\"prod\"},{\"field\":\"tier\",\"operator\":\"gte\",\"value\":2}],\"resource\":\"/accounts/*\"}},{\"effect\":\"deny\",\"id\":\"B-2\",\"match\":{\"actions\":[\"delete\",\"read\"],\"resource\":\"/accounts/*\"}}]}}}";
Assert.Equal(expected, canonical);
}
[Fact]
public void ComputeDigest_IgnoresOrderingNoise()
{
const string versionA = """
{"apiVersion":"spl.stellaops/v1","kind":"Policy","metadata":{"name":"demo"},"spec":{"defaultEffect":"deny","statements":[{"id":"B","effect":"deny","match":{"resource":"/r","actions":["write","read"]}},{"id":"A","effect":"allow","match":{"resource":"/r","actions":["read"],"conditions":[{"field":"env","operator":"eq","value":"prod"}]}}]}}
""";
const string versionB = """
{"spec":{"statements":[{"match":{"actions":["read"],"resource":"/r","conditions":[{"value":"prod","operator":"eq","field":"env"}]},"effect":"allow","id":"A"},{"match":{"actions":["read","write"],"resource":"/r"},"effect":"deny","id":"B"}],"defaultEffect":"deny"},"kind":"Policy","metadata":{"name":"demo"},"apiVersion":"spl.stellaops/v1"}
""";
var hashA = SplCanonicalizer.ComputeDigest(versionA);
var hashB = SplCanonicalizer.ComputeDigest(versionB);
Assert.Equal(hashA, hashB);
}
[Fact]
public void ComputeDigest_DetectsContentChange()
{
const string baseDoc = """
{"apiVersion":"spl.stellaops/v1","kind":"Policy","metadata":{"name":"demo"},"spec":{"statements":[{"id":"A","effect":"allow","match":{"resource":"/r","actions":["read"]}}]}}
""";
const string changedDoc = """
{"apiVersion":"spl.stellaops/v1","kind":"Policy","metadata":{"name":"demo"},"spec":{"statements":[{"id":"A","effect":"allow","match":{"resource":"/r","actions":["read","write"]}}]}}
""";
var original = SplCanonicalizer.ComputeDigest(baseDoc);
var changed = SplCanonicalizer.ComputeDigest(changedDoc);
Assert.NotEqual(original, changed);
}
}

View File

@@ -0,0 +1,64 @@
using System.Text.Json;
using StellaOps.Policy;
using Xunit;
namespace StellaOps.Policy.Tests;
public class SplLayeringEngineTests
{
[Fact]
public void Merge_ReplacesStatementsById_AndKeepsBaseOnes()
{
const string baseDoc = """
{"apiVersion":"spl.stellaops/v1","kind":"Policy","metadata":{"name":"demo"},"spec":{"defaultEffect":"deny","statements":[{"id":"A","effect":"allow","match":{"resource":"/r","actions":["read"]}}, {"id":"B","effect":"deny","match":{"resource":"/r","actions":["write"]}}]}}
""";
const string overlay = """
{"apiVersion":"spl.stellaops/v1","kind":"Policy","metadata":{"name":"demo"},"spec":{"statements":[{"id":"A","effect":"deny","match":{"resource":"/r","actions":["read","write"]}}, {"id":"C","effect":"allow","match":{"resource":"/r","actions":["read"]}}]}}
""";
var merged = SplLayeringEngine.Merge(baseDoc, overlay);
const string expected = "{\"apiVersion\":\"spl.stellaops/v1\",\"kind\":\"Policy\",\"metadata\":{\"name\":\"demo\"},\"spec\":{\"defaultEffect\":\"deny\",\"statements\":[{\"effect\":\"deny\",\"id\":\"A\",\"match\":{\"actions\":[\"read\",\"write\"],\"resource\":\"/r\"}},{\"effect\":\"deny\",\"id\":\"B\",\"match\":{\"actions\":[\"write\"],\"resource\":\"/r\"}},{\"effect\":\"allow\",\"id\":\"C\",\"match\":{\"actions\":[\"read\"],\"resource\":\"/r\"}}]}}";
Assert.Equal(expected, merged);
}
[Fact]
public void Merge_MergesMetadataAndDefaultEffect()
{
const string baseDoc = """
{"apiVersion":"spl.stellaops/v1","kind":"Policy","metadata":{"name":"demo","labels":{"env":"dev"}},"spec":{"defaultEffect":"deny","statements":[{"id":"A","effect":"allow","match":{"resource":"/r","actions":["read"]}}]}}
""";
const string overlay = """
{"apiVersion":"spl.stellaops/v1","kind":"Policy","metadata":{"labels":{"env":"prod","tier":"gold"}},"spec":{"defaultEffect":"allow","statements":[{"id":"B","effect":"deny","match":{"resource":"/r","actions":["write"]}}]}}
""";
var merged = SplLayeringEngine.Merge(baseDoc, overlay);
const string expected = "{\"apiVersion\":\"spl.stellaops/v1\",\"kind\":\"Policy\",\"metadata\":{\"labels\":{\"env\":\"prod\",\"tier\":\"gold\"},\"name\":\"demo\"},\"spec\":{\"defaultEffect\":\"allow\",\"statements\":[{\"effect\":\"allow\",\"id\":\"A\",\"match\":{\"actions\":[\"read\"],\"resource\":\"/r\"}},{\"effect\":\"deny\",\"id\":\"B\",\"match\":{\"actions\":[\"write\"],\"resource\":\"/r\"}}]}}";
Assert.Equal(expected, merged);
}
[Fact]
public void Merge_PreservesUnknownTopLevelAndSpecFields()
{
const string baseDoc = """
{"apiVersion":"spl.stellaops/v1","kind":"Policy","metadata":{"name":"demo"},"extras":{"foo":1},"spec":{"defaultEffect":"deny","statements":[{"id":"A","effect":"allow","match":{"resource":"/r","actions":["read"]}}],"extensions":{"bar":true}}}
""";
const string overlay = """
{"apiVersion":"spl.stellaops/v1","kind":"Policy","metadata":{"name":"demo"},"spec":{"statements":[{"id":"B","effect":"deny","match":{"resource":"/r","actions":["write"]}}]}}
""";
var merged = SplLayeringEngine.Merge(baseDoc, overlay);
using var doc = JsonDocument.Parse(merged);
var root = doc.RootElement;
Assert.True(root.TryGetProperty("extras", out var extras) && extras.TryGetProperty("foo", out var foo) && foo.GetInt32() == 1);
Assert.True(root.GetProperty("spec").TryGetProperty("extensions", out var extensions) && extensions.TryGetProperty("bar", out var bar) && bar.GetBoolean());
}
}

View File

@@ -0,0 +1,75 @@
using System.Collections.Immutable;
using StellaOps.Policy;
using Xunit;
namespace StellaOps.Policy.Tests;
public class SplMigrationToolTests
{
[Fact]
public void ToSplPolicyJson_ConvertsRulesAndMetadata()
{
var rule = PolicyRule.Create(
name: "Block CVE",
action: new PolicyAction(PolicyActionType.Block, null, null, null, false),
severities: ImmutableArray.Create(PolicySeverity.Critical),
environments: ImmutableArray<string>.Empty,
sources: ImmutableArray<string>.Empty,
vendors: ImmutableArray<string>.Empty,
licenses: ImmutableArray<string>.Empty,
tags: ImmutableArray<string>.Empty,
match: PolicyRuleMatchCriteria.Create(
ImmutableArray<string>.Empty,
ImmutableArray<string>.Empty,
ImmutableArray<string>.Empty,
ImmutableArray<string>.Empty,
ImmutableArray<string>.Empty,
ImmutableArray.Create("/app"),
ImmutableArray<string>.Empty,
ImmutableArray<string>.Empty),
expires: null,
justification: "block it",
identifier: "RULE-1");
var document = new PolicyDocument(
PolicySchema.CurrentVersion,
ImmutableArray.Create(rule),
ImmutableDictionary<string, string>.Empty.Add("name", "demo"),
PolicyExceptionConfiguration.Empty);
var spl = SplMigrationTool.ToSplPolicyJson(document);
const string expected = "{\"apiVersion\":\"spl.stellaops/v1\",\"kind\":\"Policy\",\"metadata\":{\"labels\":{\"name\":\"demo\"},\"name\":\"demo\"},\"spec\":{\"defaultEffect\":\"deny\",\"statements\":[{\"effect\":\"deny\",\"id\":\"RULE-1\",\"match\":{\"actions\":[\"access\"],\"resource\":\"/app\"}}]}}";
Assert.Equal(expected, spl);
}
[Fact]
public void ToSplPolicyJson_UsesOverlaySafeIdsAndAudits()
{
var rule = PolicyRule.Create(
name: "Warn entrypoint",
action: new PolicyAction(PolicyActionType.Warn, null, null, null, true),
severities: ImmutableArray.Create(PolicySeverity.Low),
environments: ImmutableArray<string>.Empty,
sources: ImmutableArray<string>.Empty,
vendors: ImmutableArray<string>.Empty,
licenses: ImmutableArray<string>.Empty,
tags: ImmutableArray<string>.Empty,
match: PolicyRuleMatchCriteria.Empty,
expires: null,
justification: "soft warning");
var document = new PolicyDocument(
PolicySchema.CurrentVersion,
ImmutableArray.Create(rule),
ImmutableDictionary<string, string>.Empty,
PolicyExceptionConfiguration.Empty);
var spl = SplMigrationTool.ToSplPolicyJson(document);
const string expectedId = "warn-entrypoint";
Assert.Contains(expectedId, spl);
Assert.Contains("\"audit\":{\"message\":\"soft warning\",\"severity\":\"warn\"}", spl);
}
}

View File

@@ -0,0 +1,29 @@
using System.Text.Json;
using StellaOps.Policy;
using Xunit;
namespace StellaOps.Policy.Tests;
public class SplSchemaResourceTests
{
[Fact]
public void Schema_IncludesReachabilityAndExploitability()
{
var schema = SplSchemaResource.GetSchema();
using var doc = JsonDocument.Parse(schema);
var match = doc.RootElement
.GetProperty("properties")
.GetProperty("spec")
.GetProperty("properties")
.GetProperty("statements")
.GetProperty("items")
.GetProperty("properties")
.GetProperty("match")
.GetProperty("properties");
Assert.True(match.TryGetProperty("reachability", out var reachability));
Assert.Equal(JsonValueKind.Object, reachability.ValueKind);
Assert.True(match.TryGetProperty("exploitability", out var exploitability));
Assert.Equal(JsonValueKind.Object, exploitability.ValueKind);
}
}