save progress

This commit is contained in:
StellaOps Bot
2026-01-03 12:41:57 +02:00
parent 83c37243e0
commit d486d41a48
48 changed files with 7174 additions and 1086 deletions

View File

@@ -0,0 +1,58 @@
{
"_type": "https://in-toto.io/Statement/v1",
"subject": [
{
"name": "file://dist/app.tar.gz",
"digest": {
"sha256": "b2c3d4e5f6789012345678901234567890123456789012345678901234abcdef"
}
}
],
"predicateType": "https://in-toto.io/Link/v1",
"predicate": {
"name": "build",
"command": [
"make",
"release",
"VERSION=1.0.0"
],
"materials": [
{
"uri": "git://github.com/example/repo@abc123def456",
"digest": {
"sha256": "abc123def4567890123456789012345678901234567890123456789012345678"
}
},
{
"uri": "file://Cargo.lock",
"digest": {
"sha256": "def456789012345678901234567890123456789012345678901234567890abcd"
}
}
],
"products": [
{
"uri": "file://dist/app.tar.gz",
"digest": {
"sha256": "b2c3d4e5f6789012345678901234567890123456789012345678901234abcdef"
}
},
{
"uri": "file://dist/app.tar.gz.sha256",
"digest": {
"sha256": "c3d4e5f6789012345678901234567890123456789012345678901234abcdef12"
}
}
],
"byproducts": {
"return-value": 0,
"stdout": "Building release v1.0.0...\nBuild complete.",
"stderr": ""
},
"environment": {
"GITHUB_SHA": "abc123def456",
"GITHUB_RUN_ID": "12345",
"RUST_VERSION": "1.75.0"
}
}
}

View File

@@ -0,0 +1,46 @@
{
"steps": [
{
"name": "build",
"expectedMaterials": ["git://*"],
"expectedProducts": ["file://dist/*"],
"threshold": 1
},
{
"name": "scan",
"expectedMaterials": ["oci://*", "file://dist/*"],
"expectedProducts": ["file://sbom.*", "file://vulns.*"],
"threshold": 1
},
{
"name": "sign",
"expectedMaterials": ["file://dist/*"],
"expectedProducts": ["file://dist/*.sig"],
"threshold": 2
}
],
"keys": {
"builder-key-001": {
"keyType": "ecdsa-p256",
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE...\n-----END PUBLIC KEY-----",
"allowedSteps": ["build"]
},
"scanner-key-001": {
"keyType": "ecdsa-p256",
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE...\n-----END PUBLIC KEY-----",
"allowedSteps": ["scan"]
},
"signer-key-001": {
"keyType": "ecdsa-p256",
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE...\n-----END PUBLIC KEY-----",
"allowedSteps": ["sign"]
},
"signer-key-002": {
"keyType": "ecdsa-p256",
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE...\n-----END PUBLIC KEY-----",
"allowedSteps": ["sign"]
}
},
"rootLayoutId": "layout-v1-20260102",
"expires": "2027-01-01T00:00:00Z"
}

View File

@@ -0,0 +1,44 @@
{
"_type": "https://in-toto.io/Statement/v1",
"subject": [
{
"name": "file://sbom.cdx.json",
"digest": {
"sha256": "a1b2c3d4e5f6789012345678901234567890123456789012345678901234abcd"
}
}
],
"predicateType": "https://in-toto.io/Link/v1",
"predicate": {
"name": "scan",
"command": [
"stella",
"scan",
"--image",
"nginx:1.25"
],
"materials": [
{
"uri": "oci://docker.io/library/nginx@sha256:abc123456789",
"digest": {
"sha256": "abc123456789012345678901234567890123456789012345678901234567890a"
}
}
],
"products": [
{
"uri": "file://sbom.cdx.json",
"digest": {
"sha256": "a1b2c3d4e5f6789012345678901234567890123456789012345678901234abcd"
}
}
],
"byproducts": {
"return-value": 0
},
"environment": {
"STELLAOPS_VERSION": "2026.01",
"CI": "true"
}
}
}

View File

