partly or unimplemented features - now implemented

This commit is contained in:
master
2026-02-09 08:53:51 +02:00
parent 1bf6bbf395
commit 4bdc298ec1
674 changed files with 90194 additions and 2271 deletions

View File

@@ -0,0 +1,239 @@
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using StellaOps.Authority.Timestamping.Abstractions;
using System.Security.Cryptography;
namespace StellaOps.Authority.Timestamping.Tests;
public sealed class CiCdTimestampingServiceTests
{
[Fact]
public async Task TimestampArtifactsAsync_StoresReceipts_WithDeterministicArtifactOrdering()
{
var registry = new InMemoryArtifactTimestampRegistry();
var client = new FakeTimeStampAuthorityClient(
[
CreateSuccess("tsa-b", 0x10),
CreateSuccess("tsa-a", 0x11),
]);
var service = CreateService(
client,
registry,
new PipelineTimestampingPolicyOptions
{
DefaultPolicy = new PipelineTimestampPolicy
{
Enabled = true,
RequiredSuccessCount = 1,
MaxAttemptsPerArtifact = 1,
IncludeNonce = false,
},
});
var result = await service.TimestampArtifactsAsync(new CiCdTimestampingRequest
{
TenantId = "tenant-a",
PipelineId = "pipeline-a",
Environment = "stage",
CorrelationId = "corr-1",
Artifacts =
[
new CiCdArtifactInput { ArtifactDigest = "sha256:BBBB", ArtifactType = "sbom" },
new CiCdArtifactInput { ArtifactDigest = "sha256:AAAA", ArtifactType = "attestation" },
],
});
Assert.True(result.IsSuccess);
Assert.Equal(new[]
{
"sha256:aaaa",
"sha256:bbbb",
}, result.Artifacts.Select(static a => a.ArtifactDigest).ToArray());
var persisted = await registry.GetByPipelineAsync("tenant-a", "pipeline-a");
Assert.Equal(2, persisted.Count);
Assert.Equal("sha256:aaaa", persisted[0].ArtifactDigest);
Assert.Equal("sha256:bbbb", persisted[1].ArtifactDigest);
}
[Fact]
public async Task TimestampArtifactsAsync_DualProviderPolicy_RequiresDistinctProviders()
{
var registry = new InMemoryArtifactTimestampRegistry();
var client = new FakeTimeStampAuthorityClient(
[
CreateSuccess("tsa-a", 0x21),
CreateSuccess("tsa-a", 0x22),
CreateSuccess("tsa-b", 0x23),
]);
var service = CreateService(
client,
registry,
new PipelineTimestampingPolicyOptions
{
DefaultPolicy = new PipelineTimestampPolicy(),
Pipelines = new Dictionary<string, PipelineTimestampPolicy>(StringComparer.OrdinalIgnoreCase)
{
["release-pipeline"] = new PipelineTimestampPolicy
{
Enabled = true,
RequiredSuccessCount = 2,
MaxAttemptsPerArtifact = 3,
RequireDistinctProviders = true,
IncludeNonce = false,
},
},
});
var result = await service.TimestampArtifactsAsync(new CiCdTimestampingRequest
{
TenantId = "tenant-a",
PipelineId = "release-pipeline",
Environment = "prod",
Artifacts =
[
new CiCdArtifactInput { ArtifactDigest = "sha256:1111", ArtifactType = "sbom" },
],
});
var artifact = Assert.Single(result.Artifacts);
Assert.Equal(CiCdTimestampingArtifactStatus.Timestamped, artifact.Status);
Assert.Equal(2, artifact.Receipts.Count);
Assert.Equal(new[] { "tsa-a", "tsa-b" }, artifact.Receipts.Select(static receipt => receipt.ProviderName).ToArray());
}
[Fact]
public async Task TimestampArtifactsAsync_InvalidDigest_ReturnsFailureWithoutRegistryWrites()
{
var registry = new InMemoryArtifactTimestampRegistry();
var client = new FakeTimeStampAuthorityClient([]);
var service = CreateService(client, registry, new PipelineTimestampingPolicyOptions());
var result = await service.TimestampArtifactsAsync(new CiCdTimestampingRequest
{
TenantId = "tenant-a",
PipelineId = "pipeline-a",
Environment = "dev",
Artifacts =
[
new CiCdArtifactInput { ArtifactDigest = "not-a-hex-digest", ArtifactType = "sbom" },
],
});
var artifact = Assert.Single(result.Artifacts);
Assert.Equal(CiCdTimestampingArtifactStatus.Failed, artifact.Status);
Assert.Contains("hexadecimal", artifact.FailureReason, StringComparison.OrdinalIgnoreCase);
Assert.Empty(await registry.GetByPipelineAsync("tenant-a", "pipeline-a"));
}
[Fact]
public async Task TimestampArtifactsAsync_DisabledPolicy_SkipsArtifactsWithoutCallingTsa()
{
var registry = new InMemoryArtifactTimestampRegistry();
var client = new FakeTimeStampAuthorityClient([]);
var service = CreateService(
client,
registry,
new PipelineTimestampingPolicyOptions
{
DefaultPolicy = new PipelineTimestampPolicy(),
Pipelines = new Dictionary<string, PipelineTimestampPolicy>(StringComparer.OrdinalIgnoreCase)
{
["pipeline-a"] = new PipelineTimestampPolicy
{
Enabled = true,
Environments = new Dictionary<string, PipelineTimestampPolicy>(StringComparer.OrdinalIgnoreCase)
{
["prod"] = new PipelineTimestampPolicy
{
Enabled = false,
},
},
},
},
});
var result = await service.TimestampArtifactsAsync(new CiCdTimestampingRequest
{
TenantId = "tenant-a",
PipelineId = "pipeline-a",
Environment = "prod",
Artifacts =
[
new CiCdArtifactInput { ArtifactDigest = "sha256:aaaa", ArtifactType = "sbom" },
],
});
var artifact = Assert.Single(result.Artifacts);
Assert.Equal(CiCdTimestampingArtifactStatus.Skipped, artifact.Status);
Assert.Equal(0, client.RequestCount);
Assert.Empty(await registry.GetByPipelineAsync("tenant-a", "pipeline-a"));
}
private static CiCdTimestampingService CreateService(
ITimeStampAuthorityClient client,
IArtifactTimestampRegistry registry,
PipelineTimestampingPolicyOptions options)
{
return new CiCdTimestampingService(
client,
registry,
Options.Create(options),
new FixedTimeProvider(new DateTimeOffset(2026, 2, 8, 0, 0, 0, TimeSpan.Zero)),
NullLogger<CiCdTimestampingService>.Instance);
}
private static TimeStampResponse CreateSuccess(string providerName, byte marker)
{
return TimeStampResponse.Success(
TimestampingTestData.CreateToken(
info: TimestampingTestData.CreateTstInfo(
messageImprint: new byte[] { 0x01, 0x02 },
algorithm: HashAlgorithmName.SHA256,
genTime: new DateTimeOffset(2026, 2, 8, 0, 0, marker, TimeSpan.Zero)),
encodedToken: new[] { (byte)0x30, marker }),
providerName);
}
private sealed class FixedTimeProvider(DateTimeOffset utcNow) : TimeProvider
{
public override DateTimeOffset GetUtcNow() => utcNow;
}
private sealed class FakeTimeStampAuthorityClient(
IEnumerable<TimeStampResponse> responses) : ITimeStampAuthorityClient
{
private readonly Queue<TimeStampResponse> _responses = new(responses);
public int RequestCount { get; private set; }
public IReadOnlyList<TsaProviderInfo> Providers => [];
public Task<TimeStampResponse> GetTimeStampAsync(TimeStampRequest request, CancellationToken cancellationToken = default)
{
RequestCount++;
return Task.FromResult(_responses.Count > 0
? _responses.Dequeue()
: TimeStampResponse.Failure(PkiStatus.Rejection, PkiFailureInfo.SystemFailure, "no fake response configured"));
}
public Task<TimeStampVerificationResult> VerifyAsync(
TimeStampToken token,
ReadOnlyMemory<byte> originalHash,
TimeStampVerificationOptions? options = null,
CancellationToken cancellationToken = default)
{
return Task.FromResult(TimeStampVerificationResult.Success(
verifiedTime: token.TstInfo.GenTime,
timeRange: token.TstInfo.GetTimeRange(),
policyOid: token.TstInfo.PolicyOid));
}
public TimeStampToken ParseToken(ReadOnlyMemory<byte> encodedToken)
{
throw new NotSupportedException();
}
}
}

