feat(zastava): add evidence locker plan and schema examples

- Introduced README.md for Zastava Evidence Locker Plan detailing artifacts to sign and post-signing steps.
- Added example JSON schemas for observer events and webhook admissions.
- Updated implementor guidelines with checklist for CI linting, determinism, secrets management, and schema control.
- Created alert rules for Vuln Explorer to monitor API latency and projection errors.
- Developed analytics ingestion plan for Vuln Explorer, focusing on telemetry and PII guardrails.
- Implemented Grafana dashboard configuration for Vuln Explorer metrics visualization.
- Added expected projection SHA256 for vulnerability events.
- Created k6 load testing script for Vuln Explorer API.
- Added sample projection and replay event data for testing.
- Implemented ReplayInputsLock for deterministic replay inputs management.
- Developed tests for ReplayInputsLock to ensure stable hash computation.
- Created SurfaceManifestDeterminismVerifier to validate manifest determinism and integrity.
- Added unit tests for SurfaceManifestDeterminismVerifier to ensure correct functionality.
- Implemented Angular tests for VulnerabilityHttpClient and VulnerabilityDetailComponent to verify API interactions and UI rendering.
This commit is contained in:
StellaOps Bot
2025-12-02 09:27:31 +02:00
parent 885ce86af4
commit 2d08f52715
74 changed files with 1690 additions and 131 deletions

View File