@@ -0,0 +1,257 @@
// Licensed under AGPL-3.0-or-later. Copyright (C) 2024-2026 StellaOps Contributors.
using System.Text.Json;
using FluentAssertions;
using StellaOps.Attestor.Core.InToto;
using Xunit;
namespace StellaOps.Attestor.Core.Tests.InToto;
/// <summary>
/// Golden tests that verify in-toto link parsing and serialization against reference fixtures.
/// These tests ensure compatibility with the in-toto specification.
/// </summary>
public class InTotoGoldenTests
{
private static readonly string FixturesPath = Path.Combine(
AppContext.BaseDirectory, "Fixtures", "InToto");
/// <summary>
/// Test that we can parse the golden scan link fixture.
/// </summary>
[Fact]
public void ParseGoldenScanLink_ShouldSucceed()
{
// Arrange
var json = File.ReadAllText(Path.Combine(FixturesPath, "golden_scan_link.json"));
// Act
var link = InTotoLink.FromJson(json);
// Assert
link.Should().NotBeNull();
link.Subjects.Should().HaveCount(1);
link.Subjects[0].Name.Should().Be("file://sbom.cdx.json");
link.Subjects[0].Digest.Sha256.Should().Be("a1b2c3d4e5f6789012345678901234567890123456789012345678901234abcd");
link.Predicate.Name.Should().Be("scan");
link.Predicate.Command.Should().BeEquivalentTo(new[] { "stella", "scan", "--image", "nginx:1.25" });
link.Predicate.Materials.Should().HaveCount(1);
link.Predicate.Materials[0].Uri.Should().Be("oci://docker.io/library/nginx@sha256:abc123456789");
link.Predicate.Products.Should().HaveCount(1);
link.Predicate.Products[0].Uri.Should().Be("file://sbom.cdx.json");
link.Predicate.ByProducts.ReturnValue.Should().Be(0);
link.Predicate.Environment.Should().ContainKey("STELLAOPS_VERSION");
link.Predicate.Environment["STELLAOPS_VERSION"].Should().Be("2026.01");
}
/// <summary>
/// Test that we can parse the golden build link fixture.
/// </summary>
[Fact]
public void ParseGoldenBuildLink_ShouldSucceed()
{
// Arrange
var json = File.ReadAllText(Path.Combine(FixturesPath, "golden_build_link.json"));
// Act
var link = InTotoLink.FromJson(json);
// Assert
link.Should().NotBeNull();
link.Predicate.Name.Should().Be("build");
link.Predicate.Command.Should().Contain("make");
link.Predicate.Materials.Should().HaveCount(2);
link.Predicate.Products.Should().HaveCount(2);
link.Predicate.ByProducts.ReturnValue.Should().Be(0);
link.Predicate.ByProducts.Stdout.Should().Contain("Build complete");
link.Predicate.Environment.Should().ContainKey("GITHUB_SHA");
link.Predicate.Environment.Should().ContainKey("RUST_VERSION");
}
/// <summary>
/// Test round-trip serialization of the golden scan link.
/// </summary>
[Fact]
public void RoundTripGoldenScanLink_ShouldPreserveContent()
{
// Arrange
var originalJson = File.ReadAllText(Path.Combine(FixturesPath, "golden_scan_link.json"));
var link = InTotoLink.FromJson(originalJson);
// Act
var serializedJson = link.ToJson(indented: true);
var reparsedLink = InTotoLink.FromJson(serializedJson);
// Assert
reparsedLink.Predicate.Name.Should().Be(link.Predicate.Name);
reparsedLink.Predicate.Command.Should().BeEquivalentTo(link.Predicate.Command);
reparsedLink.Predicate.Materials.Should().HaveCount(link.Predicate.Materials.Length);
reparsedLink.Predicate.Products.Should().HaveCount(link.Predicate.Products.Length);
reparsedLink.Subjects.Should().HaveCount(link.Subjects.Length);
}
/// <summary>
/// Test round-trip serialization of the golden build link.
/// </summary>
[Fact]
public void RoundTripGoldenBuildLink_ShouldPreserveContent()
{
// Arrange
var originalJson = File.ReadAllText(Path.Combine(FixturesPath, "golden_build_link.json"));
var link = InTotoLink.FromJson(originalJson);
// Act
var serializedJson = link.ToJson(indented: true);
var reparsedLink = InTotoLink.FromJson(serializedJson);
// Assert
reparsedLink.Predicate.Name.Should().Be(link.Predicate.Name);
reparsedLink.Predicate.Environment.Should().BeEquivalentTo(link.Predicate.Environment);
reparsedLink.Predicate.ByProducts.Stdout.Should().Be(link.Predicate.ByProducts.Stdout);
}
/// <summary>
/// Test that golden links have the correct in-toto statement type.
/// </summary>
[Theory]
[InlineData("golden_scan_link.json")]
[InlineData("golden_build_link.json")]
public void GoldenLinks_ShouldHaveCorrectStatementType(string filename)
{
// Arrange
var json = File.ReadAllText(Path.Combine(FixturesPath, filename));
// Act
var doc = JsonDocument.Parse(json);
var root = doc.RootElement;
// Assert
root.GetProperty("_type").GetString().Should().Be("https://in-toto.io/Statement/v1");
root.GetProperty("predicateType").GetString().Should().Be("https://in-toto.io/Link/v1");
}
/// <summary>
/// Test that golden links have required predicate fields per in-toto spec.
/// </summary>
[Theory]
[InlineData("golden_scan_link.json")]
[InlineData("golden_build_link.json")]
public void GoldenLinks_ShouldHaveRequiredPredicateFields(string filename)
{
// Arrange
var json = File.ReadAllText(Path.Combine(FixturesPath, filename));
// Act
var doc = JsonDocument.Parse(json);
var predicate = doc.RootElement.GetProperty("predicate");
// Assert - Required fields per in-toto Link predicate spec
predicate.TryGetProperty("name", out _).Should().BeTrue("name is required");
predicate.TryGetProperty("command", out _).Should().BeTrue("command is required");
predicate.TryGetProperty("materials", out _).Should().BeTrue("materials is required");
predicate.TryGetProperty("products", out _).Should().BeTrue("products is required");
}
/// <summary>
/// Test that subjects match products (per in-toto link semantics).
/// </summary>
[Theory]
[InlineData("golden_scan_link.json")]
[InlineData("golden_build_link.json")]
public void GoldenLinks_SubjectsShouldMatchProducts(string filename)
{
// Arrange
var json = File.ReadAllText(Path.Combine(FixturesPath, filename));
var link = InTotoLink.FromJson(json);
// Act & Assert
// In in-toto, subjects are the products - they should have matching digests
foreach (var subject in link.Subjects)
{
var matchingProduct = link.Predicate.Products
.FirstOrDefault(p => p.Uri == subject.Name);
matchingProduct.Should().NotBeNull(
$"Subject '{subject.Name}' should have a matching product");
if (matchingProduct is not null)
{
matchingProduct.Digest.Sha256.Should().Be(subject.Digest.Sha256,
"Subject and product digests should match");
}
}
}
/// <summary>
/// Test that all artifacts have valid digests.
/// </summary>
[Theory]
[InlineData("golden_scan_link.json")]
[InlineData("golden_build_link.json")]
public void GoldenLinks_AllArtifactsShouldHaveValidDigests(string filename)
{
// Arrange
var json = File.ReadAllText(Path.Combine(FixturesPath, filename));
var link = InTotoLink.FromJson(json);
// Act & Assert
foreach (var material in link.Predicate.Materials)
{
material.Digest.HasDigest.Should().BeTrue(
$"Material '{material.Uri}' should have a digest");
material.Digest.Sha256.Should().MatchRegex("^[a-f0-9]{64}$",
"SHA-256 digest should be 64 hex characters");
}
foreach (var product in link.Predicate.Products)
{
product.Digest.HasDigest.Should().BeTrue(
$"Product '{product.Uri}' should have a digest");
product.Digest.Sha256.Should().MatchRegex("^[a-f0-9]{64}$",
"SHA-256 digest should be 64 hex characters");
}
}
/// <summary>
/// Test that byproducts have a return value.
/// </summary>
[Theory]
[InlineData("golden_scan_link.json", 0)]
[InlineData("golden_build_link.json", 0)]
public void GoldenLinks_ByProductsShouldHaveReturnValue(string filename, int expectedReturnValue)
{
// Arrange
var json = File.ReadAllText(Path.Combine(FixturesPath, filename));
var link = InTotoLink.FromJson(json);
// Act & Assert
link.Predicate.ByProducts.ReturnValue.Should().Be(expectedReturnValue);
}
/// <summary>
/// Test golden layout fixture parsing.
/// </summary>
[Fact]
public void ParseGoldenLayout_ShouldSucceed()
{
// Arrange
var json = File.ReadAllText(Path.Combine(FixturesPath, "golden_layout.json"));
// Act
var doc = JsonDocument.Parse(json);
var root = doc.RootElement;
// Assert
root.GetProperty("steps").GetArrayLength().Should().Be(3);
root.GetProperty("keys").EnumerateObject().Count().Should().Be(4);
var steps = root.GetProperty("steps").EnumerateArray().ToList();
steps[0].GetProperty("name").GetString().Should().Be("build");
steps[1].GetProperty("name").GetString().Should().Be("scan");
steps[2].GetProperty("name").GetString().Should().Be("sign");
// Sign step should require threshold of 2
steps[2].GetProperty("threshold").GetInt32().Should().Be(2);
}
}

View File

@@ -27,4 +27,11 @@
<ProjectReference Include="..\StellaOps.Attestor.Core\StellaOps.Attestor.Core.csproj" />
<ProjectReference Include="../../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
</ItemGroup>
<!-- Copy fixture files to output directory -->
<ItemGroup>
<None Include="Fixtures\**\*.*">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>