Update
This commit is contained in:
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Immutable;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Globalization;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using MongoDB.Driver;
|
||||
using StellaOps.Excititor.Core;
|
||||
@@ -32,6 +33,18 @@ public sealed class ExportEngineTests
|
||||
Assert.Equal(VexExportFormat.Json, manifest.Format);
|
||||
Assert.Equal("baseline/v1", manifest.ConsensusRevision);
|
||||
Assert.Equal(1, manifest.ClaimCount);
|
||||
Assert.NotNull(dataSource.LastDataSet);
|
||||
var expectedEnvelopes = VexExportEnvelopeBuilder.Build(
|
||||
dataSource.LastDataSet!,
|
||||
VexPolicySnapshot.Default,
|
||||
context.RequestedAt);
|
||||
Assert.NotNull(manifest.ConsensusDigest);
|
||||
Assert.Equal(expectedEnvelopes.ConsensusDigest.Algorithm, manifest.ConsensusDigest!.Algorithm);
|
||||
Assert.Equal(expectedEnvelopes.ConsensusDigest.Digest, manifest.ConsensusDigest.Digest);
|
||||
Assert.NotNull(manifest.ScoreDigest);
|
||||
Assert.Equal(expectedEnvelopes.ScoreDigest.Algorithm, manifest.ScoreDigest!.Algorithm);
|
||||
Assert.Equal(expectedEnvelopes.ScoreDigest.Digest, manifest.ScoreDigest.Digest);
|
||||
Assert.Empty(manifest.QuietProvenance);
|
||||
|
||||
// second call hits cache
|
||||
var cached = await engine.ExportAsync(context, CancellationToken.None);
|
||||
@@ -114,13 +127,82 @@ public sealed class ExportEngineTests
|
||||
var manifest = await engine.ExportAsync(context, CancellationToken.None);
|
||||
|
||||
Assert.NotNull(attestation.LastRequest);
|
||||
Assert.NotNull(dataSource.LastDataSet);
|
||||
var expectedEnvelopes = VexExportEnvelopeBuilder.Build(
|
||||
dataSource.LastDataSet!,
|
||||
VexPolicySnapshot.Default,
|
||||
requestedAt);
|
||||
Assert.Equal(manifest.ExportId, attestation.LastRequest!.ExportId);
|
||||
var metadata = attestation.LastRequest.Metadata;
|
||||
Assert.True(metadata.ContainsKey("consensusDigest"), "Consensus digest metadata missing");
|
||||
Assert.Equal(expectedEnvelopes.ConsensusDigest.ToUri(), metadata["consensusDigest"]);
|
||||
Assert.True(metadata.ContainsKey("scoreDigest"), "Score digest metadata missing");
|
||||
Assert.Equal(expectedEnvelopes.ScoreDigest.ToUri(), metadata["scoreDigest"]);
|
||||
Assert.Equal(expectedEnvelopes.Consensus.Length.ToString(CultureInfo.InvariantCulture), metadata["consensusEntryCount"]);
|
||||
Assert.Equal(expectedEnvelopes.ScoreEnvelope.Entries.Length.ToString(CultureInfo.InvariantCulture), metadata["scoreEntryCount"]);
|
||||
Assert.Equal(VexPolicySnapshot.Default.RevisionId, metadata["policyRevisionId"]);
|
||||
Assert.Equal(VexPolicySnapshot.Default.Version, metadata["policyVersion"]);
|
||||
Assert.Equal(VexPolicySnapshot.Default.ConsensusOptions.Alpha.ToString("G17", CultureInfo.InvariantCulture), metadata["scoreAlpha"]);
|
||||
Assert.Equal(VexPolicySnapshot.Default.ConsensusOptions.Beta.ToString("G17", CultureInfo.InvariantCulture), metadata["scoreBeta"]);
|
||||
Assert.Equal(VexPolicySnapshot.Default.ConsensusOptions.WeightCeiling.ToString("G17", CultureInfo.InvariantCulture), metadata["scoreWeightCeiling"]);
|
||||
Assert.NotNull(manifest.Attestation);
|
||||
Assert.Equal(attestation.Response.Attestation.EnvelopeDigest, manifest.Attestation!.EnvelopeDigest);
|
||||
Assert.Equal(attestation.Response.Attestation.PredicateType, manifest.Attestation.PredicateType);
|
||||
Assert.NotNull(manifest.ConsensusDigest);
|
||||
Assert.Equal(expectedEnvelopes.ConsensusDigest.Digest, manifest.ConsensusDigest!.Digest);
|
||||
Assert.NotNull(manifest.ScoreDigest);
|
||||
Assert.Equal(expectedEnvelopes.ScoreDigest.Digest, manifest.ScoreDigest!.Digest);
|
||||
Assert.Empty(manifest.QuietProvenance);
|
||||
|
||||
Assert.NotNull(store.LastSavedManifest);
|
||||
Assert.Equal(manifest.Attestation, store.LastSavedManifest!.Attestation);
|
||||
Assert.Equal(manifest.QuietProvenance, store.LastSavedManifest!.QuietProvenance);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExportAsync_IncludesQuietProvenanceMetadata()
|
||||
{
|
||||
var store = new InMemoryExportStore();
|
||||
var evaluator = new StaticPolicyEvaluator("baseline/v1");
|
||||
var dataSource = new QuietExportDataSource();
|
||||
var exporter = new DummyExporter(VexExportFormat.Json);
|
||||
var attestation = new RecordingAttestationClient();
|
||||
var engine = new VexExportEngine(
|
||||
store,
|
||||
evaluator,
|
||||
dataSource,
|
||||
new[] { exporter },
|
||||
NullLogger<VexExportEngine>.Instance,
|
||||
cacheIndex: null,
|
||||
artifactStores: null,
|
||||
attestationClient: attestation);
|
||||
|
||||
var query = VexQuery.Create(new[] { new VexQueryFilter("vulnId", "CVE-2025-0002") });
|
||||
var requestedAt = DateTimeOffset.UtcNow;
|
||||
var context = new VexExportRequestContext(query, VexExportFormat.Json, requestedAt);
|
||||
|
||||
var manifest = await engine.ExportAsync(context, CancellationToken.None);
|
||||
|
||||
var quiet = Assert.Single(manifest.QuietProvenance);
|
||||
Assert.Equal("CVE-2025-0002", quiet.VulnerabilityId);
|
||||
Assert.Equal("pkg:demo/app", quiet.ProductKey);
|
||||
var statement = Assert.Single(quiet.Statements);
|
||||
Assert.Equal("vendor", statement.ProviderId);
|
||||
Assert.Equal("sha256:quiet", statement.StatementId);
|
||||
Assert.Equal(VexJustification.ComponentNotPresent, statement.Justification);
|
||||
Assert.NotNull(statement.Signature);
|
||||
Assert.Equal("quiet-signer", statement.Signature!.Subject);
|
||||
Assert.Equal("quiet-key", statement.Signature.KeyId);
|
||||
|
||||
var expectedQuietJson = VexCanonicalJsonSerializer.Serialize(manifest.QuietProvenance);
|
||||
Assert.NotNull(attestation.LastRequest);
|
||||
Assert.True(attestation.LastRequest!.Metadata.TryGetValue("quietedBy", out var quietJson));
|
||||
Assert.Equal(expectedQuietJson, quietJson);
|
||||
Assert.True(attestation.LastRequest.Metadata.TryGetValue("quietedByStatementCount", out var quietCount));
|
||||
Assert.Equal("1", quietCount);
|
||||
|
||||
Assert.NotNull(store.LastSavedManifest);
|
||||
Assert.Equal(manifest.QuietProvenance, store.LastSavedManifest!.QuietProvenance);
|
||||
}
|
||||
|
||||
private sealed class InMemoryExportStore : IVexExportStore
|
||||
@@ -148,6 +230,48 @@ public sealed class ExportEngineTests
|
||||
=> FormattableString.Invariant($"{signature}|{format}");
|
||||
}
|
||||
|
||||
private sealed class QuietExportDataSource : IVexExportDataSource
|
||||
{
|
||||
public ValueTask<VexExportDataSet> FetchAsync(VexQuery query, CancellationToken cancellationToken)
|
||||
{
|
||||
var signature = new VexSignatureMetadata(
|
||||
type: "pgp",
|
||||
subject: "quiet-signer",
|
||||
issuer: "quiet-ca",
|
||||
keyId: "quiet-key",
|
||||
verifiedAt: DateTimeOffset.UnixEpoch,
|
||||
transparencyLogReference: "rekor://quiet");
|
||||
|
||||
var claim = new VexClaim(
|
||||
"CVE-2025-0002",
|
||||
"vendor",
|
||||
new VexProduct("pkg:demo/app", "Demo"),
|
||||
VexClaimStatus.NotAffected,
|
||||
new VexClaimDocument(VexDocumentFormat.OpenVex, "sha256:quiet", new Uri("https://example.org/quiet"), signature: signature),
|
||||
DateTimeOffset.UtcNow,
|
||||
DateTimeOffset.UtcNow,
|
||||
justification: VexJustification.ComponentNotPresent);
|
||||
|
||||
var consensus = new VexConsensus(
|
||||
"CVE-2025-0002",
|
||||
claim.Product,
|
||||
VexConsensusStatus.NotAffected,
|
||||
DateTimeOffset.UtcNow,
|
||||
new[]
|
||||
{
|
||||
new VexConsensusSource("vendor", VexClaimStatus.NotAffected, "sha256:quiet", 1.0, claim.Justification),
|
||||
},
|
||||
conflicts: null,
|
||||
policyVersion: "baseline/v1",
|
||||
summary: "not_affected");
|
||||
|
||||
return ValueTask.FromResult(new VexExportDataSet(
|
||||
ImmutableArray.Create(consensus),
|
||||
ImmutableArray.Create(claim),
|
||||
ImmutableArray.Create("vendor")));
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class RecordingAttestationClient : IVexAttestationClient
|
||||
{
|
||||
public VexAttestationRequest? LastRequest { get; private set; }
|
||||
@@ -226,6 +350,8 @@ public sealed class ExportEngineTests
|
||||
|
||||
private sealed class InMemoryExportDataSource : IVexExportDataSource
|
||||
{
|
||||
public VexExportDataSet? LastDataSet { get; private set; }
|
||||
|
||||
public ValueTask<VexExportDataSet> FetchAsync(VexQuery query, CancellationToken cancellationToken)
|
||||
{
|
||||
var claim = new VexClaim(
|
||||
@@ -247,10 +373,13 @@ public sealed class ExportEngineTests
|
||||
policyVersion: "baseline/v1",
|
||||
summary: "affected");
|
||||
|
||||
return ValueTask.FromResult(new VexExportDataSet(
|
||||
var dataSet = new VexExportDataSet(
|
||||
ImmutableArray.Create(consensus),
|
||||
ImmutableArray.Create(claim),
|
||||
ImmutableArray.Create("vendor")));
|
||||
ImmutableArray.Create("vendor"));
|
||||
|
||||
LastDataSet = dataSet;
|
||||
return ValueTask.FromResult(dataSet);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user