@@ -101,6 +101,71 @@ public sealed class FileSurfaceManifestStoreTests : IAsyncDisposable
Assert.Equal("scan-123", retrieved.ScanId);
}
[Fact]
public async Task PublishAsync_NormalizesDeterminismMetadataAndAttestations()
{
var doc = new SurfaceManifestDocument
{
Tenant = "acme",
DeterminismMerkleRoot = "ABCDEF",
Determinism = new SurfaceDeterminismMetadata
{
MerkleRoot = "ABCDEF",
RecipeDigest = "1234",
CompositionRecipeUri = " cas://bucket/recipe.json "
},
Artifacts = new[]
{
new SurfaceManifestArtifact
{
Kind = "layer.fragments",
Uri = "cas://bucket/fragments.json",
Digest = "sha256:bbbb",
MediaType = "application/json",
Format = "json",
Attestations = new[]
{
new SurfaceManifestAttestation
{
Kind = "dsse",
Digest = "sha256:dddd",
Uri = "cas://attest/dsse.json"
},
new SurfaceManifestAttestation
{
Kind = "dsse",
Digest = "sha256:cccc",
Uri = "cas://attest/other.json"
}
}
},
new SurfaceManifestArtifact
{
Kind = "composition.recipe",
Uri = "cas://bucket/recipe.json",
Digest = "sha256:1234",
MediaType = "application/json",
Format = "composition.recipe"
}
}
};
var result = await _store.PublishAsync(doc);
Assert.Equal("abcdef", result.Document.DeterminismMerkleRoot);
Assert.Equal("sha256:1234", result.Document.Determinism!.RecipeDigest);
Assert.Equal("cas://bucket/recipe.json", result.Document.Determinism!.CompositionRecipeUri);
var attestationOrder = result.Document.Artifacts
.Single(a => a.Kind == "layer.fragments")
.Attestations!
.Select(a => a.Digest)
.ToArray();
Assert.Equal(new[] { "sha256:cccc", "sha256:dddd" }, attestationOrder);
Assert.Equal(result.Document.DeterminismMerkleRoot, result.DeterminismMerkleRoot);
}
[Fact]
public async Task TryGetByDigestAsync_ReturnsManifestAcrossTenants()
{

View File

@@ -0,0 +1,214 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using StellaOps.Scanner.Surface.FS;
using Xunit;
namespace StellaOps.Scanner.Surface.FS.Tests;
public sealed class SurfaceManifestDeterminismVerifierTests
{
[Fact]
public async Task VerifyAsync_Succeeds_WhenRecipeAndFragmentsMatch()
{
// Arrange
var fragmentContent = Encoding.UTF8.GetBytes("{\"layers\":1}");
var fragmentDigest = Sha("layer.fragments", fragmentContent);
var recipeBytes = Encoding.UTF8.GetBytes("{\"schema\":\"stellaops.composition.recipe@1\",\"artifacts\":{\"layer.fragments\":\"" + fragmentDigest + "\"}}");
var recipeDigest = $"sha256:{ShaHex(recipeBytes)}";
var merkleRoot = ShaHex(recipeBytes);
var recipeDsseBytes = BuildDeterministicDsse("application/vnd.stellaops.composition.recipe+json", recipeBytes);
var recipeDsseDigest = $"sha256:{ShaHex(recipeDsseBytes)}";
var fragmentDsseBytes = BuildDeterministicDsse("application/json", fragmentContent);
var fragmentDsseDigest = $"sha256:{ShaHex(fragmentDsseBytes)}";
var manifest = new SurfaceManifestDocument
{
Tenant = "acme",
DeterminismMerkleRoot = merkleRoot,
Artifacts = new[]
{
new SurfaceManifestArtifact
{
Kind = "composition.recipe",
Uri = "cas://bucket/recipe.json",
Digest = recipeDigest,
MediaType = "application/vnd.stellaops.composition.recipe+json",
Format = "composition.recipe",
Attestations = new[]
{
new SurfaceManifestAttestation
{
Kind = "dsse",
Digest = recipeDsseDigest,
Uri = "cas://attest/recipe.dsse.json"
}
}
},
new SurfaceManifestArtifact
{
Kind = "composition.recipe.dsse",
Uri = "cas://attest/recipe.dsse.json",
Digest = recipeDsseDigest,
MediaType = "application/vnd.dsse+json",
Format = "dsse-json"
},
new SurfaceManifestArtifact
{
Kind = "layer.fragments",
Uri = "cas://bucket/fragments.json",
Digest = fragmentDigest,
MediaType = "application/json",
Format = "json",
Attestations = new[]
{
new SurfaceManifestAttestation
{
Kind = "dsse",
Digest = fragmentDsseDigest,
Uri = "cas://attest/fragments.dsse.json"
}
}
},
new SurfaceManifestArtifact
{
Kind = "layer.fragments.dsse",
Uri = "cas://attest/fragments.dsse.json",
Digest = fragmentDsseDigest,
MediaType = "application/vnd.dsse+json",
Format = "dsse-json"
}
}
};
var loader = BuildLoader(new Dictionary<string, byte[]>
{
[recipeDigest] = recipeBytes,
[recipeDsseDigest] = recipeDsseBytes,
[fragmentDigest] = fragmentContent,
[fragmentDsseDigest] = fragmentDsseBytes
});
var verifier = new SurfaceManifestDeterminismVerifier();
// Act
var result = await verifier.VerifyAsync(manifest, loader);
// Assert
Assert.True(result.Success);
Assert.Empty(result.Errors);
Assert.Equal(merkleRoot, result.MerkleRoot);
}
[Fact]
public async Task VerifyAsync_Fails_WhenDssePayloadDoesNotMatch()
{
var fragmentContent = Encoding.UTF8.GetBytes("{\"layers\":1}");
var fragmentDigest = Sha("layer.fragments", fragmentContent);
var recipeBytes = Encoding.UTF8.GetBytes("{\"schema\":\"stellaops.composition.recipe@1\",\"artifacts\":{\"layer.fragments\":\"" + fragmentDigest + "\"}}");
var merkleRoot = ShaHex(recipeBytes);
var recipeDigest = $"sha256:{ShaHex(recipeBytes)}";
var badDsseBytes = Encoding.UTF8.GetBytes("{\"payloadType\":\"application/json\",\"payload\":\"bXlzYW1wbGU\",\"signatures\":[]}");
var badDsseDigest = $"sha256:{ShaHex(badDsseBytes)}";
var manifest = new SurfaceManifestDocument
{
Tenant = "acme",
DeterminismMerkleRoot = merkleRoot,
Artifacts = new[]
{
new SurfaceManifestArtifact
{
Kind = "composition.recipe",
Uri = "cas://bucket/recipe.json",
Digest = recipeDigest,
MediaType = "application/vnd.stellaops.composition.recipe+json",
Format = "composition.recipe",
Attestations = new[]
{
new SurfaceManifestAttestation
{
Kind = "dsse",
Digest = badDsseDigest,
Uri = "cas://attest/recipe.dsse.json"
}
}
},
new SurfaceManifestArtifact
{
Kind = "composition.recipe.dsse",
Uri = "cas://attest/recipe.dsse.json",
Digest = badDsseDigest,
MediaType = "application/vnd.dsse+json",
Format = "dsse-json"
}
}
};
var loader = BuildLoader(new Dictionary<string, byte[]>
{
[recipeDigest] = recipeBytes,
[badDsseDigest] = badDsseBytes
});
var verifier = new SurfaceManifestDeterminismVerifier();
var result = await verifier.VerifyAsync(manifest, loader);
Assert.False(result.Success);
Assert.NotEmpty(result.Errors);
}
private static Func<SurfaceManifestArtifact, Task<ReadOnlyMemory<byte>>> BuildLoader(Dictionary<string, byte[]> map)
=> artifact =>
{
if (map.TryGetValue(artifact.Digest, out var bytes))
{
return Task.FromResult((ReadOnlyMemory<byte>)bytes);
}
return Task.FromResult(ReadOnlyMemory<byte>.Empty);
};
private static string Sha(string kind, byte[] bytes) => $"sha256:{ShaHex(bytes)}";
private static string ShaHex(ReadOnlySpan<byte> bytes)
{
Span<byte> hash = stackalloc byte[32];
System.Security.Cryptography.SHA256.HashData(bytes, hash);
return Convert.ToHexString(hash).ToLowerInvariant();
}
private static byte[] BuildDeterministicDsse(string payloadType, byte[] payload)
{
var signature = ShaHex(payload);
var envelope = new
{
payloadType,
payload = Base64Url(payload),
signatures = new[]
{
new { keyid = "scanner-deterministic", sig = Base64Url(Encoding.UTF8.GetBytes(signature)) }
}
};
var json = System.Text.Json.JsonSerializer.Serialize(envelope, new System.Text.Json.JsonSerializerOptions(System.Text.Json.JsonSerializerDefaults.Web)
{
WriteIndented = false
});
return Encoding.UTF8.GetBytes(json);
}
private static string Base64Url(ReadOnlySpan<byte> data)
{
var base64 = Convert.ToBase64String(data);
return base64.Replace("+", "-").Replace("/", "_").TrimEnd('=');
}
}