work work hard work
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||
<UseConcelierTestInfra>false</UseConcelierTestInfra>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4" />
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
|
||||
<PackageReference Include="NSubstitute" Version="5.1.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.3" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\__Libraries\StellaOps.Attestor.Persistence\StellaOps.Attestor.Persistence.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,185 @@
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using NSubstitute;
|
||||
using StellaOps.Attestor.Persistence.Entities;
|
||||
using StellaOps.Attestor.Persistence.Repositories;
|
||||
using StellaOps.Attestor.Persistence.Services;
|
||||
|
||||
namespace StellaOps.Attestor.Persistence.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for trust anchor glob matching and allowlists.
|
||||
/// Sprint: SPRINT_0501_0006_0001_proof_chain_database_schema
|
||||
/// Task: PROOF-DB-0010
|
||||
/// </summary>
|
||||
public sealed class TrustAnchorMatcherTests
|
||||
{
|
||||
private readonly IProofChainRepository _repository;
|
||||
private readonly TrustAnchorMatcher _matcher;
|
||||
|
||||
public TrustAnchorMatcherTests()
|
||||
{
|
||||
_repository = Substitute.For<IProofChainRepository>();
|
||||
_matcher = new TrustAnchorMatcher(_repository, NullLogger<TrustAnchorMatcher>.Instance);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FindMatchAsync_ExactPattern_MatchesCorrectly()
|
||||
{
|
||||
var anchor = CreateAnchor("pkg:npm/lodash@4.17.21", ["key-1"]);
|
||||
await SeedAnchors(anchor);
|
||||
|
||||
var result = await _matcher.FindMatchAsync("pkg:npm/lodash@4.17.21");
|
||||
|
||||
result.Should().NotBeNull();
|
||||
result!.Anchor.AnchorId.Should().Be(anchor.AnchorId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FindMatchAsync_WildcardPattern_MatchesPackages()
|
||||
{
|
||||
var anchor = CreateAnchor("pkg:npm/*", ["key-1"]);
|
||||
await SeedAnchors(anchor);
|
||||
|
||||
var result = await _matcher.FindMatchAsync("pkg:npm/lodash@4.17.21");
|
||||
|
||||
result.Should().NotBeNull();
|
||||
result!.MatchedPattern.Should().Be("pkg:npm/*");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FindMatchAsync_DoubleWildcard_MatchesNestedPaths()
|
||||
{
|
||||
var anchor = CreateAnchor("pkg:npm/@scope/**", ["key-1"]);
|
||||
await SeedAnchors(anchor);
|
||||
|
||||
var result = await _matcher.FindMatchAsync("pkg:npm/@scope/sub/package@1.0.0");
|
||||
|
||||
result.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FindMatchAsync_MultipleMatches_ReturnsMoreSpecific()
|
||||
{
|
||||
var genericAnchor = CreateAnchor("pkg:npm/*", ["key-generic"], policyRef: "generic");
|
||||
var specificAnchor = CreateAnchor("pkg:npm/lodash@*", ["key-specific"], policyRef: "specific");
|
||||
await SeedAnchors(genericAnchor, specificAnchor);
|
||||
|
||||
var result = await _matcher.FindMatchAsync("pkg:npm/lodash@4.17.21");
|
||||
|
||||
result.Should().NotBeNull();
|
||||
result!.Anchor.PolicyRef.Should().Be("specific");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FindMatchAsync_NoMatch_ReturnsNull()
|
||||
{
|
||||
var anchor = CreateAnchor("pkg:npm/*", ["key-1"]);
|
||||
await SeedAnchors(anchor);
|
||||
|
||||
var result = await _matcher.FindMatchAsync("pkg:pypi/requests@2.28.0");
|
||||
|
||||
result.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task IsKeyAllowedAsync_AllowedKey_ReturnsTrue()
|
||||
{
|
||||
var anchor = CreateAnchor("pkg:npm/*", ["key-1", "key-2"]);
|
||||
await SeedAnchors(anchor);
|
||||
|
||||
var allowed = await _matcher.IsKeyAllowedAsync("pkg:npm/lodash@4.17.21", "key-1");
|
||||
|
||||
allowed.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task IsKeyAllowedAsync_DisallowedKey_ReturnsFalse()
|
||||
{
|
||||
var anchor = CreateAnchor("pkg:npm/*", ["key-1"]);
|
||||
await SeedAnchors(anchor);
|
||||
|
||||
var allowed = await _matcher.IsKeyAllowedAsync("pkg:npm/lodash@4.17.21", "key-unknown");
|
||||
|
||||
allowed.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task IsKeyAllowedAsync_RevokedKey_ReturnsFalse()
|
||||
{
|
||||
var anchor = CreateAnchor("pkg:npm/*", ["key-1"], revokedKeys: ["key-1"]);
|
||||
await SeedAnchors(anchor);
|
||||
|
||||
var allowed = await _matcher.IsKeyAllowedAsync("pkg:npm/lodash@4.17.21", "key-1");
|
||||
|
||||
allowed.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task IsPredicateAllowedAsync_NoRestrictions_AllowsAll()
|
||||
{
|
||||
var anchor = CreateAnchor("pkg:npm/*", ["key-1"]);
|
||||
anchor.AllowedPredicateTypes = null;
|
||||
await SeedAnchors(anchor);
|
||||
|
||||
var allowed = await _matcher.IsPredicateAllowedAsync(
|
||||
"pkg:npm/lodash@4.17.21",
|
||||
"https://in-toto.io/attestation/vulns/v0.1");
|
||||
|
||||
allowed.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task IsPredicateAllowedAsync_WithRestrictions_EnforcesAllowlist()
|
||||
{
|
||||
var anchor = CreateAnchor("pkg:npm/*", ["key-1"]);
|
||||
anchor.AllowedPredicateTypes = ["evidence.stella/v1", "sbom.stella/v1"];
|
||||
await SeedAnchors(anchor);
|
||||
|
||||
(await _matcher.IsPredicateAllowedAsync("pkg:npm/lodash@4.17.21", "evidence.stella/v1")).Should().BeTrue();
|
||||
(await _matcher.IsPredicateAllowedAsync("pkg:npm/lodash@4.17.21", "random.predicate/v1")).Should().BeFalse();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("pkg:npm/*", "pkg:npm/lodash@4.17.21", true)]
|
||||
[InlineData("pkg:npm/lodash@*", "pkg:npm/lodash@4.17.21", true)]
|
||||
[InlineData("pkg:npm/lodash@4.17.*", "pkg:npm/lodash@4.17.21", true)]
|
||||
[InlineData("pkg:npm/lodash@4.17.21", "pkg:npm/lodash@4.17.21", true)]
|
||||
[InlineData("pkg:npm/lodash@4.17.21", "pkg:npm/lodash@4.17.22", false)]
|
||||
[InlineData("pkg:pypi/*", "pkg:npm/lodash@4.17.21", false)]
|
||||
[InlineData("pkg:npm/@scope/*", "pkg:npm/@scope/package@1.0.0", true)]
|
||||
[InlineData("pkg:npm/@scope/*", "pkg:npm/@other/package@1.0.0", false)]
|
||||
public async Task FindMatchAsync_PatternVariations_MatchCorrectly(string pattern, string purl, bool shouldMatch)
|
||||
{
|
||||
var anchor = CreateAnchor(pattern, ["key-1"]);
|
||||
await SeedAnchors(anchor);
|
||||
|
||||
var result = await _matcher.FindMatchAsync(purl);
|
||||
|
||||
(result != null).Should().Be(shouldMatch);
|
||||
}
|
||||
|
||||
private Task SeedAnchors(params TrustAnchorEntity[] anchors)
|
||||
{
|
||||
_repository.GetActiveTrustAnchorsAsync(Arg.Any<CancellationToken>())
|
||||
.Returns(Task.FromResult<IReadOnlyList<TrustAnchorEntity>>(anchors));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private static TrustAnchorEntity CreateAnchor(
|
||||
string pattern,
|
||||
string[] allowedKeys,
|
||||
string? policyRef = null,
|
||||
string[]? revokedKeys = null)
|
||||
{
|
||||
return new TrustAnchorEntity
|
||||
{
|
||||
AnchorId = Guid.NewGuid(),
|
||||
PurlPattern = pattern,
|
||||
AllowedKeyIds = allowedKeys,
|
||||
PolicyRef = policyRef,
|
||||
RevokedKeys = revokedKeys ?? []
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user