save progress
This commit is contained in:
@@ -3,6 +3,7 @@ using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StellaOps.Determinism;
|
||||
using StellaOps.Signer.Core;
|
||||
|
||||
namespace StellaOps.Signer.Infrastructure.Auditing;
|
||||
@@ -11,11 +12,16 @@ public sealed class InMemorySignerAuditSink : ISignerAuditSink
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, SignerAuditEntry> _entries = new(StringComparer.Ordinal);
|
||||
private readonly TimeProvider _timeProvider;
|
||||
private readonly IGuidProvider _guidProvider;
|
||||
private readonly ILogger<InMemorySignerAuditSink> _logger;
|
||||
|
||||
public InMemorySignerAuditSink(TimeProvider timeProvider, ILogger<InMemorySignerAuditSink> logger)
|
||||
public InMemorySignerAuditSink(
|
||||
TimeProvider timeProvider,
|
||||
ILogger<InMemorySignerAuditSink> logger,
|
||||
IGuidProvider? guidProvider = null)
|
||||
{
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
_guidProvider = guidProvider ?? SystemGuidProvider.Instance;
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
@@ -30,7 +36,7 @@ public sealed class InMemorySignerAuditSink : ISignerAuditSink
|
||||
ArgumentNullException.ThrowIfNull(entitlement);
|
||||
ArgumentNullException.ThrowIfNull(caller);
|
||||
|
||||
var auditId = Guid.NewGuid().ToString("d");
|
||||
var auditId = _guidProvider.NewGuid().ToString("d");
|
||||
var entry = new SignerAuditEntry(
|
||||
auditId,
|
||||
_timeProvider.GetUtcNow(),
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Determinism;
|
||||
using StellaOps.Signer.Core;
|
||||
|
||||
namespace StellaOps.Signer.Infrastructure.Signing;
|
||||
@@ -17,15 +18,18 @@ public sealed class DefaultSigningKeyResolver : ISigningKeyResolver
|
||||
|
||||
private readonly DsseSignerOptions _options;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
private readonly IGuidProvider _guidProvider;
|
||||
private readonly ILogger<DefaultSigningKeyResolver> _logger;
|
||||
|
||||
public DefaultSigningKeyResolver(
|
||||
IOptions<DsseSignerOptions> options,
|
||||
TimeProvider timeProvider,
|
||||
ILogger<DefaultSigningKeyResolver> logger)
|
||||
ILogger<DefaultSigningKeyResolver> logger,
|
||||
IGuidProvider? guidProvider = null)
|
||||
{
|
||||
_options = options?.Value ?? throw new ArgumentNullException(nameof(options));
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
_guidProvider = guidProvider ?? SystemGuidProvider.Instance;
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
@@ -56,7 +60,7 @@ public sealed class DefaultSigningKeyResolver : ISigningKeyResolver
|
||||
{
|
||||
// Generate ephemeral key identifier using timestamp for uniqueness
|
||||
var now = _timeProvider.GetUtcNow();
|
||||
var keyId = $"{KeylessKeyIdPrefix}{tenant}:{now:yyyyMMddHHmmss}:{Guid.NewGuid():N}";
|
||||
var keyId = $"{KeylessKeyIdPrefix}{tenant}:{now:yyyyMMddHHmmss}:{_guidProvider.NewGuid():N}";
|
||||
var expiresAt = now.AddMinutes(KeylessExpiryMinutes);
|
||||
|
||||
return new SigningKeyResolution(
|
||||
|
||||
@@ -18,17 +18,20 @@ public sealed class SigstoreSigningService : ISigstoreSigningService
|
||||
private readonly IFulcioClient _fulcioClient;
|
||||
private readonly IRekorClient _rekorClient;
|
||||
private readonly SigstoreOptions _options;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
private readonly ILogger<SigstoreSigningService> _logger;
|
||||
|
||||
public SigstoreSigningService(
|
||||
IFulcioClient fulcioClient,
|
||||
IRekorClient rekorClient,
|
||||
IOptions<SigstoreOptions> options,
|
||||
ILogger<SigstoreSigningService> logger)
|
||||
ILogger<SigstoreSigningService> logger,
|
||||
TimeProvider? timeProvider = null)
|
||||
{
|
||||
_fulcioClient = fulcioClient ?? throw new ArgumentNullException(nameof(fulcioClient));
|
||||
_rekorClient = rekorClient ?? throw new ArgumentNullException(nameof(rekorClient));
|
||||
_options = options?.Value ?? throw new ArgumentNullException(nameof(options));
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
@@ -133,7 +136,7 @@ public sealed class SigstoreSigningService : ISigstoreSigningService
|
||||
}
|
||||
|
||||
// 3. Check certificate validity
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var now = _timeProvider.GetUtcNow();
|
||||
if (now < cert.NotBefore || now > cert.NotAfter)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\StellaOps.Signer.Core\StellaOps.Signer.Core.csproj" />
|
||||
<ProjectReference Include="..\..\..\__Libraries\StellaOps.Cryptography\StellaOps.Cryptography.csproj" />
|
||||
<ProjectReference Include="..\..\..\__Libraries\StellaOps.Determinism.Abstractions\StellaOps.Determinism.Abstractions.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
|
||||
|
||||
@@ -145,6 +145,7 @@ public static class KeyRotationEndpoints
|
||||
[FromBody] RevokeKeyRequestDto request,
|
||||
IKeyRotationService rotationService,
|
||||
ILoggerFactory loggerFactory,
|
||||
TimeProvider timeProvider,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var logger = loggerFactory.CreateLogger("KeyRotationEndpoints.RevokeKey");
|
||||
@@ -183,7 +184,7 @@ public static class KeyRotationEndpoints
|
||||
{
|
||||
KeyId = keyId,
|
||||
AnchorId = anchorId,
|
||||
RevokedAt = request.EffectiveAt ?? DateTimeOffset.UtcNow,
|
||||
RevokedAt = request.EffectiveAt ?? timeProvider.GetUtcNow(),
|
||||
Reason = request.Reason,
|
||||
AllowedKeyIds = result.AllowedKeyIds.ToList(),
|
||||
RevokedKeyIds = result.RevokedKeyIds.ToList(),
|
||||
@@ -217,9 +218,10 @@ public static class KeyRotationEndpoints
|
||||
[FromRoute] string keyId,
|
||||
[FromQuery] DateTimeOffset? signedAt,
|
||||
IKeyRotationService rotationService,
|
||||
TimeProvider timeProvider,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var checkTime = signedAt ?? DateTimeOffset.UtcNow;
|
||||
var checkTime = signedAt ?? timeProvider.GetUtcNow();
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
@@ -17,8 +17,14 @@ builder.Services.AddAuthentication(StubBearerAuthenticationDefaults.Authenticati
|
||||
builder.Services.AddAuthorization();
|
||||
|
||||
builder.Services.AddSignerPipeline();
|
||||
|
||||
// Configure TimeProvider for deterministic testing support
|
||||
builder.Services.AddSingleton(TimeProvider.System);
|
||||
|
||||
builder.Services.Configure<SignerEntitlementOptions>(options =>
|
||||
{
|
||||
// Note: Using 1-hour expiry for demo/test tokens.
|
||||
// Actual expiry is calculated at runtime relative to TimeProvider.
|
||||
options.Tokens["valid-poe"] = new SignerEntitlementDefinition(
|
||||
LicenseId: "LIC-TEST",
|
||||
CustomerId: "CUST-TEST",
|
||||
|
||||
@@ -8,6 +8,7 @@ using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
using StellaOps.Determinism;
|
||||
using StellaOps.Signer.KeyManagement.Entities;
|
||||
|
||||
namespace StellaOps.Signer.KeyManagement;
|
||||
@@ -22,17 +23,20 @@ public sealed class KeyRotationService : IKeyRotationService
|
||||
private readonly ILogger<KeyRotationService> _logger;
|
||||
private readonly KeyRotationOptions _options;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
private readonly IGuidProvider _guidProvider;
|
||||
|
||||
public KeyRotationService(
|
||||
KeyManagementDbContext dbContext,
|
||||
ILogger<KeyRotationService> logger,
|
||||
IOptions<KeyRotationOptions> options,
|
||||
TimeProvider? timeProvider = null)
|
||||
TimeProvider? timeProvider = null,
|
||||
IGuidProvider? guidProvider = null)
|
||||
{
|
||||
_dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_options = options?.Value ?? new KeyRotationOptions();
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
_guidProvider = guidProvider ?? SystemGuidProvider.Instance;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -85,7 +89,7 @@ public sealed class KeyRotationService : IKeyRotationService
|
||||
// Create key history entry
|
||||
var keyEntry = new KeyHistoryEntity
|
||||
{
|
||||
HistoryId = Guid.NewGuid(),
|
||||
HistoryId = _guidProvider.NewGuid(),
|
||||
AnchorId = anchorId,
|
||||
KeyId = request.KeyId,
|
||||
PublicKey = request.PublicKey,
|
||||
@@ -106,7 +110,7 @@ public sealed class KeyRotationService : IKeyRotationService
|
||||
// Create audit log entry
|
||||
var auditEntry = new KeyAuditLogEntity
|
||||
{
|
||||
LogId = Guid.NewGuid(),
|
||||
LogId = _guidProvider.NewGuid(),
|
||||
AnchorId = anchorId,
|
||||
KeyId = request.KeyId,
|
||||
Operation = KeyOperation.Add,
|
||||
@@ -209,7 +213,7 @@ public sealed class KeyRotationService : IKeyRotationService
|
||||
// Create audit log entry
|
||||
var auditEntry = new KeyAuditLogEntity
|
||||
{
|
||||
LogId = Guid.NewGuid(),
|
||||
LogId = _guidProvider.NewGuid(),
|
||||
AnchorId = anchorId,
|
||||
KeyId = keyId,
|
||||
Operation = KeyOperation.Revoke,
|
||||
|
||||
@@ -15,6 +15,10 @@
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\__Libraries\StellaOps.Determinism.Abstractions\StellaOps.Determinism.Abstractions.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="Migrations\*.sql">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
|
||||
@@ -8,6 +8,7 @@ using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
using StellaOps.Determinism;
|
||||
using StellaOps.Signer.KeyManagement.Entities;
|
||||
|
||||
namespace StellaOps.Signer.KeyManagement;
|
||||
@@ -22,17 +23,20 @@ public sealed class TrustAnchorManager : ITrustAnchorManager
|
||||
private readonly IKeyRotationService _keyRotationService;
|
||||
private readonly ILogger<TrustAnchorManager> _logger;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
private readonly IGuidProvider _guidProvider;
|
||||
|
||||
public TrustAnchorManager(
|
||||
KeyManagementDbContext dbContext,
|
||||
IKeyRotationService keyRotationService,
|
||||
ILogger<TrustAnchorManager> logger,
|
||||
TimeProvider? timeProvider = null)
|
||||
TimeProvider? timeProvider = null,
|
||||
IGuidProvider? guidProvider = null)
|
||||
{
|
||||
_dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
|
||||
_keyRotationService = keyRotationService ?? throw new ArgumentNullException(nameof(keyRotationService));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
_guidProvider = guidProvider ?? SystemGuidProvider.Instance;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -115,7 +119,7 @@ public sealed class TrustAnchorManager : ITrustAnchorManager
|
||||
|
||||
var entity = new TrustAnchorEntity
|
||||
{
|
||||
AnchorId = Guid.NewGuid(),
|
||||
AnchorId = _guidProvider.NewGuid(),
|
||||
PurlPattern = request.PurlPattern,
|
||||
AllowedKeyIds = request.AllowedKeyIds?.ToList() ?? [],
|
||||
AllowedPredicateTypes = request.AllowedPredicateTypes?.ToList(),
|
||||
|
||||
@@ -21,13 +21,15 @@ public sealed class AmbientOidcTokenProvider : IOidcTokenProvider, IDisposable
|
||||
private readonly JwtSecurityTokenHandler _tokenHandler;
|
||||
private readonly SemaphoreSlim _lock = new(1, 1);
|
||||
private readonly FileSystemWatcher? _watcher;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
private OidcTokenResult? _cachedToken;
|
||||
private bool _disposed;
|
||||
|
||||
public AmbientOidcTokenProvider(
|
||||
OidcAmbientConfig config,
|
||||
ILogger<AmbientOidcTokenProvider> logger)
|
||||
ILogger<AmbientOidcTokenProvider> logger,
|
||||
TimeProvider? timeProvider = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(config);
|
||||
ArgumentNullException.ThrowIfNull(logger);
|
||||
@@ -35,6 +37,7 @@ public sealed class AmbientOidcTokenProvider : IOidcTokenProvider, IDisposable
|
||||
_config = config;
|
||||
_logger = logger;
|
||||
_tokenHandler = new JwtSecurityTokenHandler();
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
|
||||
if (_config.WatchForChanges && File.Exists(_config.TokenPath))
|
||||
{
|
||||
@@ -65,7 +68,8 @@ public sealed class AmbientOidcTokenProvider : IOidcTokenProvider, IDisposable
|
||||
try
|
||||
{
|
||||
// Check cache first
|
||||
if (_cachedToken is not null && !_cachedToken.WillExpireSoon(TimeSpan.FromSeconds(30)))
|
||||
var now = _timeProvider.GetUtcNow();
|
||||
if (_cachedToken is not null && !_cachedToken.WillExpireSoon(now, TimeSpan.FromSeconds(30)))
|
||||
{
|
||||
return _cachedToken;
|
||||
}
|
||||
@@ -111,7 +115,7 @@ public sealed class AmbientOidcTokenProvider : IOidcTokenProvider, IDisposable
|
||||
public OidcTokenResult? GetCachedToken()
|
||||
{
|
||||
var cached = _cachedToken;
|
||||
if (cached is null || cached.IsExpired)
|
||||
if (cached is null || cached.IsExpiredAt(_timeProvider.GetUtcNow()))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
@@ -132,7 +136,7 @@ public sealed class AmbientOidcTokenProvider : IOidcTokenProvider, IDisposable
|
||||
|
||||
var expiresAt = jwt.ValidTo != DateTime.MinValue
|
||||
? new DateTimeOffset(jwt.ValidTo, TimeSpan.Zero)
|
||||
: DateTimeOffset.UtcNow.AddHours(1); // Default if no exp claim
|
||||
: _timeProvider.GetUtcNow().AddHours(1); // Default if no exp claim
|
||||
|
||||
var subject = jwt.Subject;
|
||||
var email = jwt.Claims.FirstOrDefault(c => c.Type == "email")?.Value;
|
||||
|
||||
@@ -47,7 +47,8 @@ public sealed class EphemeralKeyPair : IDisposable
|
||||
/// <param name="publicKey">The public key bytes.</param>
|
||||
/// <param name="privateKey">The private key bytes (will be copied).</param>
|
||||
/// <param name="algorithm">The algorithm identifier.</param>
|
||||
public EphemeralKeyPair(byte[] publicKey, byte[] privateKey, string algorithm)
|
||||
/// <param name="timeProvider">Optional time provider for deterministic timestamp.</param>
|
||||
public EphemeralKeyPair(byte[] publicKey, byte[] privateKey, string algorithm, TimeProvider? timeProvider = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(publicKey);
|
||||
ArgumentNullException.ThrowIfNull(privateKey);
|
||||
@@ -56,7 +57,7 @@ public sealed class EphemeralKeyPair : IDisposable
|
||||
_publicKey = (byte[])publicKey.Clone();
|
||||
_privateKey = (byte[])privateKey.Clone();
|
||||
Algorithm = algorithm;
|
||||
CreatedAt = DateTimeOffset.UtcNow;
|
||||
CreatedAt = (timeProvider ?? TimeProvider.System).GetUtcNow();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -75,9 +75,11 @@ public sealed record FulcioCertificateResult(
|
||||
public TimeSpan Validity => NotAfter - NotBefore;
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the certificate is currently valid.
|
||||
/// Checks if the certificate is valid at the specified time.
|
||||
/// </summary>
|
||||
public bool IsValid => DateTimeOffset.UtcNow >= NotBefore && DateTimeOffset.UtcNow <= NotAfter;
|
||||
/// <param name="at">The time to check validity against.</param>
|
||||
/// <returns>True if the certificate is valid at the specified time.</returns>
|
||||
public bool IsValidAt(DateTimeOffset at) => at >= NotBefore && at <= NotAfter;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the full certificate chain including the leaf certificate.
|
||||
|
||||
@@ -62,15 +62,20 @@ public sealed record OidcTokenResult
|
||||
public string? Email { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the token is expired.
|
||||
/// Checks whether the token is expired at the specified time.
|
||||
/// </summary>
|
||||
public bool IsExpired => DateTimeOffset.UtcNow >= ExpiresAt;
|
||||
/// <param name="now">The time to check against.</param>
|
||||
/// <returns>True if the token is expired.</returns>
|
||||
public bool IsExpiredAt(DateTimeOffset now) => now >= ExpiresAt;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the token will expire within the specified buffer time.
|
||||
/// Checks whether the token will expire within the specified buffer time.
|
||||
/// </summary>
|
||||
public bool WillExpireSoon(TimeSpan buffer) =>
|
||||
DateTimeOffset.UtcNow.Add(buffer) >= ExpiresAt;
|
||||
/// <param name="now">The current time.</param>
|
||||
/// <param name="buffer">The time buffer before expiration.</param>
|
||||
/// <returns>True if the token will expire soon.</returns>
|
||||
public bool WillExpireSoon(DateTimeOffset now, TimeSpan buffer) =>
|
||||
now.Add(buffer) >= ExpiresAt;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user