audit, advisories and doctors/setup work

This commit is contained in:
master
2026-01-13 18:53:39 +02:00
parent 9ca7cb183e
commit d7be6ba34b
811 changed files with 54242 additions and 4056 deletions

View File

@@ -32,12 +32,12 @@ public sealed class FixChainAttestationIntegrationTests
services.AddSingleton<TimeProvider>(_timeProvider);
services.AddLogging();
services.Configure<FixChainOptions>(opts =>
services.AddSingleton(Options.Create(new FixChainOptions
{
opts.AnalyzerName = "TestAnalyzer";
opts.AnalyzerVersion = "1.0.0";
opts.AnalyzerSourceDigest = "sha256:integrationtest";
});
AnalyzerName = "TestAnalyzer",
AnalyzerVersion = "1.0.0",
AnalyzerSourceDigest = "sha256:integrationtest"
}));
services.AddFixChainAttestation();

View File

@@ -12,7 +12,7 @@
<ItemGroup>
<PackageReference Include="Moq" />
<PackageReference Include="FluentAssertions" />
<PackageReference Include="Microsoft.Extensions.Time.Testing" />
<PackageReference Include="Microsoft.Extensions.TimeProvider.Testing" />
</ItemGroup>
<ItemGroup>

View File

@@ -415,7 +415,7 @@ public sealed class FixChainValidatorTests
// Assert
result.IsValid.Should().BeFalse();
result.Errors.Should().HaveCountGreaterOrEqualTo(3);
result.Errors.Should().HaveCountGreaterThanOrEqualTo(3);
}
private static FixChainPredicate CreateValidPredicate()

View File

