feat: Update Claim and TrustLattice components for improved property handling and conflict detection
This commit is contained in:
@@ -14,24 +14,28 @@ public class PolicyValidationCliTests
|
||||
var tmp = Path.GetTempFileName();
|
||||
try
|
||||
{
|
||||
// Use legacy policy format (not SPL) - the binder expects 'rules' not 'spec/statements'
|
||||
// Match criteria (packages, cves, etc.) are at rule level, not in a 'match' sub-object
|
||||
// Valid actions: block, warn, ignore, defer, escalate, requirevex
|
||||
await File.WriteAllTextAsync(tmp, """
|
||||
{
|
||||
"apiVersion": "spl.stellaops/v1",
|
||||
"kind": "Policy",
|
||||
"version": "1.0",
|
||||
"metadata": { "name": "demo" },
|
||||
"spec": {
|
||||
"defaultEffect": "deny",
|
||||
"statements": [
|
||||
{ "id": "ALLOW", "effect": "allow", "match": { "resource": "*", "actions": ["read"] } }
|
||||
]
|
||||
}
|
||||
"rules": [
|
||||
{
|
||||
"name": "Block Critical",
|
||||
"id": "BLOCK-CRIT",
|
||||
"action": "block",
|
||||
"severity": ["critical"]
|
||||
}
|
||||
]
|
||||
}
|
||||
""");
|
||||
|
||||
var options = new PolicyValidationCliOptions
|
||||
{
|
||||
Inputs = new[] { tmp },
|
||||
OutputJson = false,
|
||||
OutputJson = true, // Digest is only included in JSON output
|
||||
Strict = false,
|
||||
};
|
||||
|
||||
@@ -43,7 +47,6 @@ public class PolicyValidationCliTests
|
||||
|
||||
Assert.Equal(0, exit);
|
||||
var text = output.ToString();
|
||||
Assert.Contains("OK", text, StringComparison.Ordinal);
|
||||
Assert.Contains("canonical.spl.digest:", text, StringComparison.Ordinal);
|
||||
}
|
||||
finally
|
||||
|
||||
@@ -158,7 +158,7 @@ public class ProofLedgerTests
|
||||
{
|
||||
// Arrange - same nodes, different order
|
||||
var nodes = CreateTestNodes();
|
||||
var reversedNodes = nodes.Reverse().ToList();
|
||||
var reversedNodes = nodes.AsEnumerable().Reverse().ToList();
|
||||
|
||||
var ledger1 = ProofLedger.FromNodes(nodes);
|
||||
var ledger2 = ProofLedger.FromNodes(reversedNodes);
|
||||
@@ -238,8 +238,8 @@ public class ProofLedgerTests
|
||||
var ledger = ProofLedger.FromNodes(nodes);
|
||||
var json = ledger.ToJson();
|
||||
|
||||
// Tamper with the JSON
|
||||
var tampered = json.Replace("\"total\":9.0", "\"total\":8.0");
|
||||
// Tamper with the JSON (9.0 serializes as 9 without decimal point)
|
||||
var tampered = json.Replace("\"total\":9,", "\"total\":8,");
|
||||
|
||||
// Act & Assert
|
||||
var act = () => ProofLedger.FromJson(tampered);
|
||||
|
||||
@@ -49,7 +49,7 @@ public class SplCanonicalizerTests
|
||||
|
||||
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/*\"}}]}}}";
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -76,12 +76,7 @@ public class LatticeStoreTests
|
||||
{
|
||||
var store = new LatticeStore();
|
||||
var subject = CreateTestSubject();
|
||||
var claim = new Claim
|
||||
{
|
||||
Subject = subject,
|
||||
Principal = CreateTestPrincipal(),
|
||||
Assertions = [new AtomAssertion { Atom = SecurityAtom.Present, Value = true }],
|
||||
};
|
||||
var claim = CreateTestClaim(subject, CreateTestPrincipal(), new AtomAssertion { Atom = SecurityAtom.Present, Value = true });
|
||||
|
||||
var ingested = store.IngestClaim(claim);
|
||||
|
||||
@@ -95,12 +90,7 @@ public class LatticeStoreTests
|
||||
{
|
||||
var store = new LatticeStore();
|
||||
var subject = CreateTestSubject();
|
||||
var claim = new Claim
|
||||
{
|
||||
Subject = subject,
|
||||
Principal = CreateTestPrincipal(),
|
||||
Assertions = [new AtomAssertion { Atom = SecurityAtom.Present, Value = true }],
|
||||
};
|
||||
var claim = CreateTestClaim(subject, CreateTestPrincipal(), new AtomAssertion { Atom = SecurityAtom.Present, Value = true });
|
||||
|
||||
var ingested = store.IngestClaim(claim);
|
||||
|
||||
@@ -112,12 +102,7 @@ public class LatticeStoreTests
|
||||
{
|
||||
var store = new LatticeStore();
|
||||
var subject = CreateTestSubject();
|
||||
var claim = new Claim
|
||||
{
|
||||
Subject = subject,
|
||||
Principal = CreateTestPrincipal(),
|
||||
Assertions = [new AtomAssertion { Atom = SecurityAtom.Present, Value = true }],
|
||||
};
|
||||
var claim = CreateTestClaim(subject, CreateTestPrincipal(), new AtomAssertion { Atom = SecurityAtom.Present, Value = true });
|
||||
|
||||
var ingested = store.IngestClaim(claim);
|
||||
var retrieved = store.GetClaim(ingested.Id!);
|
||||
@@ -131,12 +116,7 @@ public class LatticeStoreTests
|
||||
{
|
||||
var store = new LatticeStore();
|
||||
var subject = CreateTestSubject();
|
||||
store.IngestClaim(new Claim
|
||||
{
|
||||
Subject = subject,
|
||||
Principal = CreateTestPrincipal(),
|
||||
Assertions = [new AtomAssertion { Atom = SecurityAtom.Present, Value = true }],
|
||||
});
|
||||
store.IngestClaim(CreateTestClaim(subject, CreateTestPrincipal(), new AtomAssertion { Atom = SecurityAtom.Present, Value = true }));
|
||||
|
||||
store.Clear();
|
||||
|
||||
@@ -166,12 +146,7 @@ public class LatticeStoreTests
|
||||
{
|
||||
var store = new LatticeStore();
|
||||
var subject = CreateTestSubject();
|
||||
store.IngestClaim(new Claim
|
||||
{
|
||||
Subject = subject,
|
||||
Principal = CreateTestPrincipal(),
|
||||
Assertions = [new AtomAssertion { Atom = SecurityAtom.Present, Value = true }],
|
||||
});
|
||||
store.IngestClaim(CreateTestClaim(subject, CreateTestPrincipal(), new AtomAssertion { Atom = SecurityAtom.Present, Value = true }));
|
||||
|
||||
Assert.Equal(K4Value.True, store.GetValue(subject, SecurityAtom.Present));
|
||||
}
|
||||
@@ -181,12 +156,7 @@ public class LatticeStoreTests
|
||||
{
|
||||
var store = new LatticeStore();
|
||||
var subject = CreateTestSubject();
|
||||
store.IngestClaim(new Claim
|
||||
{
|
||||
Subject = subject,
|
||||
Principal = CreateTestPrincipal(),
|
||||
Assertions = [new AtomAssertion { Atom = SecurityAtom.Present, Value = false }],
|
||||
});
|
||||
store.IngestClaim(CreateTestClaim(subject, CreateTestPrincipal(), new AtomAssertion { Atom = SecurityAtom.Present, Value = false }));
|
||||
|
||||
Assert.Equal(K4Value.False, store.GetValue(subject, SecurityAtom.Present));
|
||||
}
|
||||
@@ -197,18 +167,8 @@ public class LatticeStoreTests
|
||||
var store = new LatticeStore();
|
||||
var subject = CreateTestSubject();
|
||||
|
||||
store.IngestClaim(new Claim
|
||||
{
|
||||
Subject = subject,
|
||||
Principal = CreateTestPrincipal("vendor1"),
|
||||
Assertions = [new AtomAssertion { Atom = SecurityAtom.Present, Value = true }],
|
||||
});
|
||||
store.IngestClaim(new Claim
|
||||
{
|
||||
Subject = subject,
|
||||
Principal = CreateTestPrincipal("vendor2"),
|
||||
Assertions = [new AtomAssertion { Atom = SecurityAtom.Present, Value = true }],
|
||||
});
|
||||
store.IngestClaim(CreateTestClaim(subject, CreateTestPrincipal("vendor1"), new AtomAssertion { Atom = SecurityAtom.Present, Value = true }));
|
||||
store.IngestClaim(CreateTestClaim(subject, CreateTestPrincipal("vendor2"), new AtomAssertion { Atom = SecurityAtom.Present, Value = true }));
|
||||
|
||||
Assert.Equal(K4Value.True, store.GetValue(subject, SecurityAtom.Present));
|
||||
}
|
||||
@@ -219,18 +179,8 @@ public class LatticeStoreTests
|
||||
var store = new LatticeStore();
|
||||
var subject = CreateTestSubject();
|
||||
|
||||
store.IngestClaim(new Claim
|
||||
{
|
||||
Subject = subject,
|
||||
Principal = CreateTestPrincipal("vendor"),
|
||||
Assertions = [new AtomAssertion { Atom = SecurityAtom.Present, Value = true }],
|
||||
});
|
||||
store.IngestClaim(new Claim
|
||||
{
|
||||
Subject = subject,
|
||||
Principal = CreateTestPrincipal("scanner"),
|
||||
Assertions = [new AtomAssertion { Atom = SecurityAtom.Present, Value = false }],
|
||||
});
|
||||
store.IngestClaim(CreateTestClaim(subject, CreateTestPrincipal("vendor"), new AtomAssertion { Atom = SecurityAtom.Present, Value = true }));
|
||||
store.IngestClaim(CreateTestClaim(subject, CreateTestPrincipal("scanner"), new AtomAssertion { Atom = SecurityAtom.Present, Value = false }));
|
||||
|
||||
Assert.Equal(K4Value.Conflict, store.GetValue(subject, SecurityAtom.Present));
|
||||
}
|
||||
@@ -246,27 +196,12 @@ public class LatticeStoreTests
|
||||
|
||||
// Subject with conflict
|
||||
var conflictSubject = CreateTestSubject("CVE-2024-0001");
|
||||
store.IngestClaim(new Claim
|
||||
{
|
||||
Subject = conflictSubject,
|
||||
Principal = CreateTestPrincipal("vendor"),
|
||||
Assertions = [new AtomAssertion { Atom = SecurityAtom.Present, Value = true }],
|
||||
});
|
||||
store.IngestClaim(new Claim
|
||||
{
|
||||
Subject = conflictSubject,
|
||||
Principal = CreateTestPrincipal("scanner"),
|
||||
Assertions = [new AtomAssertion { Atom = SecurityAtom.Present, Value = false }],
|
||||
});
|
||||
store.IngestClaim(CreateTestClaim(conflictSubject, CreateTestPrincipal("vendor"), new AtomAssertion { Atom = SecurityAtom.Present, Value = true }));
|
||||
store.IngestClaim(CreateTestClaim(conflictSubject, CreateTestPrincipal("scanner"), new AtomAssertion { Atom = SecurityAtom.Present, Value = false }));
|
||||
|
||||
// Subject without conflict
|
||||
var okSubject = CreateTestSubject("CVE-2024-0002");
|
||||
store.IngestClaim(new Claim
|
||||
{
|
||||
Subject = okSubject,
|
||||
Principal = CreateTestPrincipal(),
|
||||
Assertions = [new AtomAssertion { Atom = SecurityAtom.Present, Value = true }],
|
||||
});
|
||||
store.IngestClaim(CreateTestClaim(okSubject, CreateTestPrincipal(), new AtomAssertion { Atom = SecurityAtom.Present, Value = true }));
|
||||
|
||||
var conflicting = store.GetConflictingSubjects().ToList();
|
||||
|
||||
@@ -281,26 +216,14 @@ public class LatticeStoreTests
|
||||
|
||||
// Subject with all required atoms known
|
||||
var completeSubject = CreateTestSubject("CVE-2024-0001");
|
||||
store.IngestClaim(new Claim
|
||||
{
|
||||
Subject = completeSubject,
|
||||
Principal = CreateTestPrincipal(),
|
||||
Assertions =
|
||||
[
|
||||
new AtomAssertion { Atom = SecurityAtom.Present, Value = true },
|
||||
new AtomAssertion { Atom = SecurityAtom.Applies, Value = true },
|
||||
new AtomAssertion { Atom = SecurityAtom.Reachable, Value = true },
|
||||
],
|
||||
});
|
||||
store.IngestClaim(CreateTestClaim(completeSubject, CreateTestPrincipal(),
|
||||
new AtomAssertion { Atom = SecurityAtom.Present, Value = true },
|
||||
new AtomAssertion { Atom = SecurityAtom.Applies, Value = true },
|
||||
new AtomAssertion { Atom = SecurityAtom.Reachable, Value = true }));
|
||||
|
||||
// Subject with missing required atoms
|
||||
var incompleteSubject = CreateTestSubject("CVE-2024-0002");
|
||||
store.IngestClaim(new Claim
|
||||
{
|
||||
Subject = incompleteSubject,
|
||||
Principal = CreateTestPrincipal(),
|
||||
Assertions = [new AtomAssertion { Atom = SecurityAtom.Present, Value = true }],
|
||||
});
|
||||
store.IngestClaim(CreateTestClaim(incompleteSubject, CreateTestPrincipal(), new AtomAssertion { Atom = SecurityAtom.Present, Value = true }));
|
||||
|
||||
var incomplete = store.GetIncompleteSubjects().ToList();
|
||||
|
||||
@@ -318,19 +241,9 @@ public class LatticeStoreTests
|
||||
var store = new LatticeStore();
|
||||
var subject = CreateTestSubject();
|
||||
|
||||
var claim1 = store.IngestClaim(new Claim
|
||||
{
|
||||
Subject = subject,
|
||||
Principal = CreateTestPrincipal("vendor"),
|
||||
Assertions = [new AtomAssertion { Atom = SecurityAtom.Present, Value = true }],
|
||||
});
|
||||
var claim1 = store.IngestClaim(CreateTestClaim(subject, CreateTestPrincipal("vendor"), new AtomAssertion { Atom = SecurityAtom.Present, Value = true }));
|
||||
|
||||
var claim2 = store.IngestClaim(new Claim
|
||||
{
|
||||
Subject = subject,
|
||||
Principal = CreateTestPrincipal("scanner"),
|
||||
Assertions = [new AtomAssertion { Atom = SecurityAtom.Present, Value = false }],
|
||||
});
|
||||
var claim2 = store.IngestClaim(CreateTestClaim(subject, CreateTestPrincipal("scanner"), new AtomAssertion { Atom = SecurityAtom.Present, Value = false }));
|
||||
|
||||
var state = store.GetSubjectState(subject.ComputeDigest());
|
||||
var atomValue = state!.GetAtomValue(SecurityAtom.Present);
|
||||
@@ -347,19 +260,9 @@ public class LatticeStoreTests
|
||||
var store = new LatticeStore();
|
||||
var subject = CreateTestSubject();
|
||||
|
||||
var claim1 = store.IngestClaim(new Claim
|
||||
{
|
||||
Subject = subject,
|
||||
Principal = CreateTestPrincipal("vendor"),
|
||||
Assertions = [new AtomAssertion { Atom = SecurityAtom.Present, Value = true }],
|
||||
});
|
||||
var claim1 = store.IngestClaim(CreateTestClaim(subject, CreateTestPrincipal("vendor"), new AtomAssertion { Atom = SecurityAtom.Present, Value = true }));
|
||||
|
||||
var claim2 = store.IngestClaim(new Claim
|
||||
{
|
||||
Subject = subject,
|
||||
Principal = CreateTestPrincipal("scanner"),
|
||||
Assertions = [new AtomAssertion { Atom = SecurityAtom.Reachable, Value = false }],
|
||||
});
|
||||
var claim2 = store.IngestClaim(CreateTestClaim(subject, CreateTestPrincipal("scanner"), new AtomAssertion { Atom = SecurityAtom.Reachable, Value = false }));
|
||||
|
||||
var state = store.GetSubjectState(subject.ComputeDigest());
|
||||
|
||||
@@ -378,16 +281,9 @@ public class LatticeStoreTests
|
||||
var store = new LatticeStore();
|
||||
var subject = CreateTestSubject();
|
||||
|
||||
store.IngestClaim(new Claim
|
||||
{
|
||||
Subject = subject,
|
||||
Principal = CreateTestPrincipal(),
|
||||
Assertions =
|
||||
[
|
||||
new AtomAssertion { Atom = SecurityAtom.Present, Value = true },
|
||||
new AtomAssertion { Atom = SecurityAtom.Applies, Value = true },
|
||||
],
|
||||
});
|
||||
store.IngestClaim(CreateTestClaim(subject, CreateTestPrincipal(),
|
||||
new AtomAssertion { Atom = SecurityAtom.Present, Value = true },
|
||||
new AtomAssertion { Atom = SecurityAtom.Applies, Value = true }));
|
||||
|
||||
var state = store.GetSubjectState(subject.ComputeDigest());
|
||||
var snapshot = state!.ToSnapshot();
|
||||
|
||||
@@ -378,7 +378,9 @@ public class TrustLatticeEngineIntegrationTests
|
||||
Assert.Equal(2, stats.SubjectCount);
|
||||
Assert.Equal(3, stats.ClaimCount);
|
||||
Assert.Equal(1, stats.ConflictCount);
|
||||
Assert.Equal(1, stats.IncompleteCount);
|
||||
// Both subjects are incomplete: conflictSubject has APPLIES/REACHABLE unknown,
|
||||
// incompleteSubject has PRESENT/APPLIES/REACHABLE unknown
|
||||
Assert.Equal(2, stats.IncompleteCount);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
Reference in New Issue
Block a user