more audit work

This commit is contained in:
master
2026-01-08 10:21:51 +02:00
parent 43c02081ef
commit 51cf4bc16c
546 changed files with 36721 additions and 4003 deletions

View File

@@ -6,7 +6,8 @@ namespace StellaOps.Attestor.Core.Rekor;
public sealed class RekorInclusionVerificationResult
{
/// <summary>
/// True if inclusion proof was successfully verified.
/// True if inclusion proof was successfully verified (Merkle path only).
/// Check <see cref="CheckpointSignatureValid"/> for checkpoint signature status.
/// </summary>
public required bool Verified { get; init; }

View File

@@ -15,6 +15,7 @@ using NpgsqlTypes;
using StellaOps.Attestor.Core.Observability;
using StellaOps.Attestor.Core.Options;
using StellaOps.Attestor.Core.Queue;
using StellaOps.Determinism;
namespace StellaOps.Attestor.Infrastructure.Queue;
@@ -29,6 +30,7 @@ public sealed class PostgresRekorSubmissionQueue : IRekorSubmissionQueue
private readonly AttestorMetrics _metrics;
private readonly TimeProvider _timeProvider;
private readonly ILogger<PostgresRekorSubmissionQueue> _logger;
private readonly IGuidProvider _guidProvider;
private const int DefaultCommandTimeoutSeconds = 30;
@@ -37,12 +39,14 @@ public sealed class PostgresRekorSubmissionQueue : IRekorSubmissionQueue
IOptions<RekorQueueOptions> options,
AttestorMetrics metrics,
TimeProvider timeProvider,
IGuidProvider guidProvider,
ILogger<PostgresRekorSubmissionQueue> logger)
{
_dataSource = dataSource ?? throw new ArgumentNullException(nameof(dataSource));
_options = options?.Value ?? throw new ArgumentNullException(nameof(options));
_metrics = metrics ?? throw new ArgumentNullException(nameof(metrics));
_timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider));
_guidProvider = guidProvider ?? throw new ArgumentNullException(nameof(guidProvider));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
@@ -55,7 +59,7 @@ public sealed class PostgresRekorSubmissionQueue : IRekorSubmissionQueue
CancellationToken cancellationToken = default)
{
var now = _timeProvider.GetUtcNow();
var id = Guid.NewGuid();
var id = _guidProvider.NewGuid();
const string sql = """
INSERT INTO attestor.rekor_submission_queue (
@@ -138,7 +142,7 @@ public sealed class PostgresRekorSubmissionQueue : IRekorSubmissionQueue
await using var reader = await command.ExecuteReaderAsync(cancellationToken);
while (await reader.ReadAsync(cancellationToken))
{
var queuedAt = reader.GetDateTime(reader.GetOrdinal("created_at"));
var queuedAt = reader.GetFieldValue<DateTimeOffset>(reader.GetOrdinal("created_at"));
var waitTime = (now - queuedAt).TotalSeconds;
_metrics.RekorQueueWaitTime.Record(waitTime);

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Http;
@@ -131,7 +132,15 @@ internal sealed class HttpRekorClient : IRekorClient
Origin = checkpointElement.TryGetProperty("origin", out var origin) ? origin.GetString() : null,
Size = checkpointElement.TryGetProperty("size", out var size) && size.TryGetInt64(out var sizeValue) ? sizeValue : 0,
RootHash = checkpointElement.TryGetProperty("rootHash", out var rootHash) ? rootHash.GetString() : null,
Timestamp = checkpointElement.TryGetProperty("timestamp", out var ts) && ts.ValueKind == JsonValueKind.String && DateTimeOffset.TryParse(ts.GetString(), out var dto) ? dto : null
Timestamp = checkpointElement.TryGetProperty("timestamp", out var ts)
&& ts.ValueKind == JsonValueKind.String
&& DateTimeOffset.TryParse(
ts.GetString(),
CultureInfo.InvariantCulture,
DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal,
out var dto)
? dto
: null
}
: null,
Inclusion = inclusionElement.ValueKind == JsonValueKind.Object
@@ -269,6 +278,10 @@ internal sealed class HttpRekorClient : IRekorClient
"Successfully verified Rekor inclusion for UUID {Uuid} at index {Index}",
rekorUuid, logIndex);
_logger.LogDebug(
"Checkpoint signature verification is unavailable for UUID {Uuid}; treating checkpoint as unverified",
rekorUuid);
return RekorInclusionVerificationResult.Success(
logIndex.Value,
computedRootHex,

View File

@@ -0,0 +1,55 @@
using StellaOps.Attestor.Core.Options;
using StellaOps.Attestor.Core.Rekor;
namespace StellaOps.Attestor.Infrastructure.Rekor;
internal static class RekorBackendResolver
{
public static RekorBackend ResolveBackend(
AttestorOptions options,
string? backendName,
bool allowFallbackToPrimary)
{
ArgumentNullException.ThrowIfNull(options);
var normalized = string.IsNullOrWhiteSpace(backendName)
? "primary"
: backendName.Trim();
if (string.Equals(normalized, "primary", StringComparison.OrdinalIgnoreCase))
{
return BuildBackend("primary", options.Rekor.Primary);
}
if (string.Equals(normalized, "mirror", StringComparison.OrdinalIgnoreCase))
{
return BuildBackend("mirror", options.Rekor.Mirror);
}
if (allowFallbackToPrimary)
{
return BuildBackend(normalized, options.Rekor.Primary);
}
throw new InvalidOperationException($"Unknown Rekor backend: {backendName}");
}
public static RekorBackend BuildBackend(string name, AttestorOptions.RekorBackendOptions options)
{
ArgumentException.ThrowIfNullOrWhiteSpace(name);
if (string.IsNullOrWhiteSpace(options.Url))
{
throw new InvalidOperationException($"Rekor backend '{name}' is not configured.");
}
return new RekorBackend
{
Name = name,
Url = new Uri(options.Url, UriKind.Absolute),
ProofTimeout = TimeSpan.FromMilliseconds(options.ProofTimeoutMs),
PollInterval = TimeSpan.FromMilliseconds(options.PollIntervalMs),
MaxAttempts = options.MaxAttempts
};
}
}

View File

@@ -30,6 +30,7 @@ using StellaOps.Attestor.Core.InToto;
using StellaOps.Attestor.Core.InToto.Layout;
using StellaOps.Attestor.Infrastructure.InToto;
using StellaOps.Attestor.Verify;
using StellaOps.Determinism;
namespace StellaOps.Attestor.Infrastructure;
@@ -39,6 +40,7 @@ public static class ServiceCollectionExtensions
{
services.AddMemoryCache();
services.AddSingleton(TimeProvider.System);
services.AddSystemGuidProvider();
services.AddSingleton<IDsseCanonicalizer, DefaultDsseCanonicalizer>();
services.AddSingleton(sp =>

View File

@@ -13,6 +13,7 @@
<ProjectReference Include="..\..\..\__Libraries\StellaOps.Cryptography.Plugin.BouncyCastle\StellaOps.Cryptography.Plugin.BouncyCastle.csproj" />
<ProjectReference Include="..\..\..\__Libraries\StellaOps.Cryptography.Kms\StellaOps.Cryptography.Kms.csproj" />
<ProjectReference Include="..\..\..\__Libraries\StellaOps.Cryptography.Plugin.SmSoft\StellaOps.Cryptography.Plugin.SmSoft.csproj" />
<ProjectReference Include="..\..\..\__Libraries\StellaOps.Determinism.Abstractions\StellaOps.Determinism.Abstractions.csproj" />
<ProjectReference Include="..\..\..\Router/__Libraries/StellaOps.Messaging\StellaOps.Messaging.csproj" />
</ItemGroup>
<ItemGroup>

View File

@@ -15,6 +15,7 @@ using StellaOps.Attestor.Core.Storage;
using StellaOps.Attestor.Core.Submission;
using StellaOps.Attestor.Core.Transparency;
using StellaOps.Attestor.Core.Verification;
using StellaOps.Attestor.Infrastructure.Rekor;
namespace StellaOps.Attestor.Infrastructure.Submission;
@@ -384,7 +385,7 @@ internal sealed class AttestorSubmissionService : IAttestorSubmissionService
AttestorOptions.RekorBackendOptions backendOptions,
CancellationToken cancellationToken)
{
var backend = BuildBackend(backendName, backendOptions);
var backend = RekorBackendResolver.BuildBackend(backendName, backendOptions);
var stopwatch = Stopwatch.StartNew();
try
{
@@ -782,20 +783,4 @@ internal sealed class AttestorSubmissionService : IAttestorSubmissionService
new SubmissionOutcome(backend, url ?? string.Empty, null, null, null, latency, error);
}
private static RekorBackend BuildBackend(string name, AttestorOptions.RekorBackendOptions options)
{
if (string.IsNullOrWhiteSpace(options.Url))
{
throw new InvalidOperationException($"Rekor backend '{name}' is not configured.");
}
return new RekorBackend
{
Name = name,
Url = new Uri(options.Url, UriKind.Absolute),
ProofTimeout = TimeSpan.FromMilliseconds(options.ProofTimeoutMs),
PollInterval = TimeSpan.FromMilliseconds(options.PollIntervalMs),
MaxAttempts = options.MaxAttempts
};
}
}

View File

@@ -7,5 +7,5 @@ Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.
| --- | --- | --- |
| AUDIT-0055-M | DONE | Revalidated 2026-01-06. |
| AUDIT-0055-T | DONE | Revalidated 2026-01-06. |
| AUDIT-0055-A | TODO | Reopened after revalidation 2026-01-06. |
| AUDIT-0055-A | DONE | Applied determinism, backend resolver, and Rekor client fixes 2026-01-08. |
| VAL-SMOKE-001 | DONE | Fixed continuation token behavior; unit tests pass. |

View File

@@ -14,6 +14,7 @@ using StellaOps.Attestor.Core.Storage;
using StellaOps.Attestor.Core.Submission;
using StellaOps.Attestor.Core.Transparency;
using StellaOps.Attestor.Core.Verification;
using StellaOps.Attestor.Infrastructure.Rekor;
using StellaOps.Attestor.Verify;
namespace StellaOps.Attestor.Infrastructure.Verification;
@@ -238,7 +239,7 @@ internal sealed class AttestorVerificationService : IAttestorVerificationService
var backendOptions = string.Equals(backendName, "mirror", StringComparison.OrdinalIgnoreCase)
? _options.Rekor.Mirror
: _options.Rekor.Primary;
var backend = BuildBackend(backendName ?? "primary", backendOptions);
var backend = RekorBackendResolver.ResolveBackend(_options, backendName, allowFallbackToPrimary: true);
using var activity = _activitySource.StartProofRefresh(backend.Name, _options.Verification.PolicyId);
@@ -354,23 +355,6 @@ internal sealed class AttestorVerificationService : IAttestorVerificationService
};
}
private static RekorBackend BuildBackend(string name, AttestorOptions.RekorBackendOptions options)
{
if (string.IsNullOrWhiteSpace(options.Url))
{
throw new InvalidOperationException($"Rekor backend '{name}' is not configured.");
}
return new RekorBackend
{
Name = name,
Url = new Uri(options.Url, UriKind.Absolute),
ProofTimeout = TimeSpan.FromMilliseconds(options.ProofTimeoutMs),
PollInterval = TimeSpan.FromMilliseconds(options.PollIntervalMs),
MaxAttempts = options.MaxAttempts
};
}
private static string NormalizeTag(string? value) => string.IsNullOrWhiteSpace(value) ? "unknown" : value;
}

View File

@@ -17,6 +17,7 @@ using StellaOps.Attestor.Core.Queue;
using StellaOps.Attestor.Core.Rekor;
using StellaOps.Attestor.Core.Submission;
using System.Text.Json;
using StellaOps.Attestor.Infrastructure.Rekor;
namespace StellaOps.Attestor.Infrastructure.Workers;
@@ -153,7 +154,7 @@ public sealed class RekorRetryWorker : BackgroundService
try
{
var backend = ResolveBackend(item.Backend);
var backend = RekorBackendResolver.ResolveBackend(_attestorOptions, item.Backend, allowFallbackToPrimary: false);
var request = BuildSubmissionRequest(item);
var response = await _rekorClient.SubmitAsync(request, backend, ct);
@@ -188,16 +189,6 @@ public sealed class RekorRetryWorker : BackgroundService
}
}
private RekorBackend ResolveBackend(string backend)
{
return backend.ToLowerInvariant() switch
{
"primary" => BuildBackend("primary", _attestorOptions.Rekor.Primary),
"mirror" => BuildBackend("mirror", _attestorOptions.Rekor.Mirror),
_ => throw new InvalidOperationException($"Unknown Rekor backend: {backend}")
};
}
private static AttestorSubmissionRequest BuildSubmissionRequest(RekorQueueItem item)
{
var dsseEnvelope = ParseDsseEnvelope(item.DssePayload);
@@ -260,22 +251,6 @@ public sealed class RekorRetryWorker : BackgroundService
};
}
private static RekorBackend BuildBackend(string name, AttestorOptions.RekorBackendOptions options)
{
if (string.IsNullOrWhiteSpace(options.Url))
{
throw new InvalidOperationException($"Rekor backend '{name}' is not configured.");
}
return new RekorBackend
{
Name = name,
Url = new Uri(options.Url, UriKind.Absolute),
ProofTimeout = TimeSpan.FromMilliseconds(options.ProofTimeoutMs),
PollInterval = TimeSpan.FromMilliseconds(options.PollIntervalMs),
MaxAttempts = options.MaxAttempts
};
}
}
#endif

View File

@@ -15,6 +15,7 @@ using StellaOps.Attestor.Core.Observability;
using StellaOps.Attestor.Core.Options;
using StellaOps.Attestor.Core.Queue;
using StellaOps.Attestor.Infrastructure.Queue;
using StellaOps.Determinism;
using Testcontainers.PostgreSql;
using Xunit;
@@ -63,6 +64,7 @@ public class PostgresRekorSubmissionQueueIntegrationTests : IAsyncLifetime
}),
_metrics,
_timeProvider,
SystemGuidProvider.Instance,
NullLogger<PostgresRekorSubmissionQueue>.Instance);
}
@@ -261,6 +263,7 @@ public class PostgresRekorSubmissionQueueIntegrationTests : IAsyncLifetime
Options.Create(new RekorQueueOptions { MaxAttempts = 2 }),
_metrics,
_timeProvider,
SystemGuidProvider.Instance,
NullLogger<PostgresRekorSubmissionQueue>.Instance);
var id = await queue.EnqueueAsync("tenant-1", "sha256:deadletter", new byte[] { 0x01 }, "primary");
@@ -307,6 +310,7 @@ public class PostgresRekorSubmissionQueueIntegrationTests : IAsyncLifetime
Options.Create(new RekorQueueOptions { MaxAttempts = 1 }),
_metrics,
_timeProvider,
SystemGuidProvider.Instance,
NullLogger<PostgresRekorSubmissionQueue>.Instance);
var id = await queue.EnqueueAsync("tenant-dlq", "sha256:dlq", new byte[] { 0x01 }, "primary");