@@ -0,0 +1,72 @@
using FluentAssertions;
using StellaOps.Attestor.Envelope;
using StellaOps.Attestor.StandardPredicates.BinaryDiff;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Attestor.StandardPredicates.Tests.BinaryDiff;
public sealed class BinaryDiffDsseSignerTests
{
private readonly EnvelopeSignatureService _signatureService = new();
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task SignAndVerify_Succeeds()
{
var predicate = BinaryDiffTestData.CreatePredicate();
var serializer = new BinaryDiffPredicateSerializer();
var signer = new BinaryDiffDsseSigner(_signatureService, serializer);
var verifier = new BinaryDiffDsseVerifier(_signatureService, serializer);
var keys = BinaryDiffTestData.CreateDeterministicKeyPair("binarydiff-key");
var signResult = await signer.SignAsync(predicate, keys.Signer);
var envelope = new DsseEnvelope(signResult.PayloadType, signResult.Payload, signResult.Signatures);
var verifyResult = verifier.Verify(envelope, keys.Verifier);
verifyResult.IsValid.Should().BeTrue();
verifyResult.Predicate.Should().NotBeNull();
verifyResult.VerifiedKeyId.Should().Be(keys.Signer.KeyId);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Verify_Fails_OnTamperedPayload()
{
var predicate = BinaryDiffTestData.CreatePredicate();
var serializer = new BinaryDiffPredicateSerializer();
var signer = new BinaryDiffDsseSigner(_signatureService, serializer);
var verifier = new BinaryDiffDsseVerifier(_signatureService, serializer);
var keys = BinaryDiffTestData.CreateDeterministicKeyPair("binarydiff-key");
var signResult = await signer.SignAsync(predicate, keys.Signer);
var tamperedPayload = signResult.Payload.ToArray();
tamperedPayload[^1] ^= 0xFF;
var envelope = new DsseEnvelope(signResult.PayloadType, tamperedPayload, signResult.Signatures);
var verifyResult = verifier.Verify(envelope, keys.Verifier);
verifyResult.IsValid.Should().BeFalse();
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Verify_Fails_OnSchemaViolation()
{
var invalidJson = "{\"predicateType\":\"stellaops.binarydiff.v1\"}";
var payload = System.Text.Encoding.UTF8.GetBytes(invalidJson);
var keys = BinaryDiffTestData.CreateDeterministicKeyPair("binarydiff-key");
var signResult = _signatureService.SignDsse(BinaryDiffPredicate.PredicateType, payload, keys.Signer);
signResult.IsSuccess.Should().BeTrue();
var signature = DsseSignature.FromBytes(signResult.Value!.Value.Span, signResult.Value.KeyId);
var envelope = new DsseEnvelope(BinaryDiffPredicate.PredicateType, payload, new[] { signature });
var verifier = new BinaryDiffDsseVerifier(_signatureService, new BinaryDiffPredicateSerializer());
var verifyResult = verifier.Verify(envelope, keys.Verifier);
verifyResult.IsValid.Should().BeFalse();
verifyResult.SchemaErrors.Should().NotBeEmpty();
}
}

View File

@@ -0,0 +1,122 @@
using System.Collections.Immutable;
using FluentAssertions;
using Microsoft.Extensions.Options;
using StellaOps.Attestor.StandardPredicates.BinaryDiff;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Attestor.StandardPredicates.Tests.BinaryDiff;
public sealed class BinaryDiffPredicateBuilderTests
{
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Build_RequiresSubject()
{
var options = Options.Create(new BinaryDiffOptions { ToolVersion = "1.0.0" });
var builder = new BinaryDiffPredicateBuilder(options, BinaryDiffTestData.FixedTimeProvider);
builder.WithInputs(
new BinaryDiffImageReference { Digest = "sha256:base" },
new BinaryDiffImageReference { Digest = "sha256:target" });
Action act = () => builder.Build();
act.Should().Throw<InvalidOperationException>()
.WithMessage("*subject*");
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Build_RequiresInputs()
{
var options = Options.Create(new BinaryDiffOptions { ToolVersion = "1.0.0" });
var builder = new BinaryDiffPredicateBuilder(options, BinaryDiffTestData.FixedTimeProvider);
builder.WithSubject("docker://example/app@sha256:base", "sha256:aaaa");
Action act = () => builder.Build();
act.Should().Throw<InvalidOperationException>()
.WithMessage("*Inputs*");
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Build_SortsFindingsAndSections()
{
var options = Options.Create(new BinaryDiffOptions { ToolVersion = "1.0.0" });
var builder = new BinaryDiffPredicateBuilder(options, BinaryDiffTestData.FixedTimeProvider);
builder.WithSubject("docker://example/app@sha256:base", "sha256:aaaa")
.WithInputs(
new BinaryDiffImageReference { Digest = "sha256:base" },
new BinaryDiffImageReference { Digest = "sha256:target" })
.AddFinding(new BinaryDiffFinding
{
Path = "/z/libz.so",
ChangeType = ChangeType.Modified,
BinaryFormat = BinaryFormat.Elf,
SectionDeltas = ImmutableArray.Create(
new SectionDelta
{
Section = ".text",
Status = SectionStatus.Modified
},
new SectionDelta
{
Section = ".bss",
Status = SectionStatus.Added
})
})
.AddFinding(new BinaryDiffFinding
{
Path = "/a/liba.so",
ChangeType = ChangeType.Added,
BinaryFormat = BinaryFormat.Elf,
SectionDeltas = ImmutableArray.Create(
new SectionDelta
{
Section = ".zlast",
Status = SectionStatus.Added
},
new SectionDelta
{
Section = ".afirst",
Status = SectionStatus.Added
})
})
.WithMetadata(metadata => metadata.WithTotals(2, 1));
var predicate = builder.Build();
predicate.Findings[0].Path.Should().Be("/a/liba.so");
predicate.Findings[1].Path.Should().Be("/z/libz.so");
predicate.Findings[0].SectionDeltas[0].Section.Should().Be(".afirst");
predicate.Findings[0].SectionDeltas[1].Section.Should().Be(".zlast");
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Build_UsesOptionsDefaults()
{
var options = Options.Create(new BinaryDiffOptions
{
ToolVersion = "2.0.0",
ConfigDigest = "sha256:cfg",
AnalyzedSections = [".z", ".a"]
});
var builder = new BinaryDiffPredicateBuilder(options, BinaryDiffTestData.FixedTimeProvider);
builder.WithSubject("docker://example/app@sha256:base", "sha256:aaaa")
.WithInputs(
new BinaryDiffImageReference { Digest = "sha256:base" },
new BinaryDiffImageReference { Digest = "sha256:target" });
var predicate = builder.Build();
predicate.Metadata.ToolVersion.Should().Be("2.0.0");
predicate.Metadata.ConfigDigest.Should().Be("sha256:cfg");
predicate.Metadata.AnalysisTimestamp.Should().Be(BinaryDiffTestData.FixedTimeProvider.GetUtcNow());
predicate.Metadata.AnalyzedSections.Should().Equal(".a", ".z");
}
}

View File

@@ -0,0 +1,35 @@
using FluentAssertions;
using StellaOps.Attestor.StandardPredicates.BinaryDiff;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Attestor.StandardPredicates.Tests.BinaryDiff;
public sealed class BinaryDiffPredicateSerializerTests
{
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Serialize_IsDeterministic()
{
var predicate = BinaryDiffTestData.CreatePredicate();
var serializer = new BinaryDiffPredicateSerializer();
var jsonA = serializer.Serialize(predicate);
var jsonB = serializer.Serialize(predicate);
jsonA.Should().Be(jsonB);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Serialize_RoundTrip_ProducesEquivalentPredicate()
{
var predicate = BinaryDiffTestData.CreatePredicate();
var serializer = new BinaryDiffPredicateSerializer();
var json = serializer.Serialize(predicate);
var roundTrip = serializer.Deserialize(json);
roundTrip.Should().BeEquivalentTo(predicate);
}
}

View File

@@ -0,0 +1,71 @@
using System.Text.Json;
using FluentAssertions;
using Json.Schema;
using StellaOps.Attestor.StandardPredicates.BinaryDiff;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Attestor.StandardPredicates.Tests.BinaryDiff;
public sealed class BinaryDiffSchemaValidationTests
{
[Trait("Category", TestCategories.Unit)]
[Fact]
public void SchemaFile_ValidatesSamplePredicate()
{
var schema = LoadSchemaFromDocs();
var predicate = BinaryDiffTestData.CreatePredicate();
var serializer = new BinaryDiffPredicateSerializer();
var json = serializer.Serialize(predicate);
using var document = JsonDocument.Parse(json);
var result = schema.Evaluate(document.RootElement, new EvaluationOptions
{
OutputFormat = OutputFormat.List,
RequireFormatValidation = true
});
result.IsValid.Should().BeTrue();
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void InlineSchema_RejectsMissingRequiredFields()
{
using var document = JsonDocument.Parse("{\"predicateType\":\"stellaops.binarydiff.v1\"}");
var result = BinaryDiffSchema.Validate(document.RootElement);
result.IsValid.Should().BeFalse();
result.Errors.Should().NotBeEmpty();
}
private static JsonSchema LoadSchemaFromDocs()
{
var root = FindRepoRoot();
var schemaPath = Path.Combine(root, "docs", "schemas", "binarydiff-v1.schema.json");
File.Exists(schemaPath).Should().BeTrue($"schema file should exist at '{schemaPath}'");
var schemaText = File.ReadAllText(schemaPath);
return JsonSchema.FromText(schemaText, new BuildOptions
{
SchemaRegistry = new SchemaRegistry()
});
}
private static string FindRepoRoot()
{
var directory = new DirectoryInfo(AppContext.BaseDirectory);
while (directory is not null)
{
var docs = Path.Combine(directory.FullName, "docs");
var src = Path.Combine(directory.FullName, "src");
if (Directory.Exists(docs) && Directory.Exists(src))
{
return directory.FullName;
}
directory = directory.Parent;
}
throw new DirectoryNotFoundException("Repository root not found.");
}
}

View File

@@ -0,0 +1,138 @@
using System.Collections.Immutable;
using Microsoft.Extensions.Options;
using Org.BouncyCastle.Crypto.Parameters;
using StellaOps.Attestor.Envelope;
using StellaOps.Attestor.StandardPredicates.BinaryDiff;
namespace StellaOps.Attestor.StandardPredicates.Tests.BinaryDiff;
internal static class BinaryDiffTestData
{
internal static readonly TimeProvider FixedTimeProvider =
new FixedTimeProvider(new DateTimeOffset(2026, 1, 13, 12, 0, 0, TimeSpan.Zero));
internal static BinaryDiffPredicate CreatePredicate()
{
var options = Options.Create(new BinaryDiffOptions
{
ToolVersion = "1.0.0",
ConfigDigest = "sha256:config",
AnalyzedSections = [".text", ".rodata", ".data"]
});
var builder = new BinaryDiffPredicateBuilder(options, FixedTimeProvider);
builder.WithSubject("docker://example/app@sha256:base", "sha256:aaaaaaaa")
.WithInputs(
new BinaryDiffImageReference
{
Digest = "sha256:base",
Reference = "docker://example/app:base"
},
new BinaryDiffImageReference
{
Digest = "sha256:target",
Reference = "docker://example/app:target"
})
.AddFinding(new BinaryDiffFinding
{
Path = "/usr/lib/libssl.so.3",
ChangeType = ChangeType.Modified,
BinaryFormat = BinaryFormat.Elf,
LayerDigest = "sha256:layer1",
BaseHashes = new SectionHashSet
{
BuildId = "buildid-base",
FileHash = "sha256:file-base",
Sections = ImmutableDictionary.CreateRange(
StringComparer.Ordinal,
new[]
{
new KeyValuePair<string, SectionInfo>(".text", new SectionInfo
{
Sha256 = "sha256:text-base",
Size = 1024
}),
new KeyValuePair<string, SectionInfo>(".rodata", new SectionInfo
{
Sha256 = "sha256:rodata-base",
Size = 512
})
})
},
TargetHashes = new SectionHashSet
{
BuildId = "buildid-target",
FileHash = "sha256:file-target",
Sections = ImmutableDictionary.CreateRange(
StringComparer.Ordinal,
new[]
{
new KeyValuePair<string, SectionInfo>(".text", new SectionInfo
{
Sha256 = "sha256:text-target",
Size = 1200
}),
new KeyValuePair<string, SectionInfo>(".rodata", new SectionInfo
{
Sha256 = "sha256:rodata-target",
Size = 512
})
})
},
SectionDeltas = ImmutableArray.Create(
new SectionDelta
{
Section = ".text",
Status = SectionStatus.Modified,
BaseSha256 = "sha256:text-base",
TargetSha256 = "sha256:text-target",
SizeDelta = 176
},
new SectionDelta
{
Section = ".rodata",
Status = SectionStatus.Identical,
BaseSha256 = "sha256:rodata-base",
TargetSha256 = "sha256:rodata-target",
SizeDelta = 0
}),
Confidence = 0.9,
Verdict = Verdict.Patched
})
.WithMetadata(metadata => metadata.WithTotals(1, 1));
return builder.Build();
}
internal static BinaryDiffKeyPair CreateDeterministicKeyPair(string keyId)
{
var seed = new byte[32];
for (var i = 0; i < seed.Length; i++)
{
seed[i] = (byte)(i + 1);
}
var privateKeyParameters = new Ed25519PrivateKeyParameters(seed, 0);
var publicKeyParameters = privateKeyParameters.GeneratePublicKey();
var publicKey = publicKeyParameters.GetEncoded();
var privateKey = privateKeyParameters.GetEncoded();
var signer = EnvelopeKey.CreateEd25519Signer(privateKey, publicKey, keyId);
var verifier = EnvelopeKey.CreateEd25519Verifier(publicKey, keyId);
return new BinaryDiffKeyPair(signer, verifier);
}
private sealed class FixedTimeProvider : TimeProvider
{
private readonly DateTimeOffset _fixedTime;
public FixedTimeProvider(DateTimeOffset fixedTime)
{
_fixedTime = fixedTime;
}
public override DateTimeOffset GetUtcNow() => _fixedTime;
}
}
internal sealed record BinaryDiffKeyPair(EnvelopeKey Signer, EnvelopeKey Verifier);

View File

@@ -1,10 +1,11 @@
# Attestor StandardPredicates Tests Task Board
This board mirrors active sprint tasks for this module.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
Source of truth: `docs/implplan/SPRINT_20260113_001_002_ATTESTOR_binary_diff_predicate.md`.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0065-M | DONE | Revalidated 2026-01-06. |
| AUDIT-0065-T | DONE | Revalidated 2026-01-06. |
| AUDIT-0065-A | DONE | Waived after revalidation 2026-01-06. |
| BINARYDIFF-TESTS-0001 | DONE | Add unit tests for BinaryDiff predicate, serializer, signer, and verifier. |