View File

@@ -0,0 +1,68 @@
namespace StellaOps.Authority.Timestamping.Tests;
public sealed class InMemoryArtifactTimestampRegistryTests
{
[Fact]
public async Task UpsertAsync_ReplacesExistingRecord_ByDeterministicKey()
{
var registry = new InMemoryArtifactTimestampRegistry();
var baseRecord = CreateRecord(
providerName: "tsa-a",
tokenDigest: "aa",
recordedAt: new DateTimeOffset(2026, 2, 8, 0, 0, 0, TimeSpan.Zero));
await registry.UpsertAsync(baseRecord);
await registry.UpsertAsync(baseRecord with
{
EncodedTokenBase64 = "updated-token",
RecordedAtUtc = new DateTimeOffset(2026, 2, 8, 0, 1, 0, TimeSpan.Zero),
});
var result = await registry.GetByPipelineAsync("tenant-a", "pipeline-a");
var record = Assert.Single(result);
Assert.Equal("updated-token", record.EncodedTokenBase64);
Assert.Equal(new DateTimeOffset(2026, 2, 8, 0, 1, 0, TimeSpan.Zero), record.RecordedAtUtc);
}
[Fact]
public async Task GetByArtifactDigestAsync_ReturnsDeterministicOrdering()
{
var registry = new InMemoryArtifactTimestampRegistry();
await registry.UpsertAsync(CreateRecord(
providerName: "tsa-b",
tokenDigest: "bb",
recordedAt: new DateTimeOffset(2026, 2, 8, 0, 0, 1, TimeSpan.Zero)));
await registry.UpsertAsync(CreateRecord(
providerName: "tsa-a",
tokenDigest: "aa",
recordedAt: new DateTimeOffset(2026, 2, 8, 0, 0, 0, TimeSpan.Zero)));
var result = await registry.GetByArtifactDigestAsync("sha256:aaaa");
Assert.Equal(2, result.Count);
Assert.Equal("tsa-a", result[0].ProviderName);
Assert.Equal("tsa-b", result[1].ProviderName);
}
private static ArtifactTimestampRecord CreateRecord(
string providerName,
string tokenDigest,
DateTimeOffset recordedAt)
{
return new ArtifactTimestampRecord
{
TenantId = "tenant-a",
PipelineId = "pipeline-a",
Environment = "prod",
ArtifactType = "sbom",
ArtifactDigest = "sha256:aaaa",
HashAlgorithm = "SHA256",
ProviderName = providerName,
TokenDigestSha256 = tokenDigest,
EncodedTokenBase64 = "token",
TimestampedAtUtc = new DateTimeOffset(2026, 2, 8, 0, 0, 0, TimeSpan.Zero),
RecordedAtUtc = recordedAt,
CorrelationId = "corr-1",
};
}
}

View File

@@ -6,3 +6,5 @@ Source of truth: `docs/implplan/SPRINT_20260130_002_Tools_csproj_remediation_sol
| Task ID | Status | Notes |
| --- | --- | --- |
| REMED-05 | DONE | Added unit tests for Timestamping library remediation gaps. |
| SPRINT_20260208_025-TESTS | DONE | Deterministic CI/CD timestamping service and artifact registry tests. |