save audit remarks applications progress
This commit is contained in:
@@ -4,6 +4,7 @@ using System.Text.RegularExpressions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.AirGap.Importer.Telemetry;
|
||||
using StellaOps.Determinism;
|
||||
|
||||
namespace StellaOps.AirGap.Importer.Quarantine;
|
||||
|
||||
@@ -17,15 +18,18 @@ public sealed class FileSystemQuarantineService : IQuarantineService
|
||||
private readonly QuarantineOptions _options;
|
||||
private readonly ILogger<FileSystemQuarantineService> _logger;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
private readonly IGuidProvider _guidProvider;
|
||||
|
||||
public FileSystemQuarantineService(
|
||||
IOptions<QuarantineOptions> options,
|
||||
ILogger<FileSystemQuarantineService> logger,
|
||||
TimeProvider timeProvider)
|
||||
TimeProvider timeProvider,
|
||||
IGuidProvider? guidProvider = null)
|
||||
{
|
||||
_options = options?.Value ?? throw new ArgumentNullException(nameof(options));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider));
|
||||
_guidProvider = guidProvider ?? SystemGuidProvider.Instance;
|
||||
}
|
||||
|
||||
public async Task<QuarantineResult> QuarantineAsync(
|
||||
@@ -74,7 +78,7 @@ public sealed class FileSystemQuarantineService : IQuarantineService
|
||||
var now = _timeProvider.GetUtcNow();
|
||||
var timestamp = now.ToString("yyyyMMdd-HHmmss", CultureInfo.InvariantCulture);
|
||||
var sanitizedReason = SanitizeForPathSegment(request.ReasonCode);
|
||||
var quarantineId = $"{timestamp}-{sanitizedReason}-{Guid.NewGuid():N}";
|
||||
var quarantineId = $"{timestamp}-{sanitizedReason}-{_guidProvider.NewGuid():N}";
|
||||
|
||||
var quarantinePath = Path.Combine(tenantRoot, quarantineId);
|
||||
|
||||
@@ -250,7 +254,7 @@ public sealed class FileSystemQuarantineService : IQuarantineService
|
||||
var removedPath = Path.Combine(removedRoot, quarantineId);
|
||||
if (Directory.Exists(removedPath))
|
||||
{
|
||||
removedPath = Path.Combine(removedRoot, $"{quarantineId}-{Guid.NewGuid():N}");
|
||||
removedPath = Path.Combine(removedRoot, $"{quarantineId}-{_guidProvider.NewGuid():N}");
|
||||
}
|
||||
|
||||
Directory.Move(entryPath, removedPath);
|
||||
|
||||
@@ -18,5 +18,6 @@
|
||||
<ProjectReference Include="..\\..\\Attestor\\StellaOps.Attestor.Envelope\\StellaOps.Attestor.Envelope.csproj" />
|
||||
<ProjectReference Include="..\\..\\__Libraries\\StellaOps.Cryptography\\StellaOps.Cryptography.csproj" />
|
||||
<ProjectReference Include="..\\..\\__Libraries\\StellaOps.Cryptography.Plugin.OfflineVerification\\StellaOps.Cryptography.Plugin.OfflineVerification.csproj" />
|
||||
<ProjectReference Include="..\\..\\__Libraries\\StellaOps.Determinism.Abstractions\\StellaOps.Determinism.Abstractions.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -23,15 +23,18 @@ public sealed class RuleBundleValidator
|
||||
private readonly DsseVerifier _dsseVerifier;
|
||||
private readonly IVersionMonotonicityChecker _monotonicityChecker;
|
||||
private readonly ILogger<RuleBundleValidator> _logger;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
public RuleBundleValidator(
|
||||
DsseVerifier dsseVerifier,
|
||||
IVersionMonotonicityChecker monotonicityChecker,
|
||||
ILogger<RuleBundleValidator> logger)
|
||||
ILogger<RuleBundleValidator> logger,
|
||||
TimeProvider? timeProvider = null)
|
||||
{
|
||||
_dsseVerifier = dsseVerifier ?? throw new ArgumentNullException(nameof(dsseVerifier));
|
||||
_monotonicityChecker = monotonicityChecker ?? throw new ArgumentNullException(nameof(monotonicityChecker));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -157,7 +160,7 @@ public sealed class RuleBundleValidator
|
||||
BundleVersion incomingVersion;
|
||||
try
|
||||
{
|
||||
incomingVersion = BundleVersion.Parse(request.Version, request.CreatedAt ?? DateTimeOffset.UtcNow);
|
||||
incomingVersion = BundleVersion.Parse(request.Version, request.CreatedAt ?? _timeProvider.GetUtcNow());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -12,6 +12,7 @@ using System.Text.Json;
|
||||
using StellaOps.AirGap.Bundle.Models;
|
||||
using StellaOps.Concelier.Core.Raw;
|
||||
using StellaOps.Concelier.RawModels;
|
||||
using StellaOps.Determinism;
|
||||
|
||||
namespace StellaOps.AirGap.Bundle.Services;
|
||||
|
||||
@@ -134,12 +135,22 @@ public sealed class InMemoryAdvisoryRawRepository : IAdvisoryRawRepository
|
||||
{
|
||||
private readonly Dictionary<string, AdvisoryRawRecord> _records = new();
|
||||
private readonly object _lock = new();
|
||||
private readonly TimeProvider _timeProvider;
|
||||
private readonly IGuidProvider _guidProvider;
|
||||
|
||||
public InMemoryAdvisoryRawRepository(
|
||||
TimeProvider? timeProvider = null,
|
||||
IGuidProvider? guidProvider = null)
|
||||
{
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
_guidProvider = guidProvider ?? SystemGuidProvider.Instance;
|
||||
}
|
||||
|
||||
public Task<AdvisoryRawUpsertResult> UpsertAsync(AdvisoryRawDocument document, CancellationToken cancellationToken)
|
||||
{
|
||||
var contentHash = ComputeHash(document);
|
||||
var key = $"{document.Tenant}:{contentHash}";
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var now = _timeProvider.GetUtcNow();
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
@@ -149,7 +160,7 @@ public sealed class InMemoryAdvisoryRawRepository : IAdvisoryRawRepository
|
||||
}
|
||||
|
||||
var record = new AdvisoryRawRecord(
|
||||
Id: Guid.NewGuid().ToString(),
|
||||
Id: _guidProvider.NewGuid().ToString(),
|
||||
Document: document,
|
||||
IngestedAt: now,
|
||||
CreatedAt: now);
|
||||
|
||||
@@ -10,6 +10,7 @@ using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using StellaOps.AirGap.Bundle.Models;
|
||||
using StellaOps.Determinism;
|
||||
using StellaOps.Excititor.Core;
|
||||
using StellaOps.Excititor.Core.Storage;
|
||||
|
||||
@@ -161,10 +162,14 @@ public sealed class InMemoryVexRawDocumentSink : IVexRawDocumentSink, IVexRawSto
|
||||
private readonly Dictionary<string, VexRawRecord> _records = new();
|
||||
private readonly string _tenant;
|
||||
private readonly object _lock = new();
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
public InMemoryVexRawDocumentSink(string tenant = "default")
|
||||
public InMemoryVexRawDocumentSink(
|
||||
string tenant = "default",
|
||||
TimeProvider? timeProvider = null)
|
||||
{
|
||||
_tenant = tenant;
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
public ValueTask StoreAsync(VexRawDocument document, CancellationToken cancellationToken)
|
||||
@@ -183,7 +188,7 @@ public sealed class InMemoryVexRawDocumentSink : IVexRawDocumentSink, IVexRawSto
|
||||
Metadata: document.Metadata,
|
||||
Content: document.Content,
|
||||
InlineContent: true,
|
||||
RecordedAt: DateTimeOffset.UtcNow);
|
||||
RecordedAt: _timeProvider.GetUtcNow());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ using System.IO.Compression;
|
||||
using System.Formats.Tar;
|
||||
using System.Text.Json;
|
||||
using StellaOps.AirGap.Bundle.Models;
|
||||
using StellaOps.Determinism;
|
||||
|
||||
namespace StellaOps.AirGap.Bundle.Services;
|
||||
|
||||
@@ -25,15 +26,21 @@ public sealed class KnowledgeSnapshotImporter : IKnowledgeSnapshotImporter
|
||||
private readonly IAdvisoryImportTarget? _advisoryTarget;
|
||||
private readonly IVexImportTarget? _vexTarget;
|
||||
private readonly IPolicyImportTarget? _policyTarget;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
private readonly IGuidProvider _guidProvider;
|
||||
|
||||
public KnowledgeSnapshotImporter(
|
||||
IAdvisoryImportTarget? advisoryTarget = null,
|
||||
IVexImportTarget? vexTarget = null,
|
||||
IPolicyImportTarget? policyTarget = null)
|
||||
IPolicyImportTarget? policyTarget = null,
|
||||
TimeProvider? timeProvider = null,
|
||||
IGuidProvider? guidProvider = null)
|
||||
{
|
||||
_advisoryTarget = advisoryTarget;
|
||||
_vexTarget = vexTarget;
|
||||
_policyTarget = policyTarget;
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
_guidProvider = guidProvider ?? SystemGuidProvider.Instance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -48,10 +55,10 @@ public sealed class KnowledgeSnapshotImporter : IKnowledgeSnapshotImporter
|
||||
|
||||
if (!File.Exists(request.BundlePath))
|
||||
{
|
||||
return SnapshotImportResult.Failed("Bundle file not found");
|
||||
return SnapshotImportResult.Failed("Bundle file not found", _timeProvider);
|
||||
}
|
||||
|
||||
var tempDir = Path.Combine(Path.GetTempPath(), $"import-{Guid.NewGuid():N}");
|
||||
var tempDir = Path.Combine(Path.GetTempPath(), $"import-{_guidProvider.NewGuid():N}");
|
||||
Directory.CreateDirectory(tempDir);
|
||||
|
||||
try
|
||||
@@ -63,21 +70,21 @@ public sealed class KnowledgeSnapshotImporter : IKnowledgeSnapshotImporter
|
||||
var manifestPath = Path.Combine(tempDir, "manifest.json");
|
||||
if (!File.Exists(manifestPath))
|
||||
{
|
||||
return SnapshotImportResult.Failed("Manifest not found in bundle");
|
||||
return SnapshotImportResult.Failed("Manifest not found in bundle", _timeProvider);
|
||||
}
|
||||
|
||||
var manifestBytes = await File.ReadAllBytesAsync(manifestPath, cancellationToken);
|
||||
var manifest = JsonSerializer.Deserialize<KnowledgeSnapshotManifest>(manifestBytes, JsonOptions);
|
||||
if (manifest is null)
|
||||
{
|
||||
return SnapshotImportResult.Failed("Failed to parse manifest");
|
||||
return SnapshotImportResult.Failed("Failed to parse manifest", _timeProvider);
|
||||
}
|
||||
|
||||
var result = new SnapshotImportResult
|
||||
{
|
||||
Success = true,
|
||||
BundleId = manifest.BundleId,
|
||||
StartedAt = DateTimeOffset.UtcNow
|
||||
StartedAt = _timeProvider.GetUtcNow()
|
||||
};
|
||||
|
||||
var errors = new List<string>();
|
||||
@@ -148,7 +155,7 @@ public sealed class KnowledgeSnapshotImporter : IKnowledgeSnapshotImporter
|
||||
|
||||
result = result with
|
||||
{
|
||||
CompletedAt = DateTimeOffset.UtcNow,
|
||||
CompletedAt = _timeProvider.GetUtcNow(),
|
||||
Statistics = stats,
|
||||
Errors = errors.Count > 0 ? [.. errors] : null,
|
||||
Success = errors.Count == 0 || !request.FailOnAnyError
|
||||
@@ -158,7 +165,7 @@ public sealed class KnowledgeSnapshotImporter : IKnowledgeSnapshotImporter
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return SnapshotImportResult.Failed($"Import failed: {ex.Message}");
|
||||
return SnapshotImportResult.Failed($"Import failed: {ex.Message}", _timeProvider);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -422,13 +429,17 @@ public sealed record SnapshotImportResult
|
||||
public IReadOnlyList<string>? Errors { get; init; }
|
||||
public string? Error { get; init; }
|
||||
|
||||
public static SnapshotImportResult Failed(string error) => new()
|
||||
public static SnapshotImportResult Failed(string error, TimeProvider? timeProvider = null)
|
||||
{
|
||||
Success = false,
|
||||
Error = error,
|
||||
StartedAt = DateTimeOffset.UtcNow,
|
||||
CompletedAt = DateTimeOffset.UtcNow
|
||||
};
|
||||
var now = (timeProvider ?? TimeProvider.System).GetUtcNow();
|
||||
return new()
|
||||
{
|
||||
Success = false,
|
||||
Error = error,
|
||||
StartedAt = now,
|
||||
CompletedAt = now
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public sealed record ImportStatistics
|
||||
|
||||
@@ -9,6 +9,7 @@ using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using StellaOps.AirGap.Bundle.Models;
|
||||
using StellaOps.Determinism;
|
||||
|
||||
namespace StellaOps.AirGap.Bundle.Services;
|
||||
|
||||
@@ -26,13 +27,16 @@ public sealed class PolicyRegistryImportTarget : IPolicyImportTarget
|
||||
|
||||
private readonly IPolicyPackImportStore _store;
|
||||
private readonly string _tenantId;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
public PolicyRegistryImportTarget(
|
||||
IPolicyPackImportStore store,
|
||||
string tenantId = "default")
|
||||
string tenantId = "default",
|
||||
TimeProvider? timeProvider = null)
|
||||
{
|
||||
_store = store ?? throw new ArgumentNullException(nameof(store));
|
||||
_tenantId = tenantId;
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -83,7 +87,7 @@ public sealed class PolicyRegistryImportTarget : IPolicyImportTarget
|
||||
Version: data.Version ?? "1.0.0",
|
||||
Content: data.Content,
|
||||
Metadata: bundle.Metadata,
|
||||
ImportedAt: DateTimeOffset.UtcNow);
|
||||
ImportedAt: _timeProvider.GetUtcNow());
|
||||
|
||||
await _store.SaveAsync(pack, cancellationToken);
|
||||
created++;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\__Libraries\StellaOps.Canonical.Json\StellaOps.Canonical.Json.csproj" />
|
||||
<ProjectReference Include="..\..\..\__Libraries\StellaOps.Determinism.Abstractions\StellaOps.Determinism.Abstractions.csproj" />
|
||||
<ProjectReference Include="..\..\..\Concelier\__Libraries\StellaOps.Concelier.Core\StellaOps.Concelier.Core.csproj" />
|
||||
<ProjectReference Include="..\..\..\Concelier\__Libraries\StellaOps.Concelier.RawModels\StellaOps.Concelier.RawModels.csproj" />
|
||||
<ProjectReference Include="..\..\..\Excititor\__Libraries\StellaOps.Excititor.Core\StellaOps.Excititor.Core.csproj" />
|
||||
|
||||
@@ -67,6 +67,7 @@ public static class VerdictEndpoints
|
||||
private static async Task<IResult> StoreVerdictAsync(
|
||||
[FromBody] StoreVerdictRequest request,
|
||||
[FromServices] IVerdictRepository repository,
|
||||
[FromServices] TimeProvider timeProvider,
|
||||
[FromServices] ILogger<VerdictEndpointsLogger> logger,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -105,7 +106,7 @@ public static class VerdictEndpoints
|
||||
PredicateDigest = request.PredicateDigest,
|
||||
DeterminismHash = request.DeterminismHash,
|
||||
RekorLogIndex = request.RekorLogIndex,
|
||||
CreatedAt = DateTimeOffset.UtcNow
|
||||
CreatedAt = timeProvider.GetUtcNow()
|
||||
};
|
||||
|
||||
// Store in repository
|
||||
@@ -253,6 +254,7 @@ public static class VerdictEndpoints
|
||||
private static async Task<IResult> VerifyVerdictAsync(
|
||||
string verdictId,
|
||||
[FromServices] IVerdictRepository repository,
|
||||
[FromServices] TimeProvider timeProvider,
|
||||
[FromServices] ILogger<VerdictEndpointsLogger> logger,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -270,11 +272,12 @@ public static class VerdictEndpoints
|
||||
|
||||
// TODO: Implement actual signature verification
|
||||
// For now, return a placeholder response
|
||||
var now = timeProvider.GetUtcNow();
|
||||
var response = new VerifyVerdictResponse
|
||||
{
|
||||
VerdictId = verdictId,
|
||||
SignatureValid = true, // TODO: Implement verification
|
||||
VerifiedAt = DateTimeOffset.UtcNow,
|
||||
VerifiedAt = now,
|
||||
Verifications = new[]
|
||||
{
|
||||
new SignatureVerification
|
||||
@@ -289,7 +292,7 @@ public static class VerdictEndpoints
|
||||
{
|
||||
LogIndex = record.RekorLogIndex.Value,
|
||||
InclusionProofValid = true, // TODO: Implement verification
|
||||
VerifiedAt = DateTimeOffset.UtcNow
|
||||
VerifiedAt = now
|
||||
}
|
||||
: null
|
||||
};
|
||||
|
||||
@@ -19,6 +19,7 @@ using StellaOps.EvidenceLocker.Core.Signing;
|
||||
using StellaOps.EvidenceLocker.Core.Incident;
|
||||
using StellaOps.EvidenceLocker.Core.Timeline;
|
||||
using StellaOps.EvidenceLocker.Core.Storage;
|
||||
using StellaOps.Determinism;
|
||||
|
||||
namespace StellaOps.EvidenceLocker.Infrastructure.Services;
|
||||
|
||||
@@ -37,6 +38,7 @@ public sealed class EvidenceSnapshotService
|
||||
private readonly IIncidentModeState _incidentMode;
|
||||
private readonly IEvidenceObjectStore _objectStore;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
private readonly IGuidProvider _guidProvider;
|
||||
private readonly ILogger<EvidenceSnapshotService> _logger;
|
||||
private readonly QuotaOptions _quotas;
|
||||
|
||||
@@ -49,7 +51,8 @@ public sealed class EvidenceSnapshotService
|
||||
IEvidenceObjectStore objectStore,
|
||||
TimeProvider timeProvider,
|
||||
IOptions<EvidenceLockerOptions> options,
|
||||
ILogger<EvidenceSnapshotService> logger)
|
||||
ILogger<EvidenceSnapshotService> logger,
|
||||
IGuidProvider? guidProvider = null)
|
||||
{
|
||||
_repository = repository ?? throw new ArgumentNullException(nameof(repository));
|
||||
_bundleBuilder = bundleBuilder ?? throw new ArgumentNullException(nameof(bundleBuilder));
|
||||
@@ -61,6 +64,7 @@ public sealed class EvidenceSnapshotService
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
_quotas = options.Value.Quotas ?? throw new InvalidOperationException("Quota options are required.");
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_guidProvider = guidProvider ?? SystemGuidProvider.Instance;
|
||||
}
|
||||
|
||||
public async Task<EvidenceSnapshotResult> CreateSnapshotAsync(
|
||||
@@ -76,7 +80,7 @@ public sealed class EvidenceSnapshotService
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
ValidateRequest(request);
|
||||
|
||||
var bundleId = EvidenceBundleId.FromGuid(Guid.NewGuid());
|
||||
var bundleId = EvidenceBundleId.FromGuid(_guidProvider.NewGuid());
|
||||
var createdAt = _timeProvider.GetUtcNow();
|
||||
var storageKey = $"tenants/{tenantId.Value:N}/bundles/{bundleId.Value:N}/bundle.tgz";
|
||||
var incidentSnapshot = _incidentMode.Current;
|
||||
@@ -245,7 +249,7 @@ public sealed class EvidenceSnapshotService
|
||||
}
|
||||
}
|
||||
|
||||
var holdId = EvidenceHoldId.FromGuid(Guid.NewGuid());
|
||||
var holdId = EvidenceHoldId.FromGuid(_guidProvider.NewGuid());
|
||||
var createdAt = _timeProvider.GetUtcNow();
|
||||
var hold = new EvidenceHold(
|
||||
holdId,
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
<ProjectReference Include="..\..\..\__Libraries\StellaOps.Cryptography\StellaOps.Cryptography.csproj" />
|
||||
<ProjectReference Include="..\..\..\__Libraries\StellaOps.Cryptography.DependencyInjection\StellaOps.Cryptography.DependencyInjection.csproj" />
|
||||
<ProjectReference Include="..\..\..\__Libraries\StellaOps.Cryptography.Plugin.BouncyCastle\StellaOps.Cryptography.Plugin.BouncyCastle.csproj" />
|
||||
<ProjectReference Include="..\..\..\__Libraries\StellaOps.Determinism.Abstractions\StellaOps.Determinism.Abstractions.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -3,6 +3,7 @@ using Microsoft.Extensions.Logging;
|
||||
using StellaOps.EvidenceLocker.Core.Configuration;
|
||||
using StellaOps.EvidenceLocker.Core.Domain;
|
||||
using StellaOps.EvidenceLocker.Core.Storage;
|
||||
using StellaOps.Determinism;
|
||||
|
||||
namespace StellaOps.EvidenceLocker.Infrastructure.Storage;
|
||||
|
||||
@@ -11,11 +12,15 @@ internal sealed class FileSystemEvidenceObjectStore : IEvidenceObjectStore
|
||||
private readonly string _rootPath;
|
||||
private readonly bool _enforceWriteOnce;
|
||||
private readonly ILogger<FileSystemEvidenceObjectStore> _logger;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
private readonly IGuidProvider _guidProvider;
|
||||
|
||||
public FileSystemEvidenceObjectStore(
|
||||
FileSystemStoreOptions options,
|
||||
bool enforceWriteOnce,
|
||||
ILogger<FileSystemEvidenceObjectStore> logger)
|
||||
ILogger<FileSystemEvidenceObjectStore> logger,
|
||||
TimeProvider? timeProvider = null,
|
||||
IGuidProvider? guidProvider = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(options.RootPath);
|
||||
@@ -23,6 +28,8 @@ internal sealed class FileSystemEvidenceObjectStore : IEvidenceObjectStore
|
||||
_rootPath = Path.GetFullPath(options.RootPath);
|
||||
_enforceWriteOnce = enforceWriteOnce;
|
||||
_logger = logger;
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
_guidProvider = guidProvider ?? SystemGuidProvider.Instance;
|
||||
|
||||
Directory.CreateDirectory(_rootPath);
|
||||
}
|
||||
@@ -33,8 +40,8 @@ internal sealed class FileSystemEvidenceObjectStore : IEvidenceObjectStore
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
|
||||
var writeOnce = _enforceWriteOnce || options.EnforceWriteOnce;
|
||||
var utcNow = DateTimeOffset.UtcNow;
|
||||
var tempFilePath = Path.Combine(_rootPath, ".tmp", Guid.NewGuid().ToString("N"));
|
||||
var utcNow = _timeProvider.GetUtcNow();
|
||||
var tempFilePath = Path.Combine(_rootPath, ".tmp", _guidProvider.NewGuid().ToString("N"));
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(tempFilePath)!);
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ using Microsoft.Extensions.Logging;
|
||||
using StellaOps.EvidenceLocker.Core.Configuration;
|
||||
using StellaOps.EvidenceLocker.Core.Domain;
|
||||
using StellaOps.EvidenceLocker.Core.Storage;
|
||||
using StellaOps.Determinism;
|
||||
|
||||
namespace StellaOps.EvidenceLocker.Infrastructure.Storage;
|
||||
|
||||
@@ -15,17 +16,23 @@ internal sealed class S3EvidenceObjectStore : IEvidenceObjectStore, IDisposable
|
||||
private readonly AmazonS3StoreOptions _options;
|
||||
private readonly bool _enforceWriteOnce;
|
||||
private readonly ILogger<S3EvidenceObjectStore> _logger;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
private readonly IGuidProvider _guidProvider;
|
||||
|
||||
public S3EvidenceObjectStore(
|
||||
IAmazonS3 s3,
|
||||
AmazonS3StoreOptions options,
|
||||
bool enforceWriteOnce,
|
||||
ILogger<S3EvidenceObjectStore> logger)
|
||||
ILogger<S3EvidenceObjectStore> logger,
|
||||
TimeProvider? timeProvider = null,
|
||||
IGuidProvider? guidProvider = null)
|
||||
{
|
||||
_s3 = s3 ?? throw new ArgumentNullException(nameof(s3));
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
_enforceWriteOnce = enforceWriteOnce;
|
||||
_logger = logger;
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
_guidProvider = guidProvider ?? SystemGuidProvider.Instance;
|
||||
}
|
||||
|
||||
public async Task<EvidenceObjectMetadata> StoreAsync(
|
||||
@@ -37,7 +44,7 @@ internal sealed class S3EvidenceObjectStore : IEvidenceObjectStore, IDisposable
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
|
||||
var writeOnce = _enforceWriteOnce || options.EnforceWriteOnce;
|
||||
var tempFilePath = Path.Combine(Path.GetTempPath(), $"evidence-{Guid.NewGuid():N}.tmp");
|
||||
var tempFilePath = Path.Combine(Path.GetTempPath(), $"evidence-{_guidProvider.NewGuid():N}.tmp");
|
||||
|
||||
using var sha = SHA256.Create();
|
||||
long totalBytes = 0;
|
||||
@@ -83,7 +90,7 @@ internal sealed class S3EvidenceObjectStore : IEvidenceObjectStore, IDisposable
|
||||
SizeBytes: totalBytes,
|
||||
Sha256: sha256,
|
||||
ETag: eTag,
|
||||
CreatedAt: DateTimeOffset.UtcNow);
|
||||
CreatedAt: _timeProvider.GetUtcNow());
|
||||
}
|
||||
|
||||
public async Task<Stream> OpenReadAsync(string storageKey, CancellationToken cancellationToken)
|
||||
|
||||
@@ -15,6 +15,7 @@ using StellaOps.EvidenceLocker.Core.Configuration;
|
||||
using StellaOps.EvidenceLocker.Core.Domain;
|
||||
using StellaOps.EvidenceLocker.Core.Incident;
|
||||
using StellaOps.EvidenceLocker.Core.Timeline;
|
||||
using StellaOps.Determinism;
|
||||
|
||||
namespace StellaOps.EvidenceLocker.Infrastructure.Timeline;
|
||||
|
||||
@@ -29,12 +30,14 @@ internal sealed class TimelineIndexerEvidenceTimelinePublisher : IEvidenceTimeli
|
||||
private readonly TimelineOptions _options;
|
||||
private readonly ILogger<TimelineIndexerEvidenceTimelinePublisher> _logger;
|
||||
private readonly Uri _endpoint;
|
||||
private readonly IGuidProvider _guidProvider;
|
||||
|
||||
public TimelineIndexerEvidenceTimelinePublisher(
|
||||
HttpClient httpClient,
|
||||
IOptions<EvidenceLockerOptions> options,
|
||||
TimeProvider timeProvider,
|
||||
ILogger<TimelineIndexerEvidenceTimelinePublisher> logger)
|
||||
ILogger<TimelineIndexerEvidenceTimelinePublisher> logger,
|
||||
IGuidProvider? guidProvider = null)
|
||||
{
|
||||
_httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
@@ -53,6 +56,7 @@ internal sealed class TimelineIndexerEvidenceTimelinePublisher : IEvidenceTimeli
|
||||
_endpoint = new Uri(_options.Endpoint, UriKind.Absolute);
|
||||
ArgumentNullException.ThrowIfNull(timeProvider);
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_guidProvider = guidProvider ?? SystemGuidProvider.Instance;
|
||||
}
|
||||
|
||||
public async Task PublishBundleSealedAsync(
|
||||
@@ -85,7 +89,7 @@ internal sealed class TimelineIndexerEvidenceTimelinePublisher : IEvidenceTimeli
|
||||
|
||||
private TimelineEventEnvelope BuildBundleEvent(EvidenceBundleSignature signature, EvidenceBundleManifest manifest, string rootHash)
|
||||
{
|
||||
var eventId = Guid.NewGuid();
|
||||
var eventId = _guidProvider.NewGuid();
|
||||
var occurredAt = signature.TimestampedAt ?? signature.SignedAt;
|
||||
var attributes = new SortedDictionary<string, string>(StringComparer.Ordinal)
|
||||
{
|
||||
@@ -139,7 +143,7 @@ internal sealed class TimelineIndexerEvidenceTimelinePublisher : IEvidenceTimeli
|
||||
|
||||
private TimelineEventEnvelope BuildHoldEvent(EvidenceHold hold)
|
||||
{
|
||||
var eventId = Guid.NewGuid();
|
||||
var eventId = _guidProvider.NewGuid();
|
||||
var occurredAt = hold.CreatedAt;
|
||||
var attributes = new SortedDictionary<string, string>(StringComparer.Ordinal)
|
||||
{
|
||||
@@ -175,7 +179,7 @@ internal sealed class TimelineIndexerEvidenceTimelinePublisher : IEvidenceTimeli
|
||||
|
||||
private TimelineEventEnvelope BuildIncidentEvent(IncidentModeChange change)
|
||||
{
|
||||
var eventId = Guid.NewGuid();
|
||||
var eventId = _guidProvider.NewGuid();
|
||||
var attributes = new SortedDictionary<string, string>(StringComparer.Ordinal)
|
||||
{
|
||||
["state"] = change.IsActive ? "enabled" : "disabled",
|
||||
|
||||
@@ -63,7 +63,7 @@ public sealed record VerdictAttestationRecord
|
||||
public required string PredicateDigest { get; init; }
|
||||
public string? DeterminismHash { get; init; }
|
||||
public long? RekorLogIndex { get; init; }
|
||||
public DateTimeOffset CreatedAt { get; init; } = DateTimeOffset.UtcNow;
|
||||
public required DateTimeOffset CreatedAt { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Security.Cryptography;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StellaOps.Determinism;
|
||||
using StellaOps.IssuerDirectory.Core.Abstractions;
|
||||
using StellaOps.IssuerDirectory.Core.Domain;
|
||||
using StellaOps.IssuerDirectory.Core.Observability;
|
||||
@@ -16,6 +17,7 @@ public sealed class IssuerKeyService
|
||||
private readonly IIssuerKeyRepository _keyRepository;
|
||||
private readonly IIssuerAuditSink _auditSink;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
private readonly IGuidProvider _guidProvider;
|
||||
private readonly ILogger<IssuerKeyService> _logger;
|
||||
|
||||
public IssuerKeyService(
|
||||
@@ -23,13 +25,15 @@ public sealed class IssuerKeyService
|
||||
IIssuerKeyRepository keyRepository,
|
||||
IIssuerAuditSink auditSink,
|
||||
TimeProvider timeProvider,
|
||||
ILogger<IssuerKeyService> logger)
|
||||
ILogger<IssuerKeyService> logger,
|
||||
IGuidProvider? guidProvider = null)
|
||||
{
|
||||
_issuerRepository = issuerRepository ?? throw new ArgumentNullException(nameof(issuerRepository));
|
||||
_keyRepository = keyRepository ?? throw new ArgumentNullException(nameof(keyRepository));
|
||||
_auditSink = auditSink ?? throw new ArgumentNullException(nameof(auditSink));
|
||||
_timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_guidProvider = guidProvider ?? SystemGuidProvider.Instance;
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyCollection<IssuerKeyRecord>> ListAsync(
|
||||
@@ -101,7 +105,7 @@ public sealed class IssuerKeyService
|
||||
|
||||
var now = _timeProvider.GetUtcNow();
|
||||
var record = IssuerKeyRecord.Create(
|
||||
Guid.NewGuid().ToString("n"),
|
||||
_guidProvider.NewGuid().ToString("n"),
|
||||
issuerId,
|
||||
tenantId,
|
||||
type,
|
||||
@@ -205,7 +209,7 @@ public sealed class IssuerKeyService
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var replacement = IssuerKeyRecord.Create(
|
||||
Guid.NewGuid().ToString("n"),
|
||||
_guidProvider.NewGuid().ToString("n"),
|
||||
issuerId,
|
||||
tenantId,
|
||||
newType,
|
||||
|
||||
@@ -9,4 +9,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\__Libraries\StellaOps.Determinism.Abstractions\StellaOps.Determinism.Abstractions.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -11,6 +11,13 @@ namespace StellaOps.Scanner.Analyzers.Native.Hardening;
|
||||
/// </summary>
|
||||
public sealed class ElfHardeningExtractor : IHardeningExtractor
|
||||
{
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
public ElfHardeningExtractor(TimeProvider? timeProvider = null)
|
||||
{
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
// ELF magic bytes
|
||||
private static readonly byte[] ElfMagic = [0x7F, 0x45, 0x4C, 0x46]; // \x7FELF
|
||||
|
||||
@@ -596,7 +603,7 @@ public sealed class ElfHardeningExtractor : IHardeningExtractor
|
||||
|
||||
#endregion
|
||||
|
||||
private static BinaryHardeningFlags CreateResult(
|
||||
private BinaryHardeningFlags CreateResult(
|
||||
string path,
|
||||
string digest,
|
||||
List<HardeningFlag> flags,
|
||||
@@ -623,7 +630,7 @@ public sealed class ElfHardeningExtractor : IHardeningExtractor
|
||||
Flags: [.. flags],
|
||||
HardeningScore: Math.Round(score, 2),
|
||||
MissingFlags: [.. missing],
|
||||
ExtractedAt: DateTimeOffset.UtcNow);
|
||||
ExtractedAt: _timeProvider.GetUtcNow());
|
||||
}
|
||||
|
||||
private static ushort ReadUInt16(ReadOnlySpan<byte> span, bool littleEndian)
|
||||
|
||||
@@ -17,6 +17,13 @@ namespace StellaOps.Scanner.Analyzers.Native.Hardening;
|
||||
/// </summary>
|
||||
public sealed class MachoHardeningExtractor : IHardeningExtractor
|
||||
{
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
public MachoHardeningExtractor(TimeProvider? timeProvider = null)
|
||||
{
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
// Mach-O magic numbers
|
||||
private const uint MH_MAGIC = 0xFEEDFACE; // 32-bit
|
||||
private const uint MH_CIGAM = 0xCEFAEDFE; // 32-bit (reversed)
|
||||
@@ -257,7 +264,7 @@ public sealed class MachoHardeningExtractor : IHardeningExtractor
|
||||
: BinaryPrimitives.ReadUInt32BigEndian(data.AsSpan(offset, 4));
|
||||
}
|
||||
|
||||
private static BinaryHardeningFlags CreateResult(
|
||||
private BinaryHardeningFlags CreateResult(
|
||||
string path,
|
||||
string digest,
|
||||
List<HardeningFlag> flags,
|
||||
@@ -283,6 +290,6 @@ public sealed class MachoHardeningExtractor : IHardeningExtractor
|
||||
Flags: [.. flags],
|
||||
HardeningScore: Math.Round(score, 2),
|
||||
MissingFlags: [.. missing],
|
||||
ExtractedAt: DateTimeOffset.UtcNow);
|
||||
ExtractedAt: _timeProvider.GetUtcNow());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,13 @@ namespace StellaOps.Scanner.Analyzers.Native.Hardening;
|
||||
/// </summary>
|
||||
public sealed class PeHardeningExtractor : IHardeningExtractor
|
||||
{
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
public PeHardeningExtractor(TimeProvider? timeProvider = null)
|
||||
{
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
// PE magic bytes: MZ (DOS header)
|
||||
private const ushort DOS_MAGIC = 0x5A4D; // "MZ"
|
||||
private const uint PE_SIGNATURE = 0x00004550; // "PE\0\0"
|
||||
@@ -233,7 +240,7 @@ public sealed class PeHardeningExtractor : IHardeningExtractor
|
||||
}
|
||||
}
|
||||
|
||||
private static BinaryHardeningFlags CreateResult(
|
||||
private BinaryHardeningFlags CreateResult(
|
||||
string path,
|
||||
string digest,
|
||||
List<HardeningFlag> flags,
|
||||
@@ -259,6 +266,6 @@ public sealed class PeHardeningExtractor : IHardeningExtractor
|
||||
Flags: [.. flags],
|
||||
HardeningScore: Math.Round(score, 2),
|
||||
MissingFlags: [.. missing],
|
||||
ExtractedAt: DateTimeOffset.UtcNow);
|
||||
ExtractedAt: _timeProvider.GetUtcNow());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ public sealed class OfflineBuildIdIndex : IBuildIdIndex
|
||||
private readonly BuildIdIndexOptions _options;
|
||||
private readonly ILogger<OfflineBuildIdIndex> _logger;
|
||||
private readonly IDsseSigningService? _dsseSigningService;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
private FrozenDictionary<string, BuildIdLookupResult> _index = FrozenDictionary<string, BuildIdLookupResult>.Empty;
|
||||
private bool _isLoaded;
|
||||
|
||||
@@ -31,7 +32,8 @@ public sealed class OfflineBuildIdIndex : IBuildIdIndex
|
||||
public OfflineBuildIdIndex(
|
||||
IOptions<BuildIdIndexOptions> options,
|
||||
ILogger<OfflineBuildIdIndex> logger,
|
||||
IDsseSigningService? dsseSigningService = null)
|
||||
IDsseSigningService? dsseSigningService = null,
|
||||
TimeProvider? timeProvider = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
ArgumentNullException.ThrowIfNull(logger);
|
||||
@@ -39,6 +41,7 @@ public sealed class OfflineBuildIdIndex : IBuildIdIndex
|
||||
_options = options.Value;
|
||||
_logger = logger;
|
||||
_dsseSigningService = dsseSigningService;
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -176,7 +179,7 @@ public sealed class OfflineBuildIdIndex : IBuildIdIndex
|
||||
// Check index freshness
|
||||
if (_options.MaxIndexAge > TimeSpan.Zero)
|
||||
{
|
||||
var oldestAllowed = DateTimeOffset.UtcNow - _options.MaxIndexAge;
|
||||
var oldestAllowed = _timeProvider.GetUtcNow() - _options.MaxIndexAge;
|
||||
var latestEntry = entries.Values.MaxBy(e => e.IndexedAt);
|
||||
if (latestEntry is not null && latestEntry.IndexedAt < oldestAllowed)
|
||||
{
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using StellaOps.Determinism;
|
||||
|
||||
namespace StellaOps.Scanner.Analyzers.Native.RuntimeCapture;
|
||||
|
||||
@@ -22,6 +23,8 @@ namespace StellaOps.Scanner.Analyzers.Native.RuntimeCapture;
|
||||
[SupportedOSPlatform("linux")]
|
||||
public sealed class LinuxEbpfCaptureAdapter : IRuntimeCaptureAdapter
|
||||
{
|
||||
private readonly TimeProvider _timeProvider;
|
||||
private readonly IGuidProvider _guidProvider;
|
||||
private readonly ConcurrentBag<RuntimeLoadEvent> _events = [];
|
||||
private readonly object _stateLock = new();
|
||||
private CaptureState _state = CaptureState.Idle;
|
||||
@@ -33,6 +36,17 @@ public sealed class LinuxEbpfCaptureAdapter : IRuntimeCaptureAdapter
|
||||
private long _droppedEvents;
|
||||
private int _redactedPaths;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new Linux eBPF capture adapter.
|
||||
/// </summary>
|
||||
/// <param name="timeProvider">Optional time provider for deterministic timestamps.</param>
|
||||
/// <param name="guidProvider">Optional GUID provider for deterministic session IDs.</param>
|
||||
public LinuxEbpfCaptureAdapter(TimeProvider? timeProvider = null, IGuidProvider? guidProvider = null)
|
||||
{
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
_guidProvider = guidProvider ?? SystemGuidProvider.Instance;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string AdapterId => "linux-ebpf-dlopen";
|
||||
|
||||
@@ -152,8 +166,8 @@ public sealed class LinuxEbpfCaptureAdapter : IRuntimeCaptureAdapter
|
||||
_events.Clear();
|
||||
_droppedEvents = 0;
|
||||
_redactedPaths = 0;
|
||||
SessionId = Guid.NewGuid().ToString("N");
|
||||
_startTime = DateTime.UtcNow;
|
||||
SessionId = _guidProvider.NewGuid().ToString("N");
|
||||
_startTime = _timeProvider.GetUtcNow().UtcDateTime;
|
||||
_captureCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
|
||||
try
|
||||
@@ -243,7 +257,7 @@ public sealed class LinuxEbpfCaptureAdapter : IRuntimeCaptureAdapter
|
||||
var session = new RuntimeCaptureSession(
|
||||
SessionId: SessionId ?? "unknown",
|
||||
StartTime: _startTime,
|
||||
EndTime: DateTime.UtcNow,
|
||||
EndTime: _timeProvider.GetUtcNow().UtcDateTime,
|
||||
Platform: Platform,
|
||||
CaptureMethod: CaptureMethod,
|
||||
TargetProcessId: _options?.TargetProcessId,
|
||||
@@ -405,7 +419,7 @@ public sealed class LinuxEbpfCaptureAdapter : IRuntimeCaptureAdapter
|
||||
if (parts[0] == "DLOPEN" && parts.Length >= 5)
|
||||
{
|
||||
return new RuntimeLoadEvent(
|
||||
Timestamp: DateTime.UtcNow,
|
||||
Timestamp: _timeProvider.GetUtcNow().UtcDateTime,
|
||||
ProcessId: int.Parse(parts[1]),
|
||||
ThreadId: int.Parse(parts[2]),
|
||||
LoadType: RuntimeLoadType.Dlopen,
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Text.RegularExpressions;
|
||||
using StellaOps.Determinism;
|
||||
|
||||
namespace StellaOps.Scanner.Analyzers.Native.RuntimeCapture;
|
||||
|
||||
@@ -23,6 +24,8 @@ namespace StellaOps.Scanner.Analyzers.Native.RuntimeCapture;
|
||||
[SupportedOSPlatform("macos")]
|
||||
public sealed class MacOsDyldCaptureAdapter : IRuntimeCaptureAdapter
|
||||
{
|
||||
private readonly TimeProvider _timeProvider;
|
||||
private readonly IGuidProvider _guidProvider;
|
||||
private readonly ConcurrentBag<RuntimeLoadEvent> _events = [];
|
||||
private readonly object _stateLock = new();
|
||||
private CaptureState _state = CaptureState.Idle;
|
||||
@@ -34,6 +37,17 @@ public sealed class MacOsDyldCaptureAdapter : IRuntimeCaptureAdapter
|
||||
private long _droppedEvents;
|
||||
private int _redactedPaths;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new macOS dyld capture adapter.
|
||||
/// </summary>
|
||||
/// <param name="timeProvider">Optional time provider for deterministic timestamps.</param>
|
||||
/// <param name="guidProvider">Optional GUID provider for deterministic session IDs.</param>
|
||||
public MacOsDyldCaptureAdapter(TimeProvider? timeProvider = null, IGuidProvider? guidProvider = null)
|
||||
{
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
_guidProvider = guidProvider ?? SystemGuidProvider.Instance;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string AdapterId => "macos-dyld-interpose";
|
||||
|
||||
@@ -156,8 +170,8 @@ public sealed class MacOsDyldCaptureAdapter : IRuntimeCaptureAdapter
|
||||
_events.Clear();
|
||||
_droppedEvents = 0;
|
||||
_redactedPaths = 0;
|
||||
SessionId = Guid.NewGuid().ToString("N");
|
||||
_startTime = DateTime.UtcNow;
|
||||
SessionId = _guidProvider.NewGuid().ToString("N");
|
||||
_startTime = _timeProvider.GetUtcNow().UtcDateTime;
|
||||
_captureCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
|
||||
try
|
||||
@@ -247,7 +261,7 @@ public sealed class MacOsDyldCaptureAdapter : IRuntimeCaptureAdapter
|
||||
var session = new RuntimeCaptureSession(
|
||||
SessionId: SessionId ?? "unknown",
|
||||
StartTime: _startTime,
|
||||
EndTime: DateTime.UtcNow,
|
||||
EndTime: _timeProvider.GetUtcNow().UtcDateTime,
|
||||
Platform: Platform,
|
||||
CaptureMethod: CaptureMethod,
|
||||
TargetProcessId: _options?.TargetProcessId,
|
||||
@@ -417,7 +431,7 @@ public sealed class MacOsDyldCaptureAdapter : IRuntimeCaptureAdapter
|
||||
: RuntimeLoadType.MacOsDlopen;
|
||||
|
||||
return new RuntimeLoadEvent(
|
||||
Timestamp: DateTime.UtcNow,
|
||||
Timestamp: _timeProvider.GetUtcNow().UtcDateTime,
|
||||
ProcessId: int.Parse(parts[1]),
|
||||
ThreadId: int.Parse(parts[2]),
|
||||
LoadType: loadType,
|
||||
|
||||
@@ -48,11 +48,13 @@ public static class RuntimeEvidenceAggregator
|
||||
/// <param name="runtimeEvidence">Runtime capture evidence.</param>
|
||||
/// <param name="staticEdges">Static analysis dependency edges.</param>
|
||||
/// <param name="heuristicEdges">Heuristic analysis edges.</param>
|
||||
/// <param name="timeProvider">Optional time provider for deterministic timestamps.</param>
|
||||
/// <returns>Merged evidence document.</returns>
|
||||
public static MergedEvidence MergeWithStaticAnalysis(
|
||||
RuntimeEvidence runtimeEvidence,
|
||||
IEnumerable<Observations.NativeObservationDeclaredEdge> staticEdges,
|
||||
IEnumerable<Observations.NativeObservationHeuristicEdge> heuristicEdges)
|
||||
IEnumerable<Observations.NativeObservationHeuristicEdge> heuristicEdges,
|
||||
TimeProvider? timeProvider = null)
|
||||
{
|
||||
var staticList = staticEdges.ToList();
|
||||
var heuristicList = heuristicEdges.ToList();
|
||||
@@ -140,6 +142,7 @@ public static class RuntimeEvidenceAggregator
|
||||
}
|
||||
}
|
||||
|
||||
var tp = timeProvider ?? TimeProvider.System;
|
||||
return new MergedEvidence(
|
||||
ConfirmedEdges: confirmedEdges,
|
||||
StaticOnlyEdges: staticOnlyEdges,
|
||||
@@ -148,7 +151,7 @@ public static class RuntimeEvidenceAggregator
|
||||
TotalRuntimeEvents: runtimeEvidence.Sessions.Sum(s => s.Events.Count),
|
||||
TotalDroppedEvents: runtimeEvidence.Sessions.Sum(s => s.TotalEventsDropped),
|
||||
CaptureStartTime: runtimeEvidence.Sessions.Min(s => s.StartTime),
|
||||
CaptureEndTime: runtimeEvidence.Sessions.Max(s => s.EndTime ?? DateTime.UtcNow));
|
||||
CaptureEndTime: runtimeEvidence.Sessions.Max(s => s.EndTime ?? tp.GetUtcNow().UtcDateTime));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -273,7 +273,9 @@ public sealed record CollapsedStack
|
||||
/// Parses a collapsed stack line.
|
||||
/// Format: "container@digest;buildid=xxx;func;... count"
|
||||
/// </summary>
|
||||
public static CollapsedStack? Parse(string line)
|
||||
/// <param name="line">The collapsed stack line to parse.</param>
|
||||
/// <param name="timeProvider">Optional time provider for deterministic timestamps.</param>
|
||||
public static CollapsedStack? Parse(string line, TimeProvider? timeProvider = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(line))
|
||||
return null;
|
||||
@@ -305,7 +307,8 @@ public sealed record CollapsedStack
|
||||
}
|
||||
}
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
var tp = timeProvider ?? TimeProvider.System;
|
||||
var now = tp.GetUtcNow().UtcDateTime;
|
||||
return new CollapsedStack
|
||||
{
|
||||
ContainerIdentifier = container,
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Security.Principal;
|
||||
using System.Text.RegularExpressions;
|
||||
using StellaOps.Determinism;
|
||||
|
||||
namespace StellaOps.Scanner.Analyzers.Native.RuntimeCapture;
|
||||
|
||||
@@ -21,6 +22,8 @@ namespace StellaOps.Scanner.Analyzers.Native.RuntimeCapture;
|
||||
[SupportedOSPlatform("windows")]
|
||||
public sealed class WindowsEtwCaptureAdapter : IRuntimeCaptureAdapter
|
||||
{
|
||||
private readonly TimeProvider _timeProvider;
|
||||
private readonly IGuidProvider _guidProvider;
|
||||
private readonly ConcurrentBag<RuntimeLoadEvent> _events = [];
|
||||
private readonly object _stateLock = new();
|
||||
private CaptureState _state = CaptureState.Idle;
|
||||
@@ -34,6 +37,17 @@ public sealed class WindowsEtwCaptureAdapter : IRuntimeCaptureAdapter
|
||||
private long _droppedEvents;
|
||||
private int _redactedPaths;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new Windows ETW capture adapter.
|
||||
/// </summary>
|
||||
/// <param name="timeProvider">Optional time provider for deterministic timestamps.</param>
|
||||
/// <param name="guidProvider">Optional GUID provider for deterministic session IDs.</param>
|
||||
public WindowsEtwCaptureAdapter(TimeProvider? timeProvider = null, IGuidProvider? guidProvider = null)
|
||||
{
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
_guidProvider = guidProvider ?? SystemGuidProvider.Instance;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string AdapterId => "windows-etw-imageload";
|
||||
|
||||
@@ -146,8 +160,8 @@ public sealed class WindowsEtwCaptureAdapter : IRuntimeCaptureAdapter
|
||||
_events.Clear();
|
||||
_droppedEvents = 0;
|
||||
_redactedPaths = 0;
|
||||
SessionId = Guid.NewGuid().ToString("N");
|
||||
_startTime = DateTime.UtcNow;
|
||||
SessionId = _guidProvider.NewGuid().ToString("N");
|
||||
_startTime = _timeProvider.GetUtcNow().UtcDateTime;
|
||||
_captureCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
|
||||
try
|
||||
@@ -240,7 +254,7 @@ public sealed class WindowsEtwCaptureAdapter : IRuntimeCaptureAdapter
|
||||
var session = new RuntimeCaptureSession(
|
||||
SessionId: SessionId ?? "unknown",
|
||||
StartTime: _startTime,
|
||||
EndTime: DateTime.UtcNow,
|
||||
EndTime: _timeProvider.GetUtcNow().UtcDateTime,
|
||||
Platform: Platform,
|
||||
CaptureMethod: CaptureMethod,
|
||||
TargetProcessId: _options?.TargetProcessId,
|
||||
@@ -480,7 +494,7 @@ public sealed class WindowsEtwCaptureAdapter : IRuntimeCaptureAdapter
|
||||
: RuntimeLoadType.LoadLibrary;
|
||||
|
||||
var evt = new RuntimeLoadEvent(
|
||||
Timestamp: DateTime.UtcNow,
|
||||
Timestamp: _timeProvider.GetUtcNow().UtcDateTime,
|
||||
ProcessId: processId,
|
||||
ThreadId: 0,
|
||||
LoadType: loadType,
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\\__Libraries\\StellaOps.Scanner.ProofSpine\\StellaOps.Scanner.ProofSpine.csproj" />
|
||||
<ProjectReference Include="..\\..\\__Libraries\\StellaOps.Determinism.Abstractions\\StellaOps.Determinism.Abstractions.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -151,6 +151,7 @@ public static class EpssEndpoints
|
||||
private static async Task<IResult> GetHistory(
|
||||
[FromRoute] string cveId,
|
||||
[FromServices] IEpssProvider epssProvider,
|
||||
[FromServices] TimeProvider timeProvider,
|
||||
[FromQuery] string? startDate = null,
|
||||
[FromQuery] string? endDate = null,
|
||||
[FromQuery] int days = 30,
|
||||
@@ -183,7 +184,7 @@ public static class EpssEndpoints
|
||||
else
|
||||
{
|
||||
// Default to last N days
|
||||
end = DateOnly.FromDateTime(DateTime.UtcNow);
|
||||
end = DateOnly.FromDateTime(timeProvider.GetUtcNow().UtcDateTime);
|
||||
start = end.AddDays(-days);
|
||||
}
|
||||
|
||||
@@ -213,6 +214,7 @@ public static class EpssEndpoints
|
||||
/// </summary>
|
||||
private static async Task<IResult> GetStatus(
|
||||
[FromServices] IEpssProvider epssProvider,
|
||||
[FromServices] TimeProvider timeProvider,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var isAvailable = await epssProvider.IsAvailableAsync(cancellationToken);
|
||||
@@ -222,7 +224,7 @@ public static class EpssEndpoints
|
||||
{
|
||||
Available = isAvailable,
|
||||
LatestModelDate = modelDate?.ToString("yyyy-MM-dd"),
|
||||
LastCheckedUtc = DateTimeOffset.UtcNow
|
||||
LastCheckedUtc = timeProvider.GetUtcNow()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@ internal static class EvidenceEndpoints
|
||||
string scanId,
|
||||
string findingId,
|
||||
IEvidenceCompositionService evidenceService,
|
||||
TimeProvider timeProvider,
|
||||
HttpContext context,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -108,7 +109,7 @@ internal static class EvidenceEndpoints
|
||||
}
|
||||
else if (evidence.Freshness.ExpiresAt.HasValue)
|
||||
{
|
||||
var timeUntilExpiry = evidence.Freshness.ExpiresAt.Value - DateTimeOffset.UtcNow;
|
||||
var timeUntilExpiry = evidence.Freshness.ExpiresAt.Value - timeProvider.GetUtcNow();
|
||||
if (timeUntilExpiry <= TimeSpan.FromDays(1))
|
||||
{
|
||||
context.Response.Headers["X-Evidence-Warning"] = "near-expiry";
|
||||
|
||||
@@ -270,6 +270,7 @@ internal static class SmartDiffEndpoints
|
||||
string candidateId,
|
||||
ReviewRequest request,
|
||||
IVexCandidateStore store,
|
||||
TimeProvider timeProvider,
|
||||
HttpContext httpContext,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
@@ -282,7 +283,7 @@ internal static class SmartDiffEndpoints
|
||||
var review = new VexCandidateReview(
|
||||
Action: action,
|
||||
Reviewer: reviewer,
|
||||
ReviewedAt: DateTimeOffset.UtcNow,
|
||||
ReviewedAt: timeProvider.GetUtcNow(),
|
||||
Comment: request.Comment);
|
||||
|
||||
var success = await store.ReviewCandidateAsync(candidateId, review, ct);
|
||||
|
||||
@@ -41,6 +41,7 @@ internal static class ProofBundleEndpoints
|
||||
private static async Task<IResult> HandleGenerateProofBundleAsync(
|
||||
[FromBody] ProofBundleRequest request,
|
||||
[FromServices] IProofBundleGenerator bundleGenerator,
|
||||
[FromServices] TimeProvider timeProvider,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(bundleGenerator);
|
||||
@@ -67,7 +68,7 @@ internal static class ProofBundleEndpoints
|
||||
{
|
||||
PathId = request.PathId,
|
||||
Bundle = bundle,
|
||||
GeneratedAt = DateTimeOffset.UtcNow
|
||||
GeneratedAt = timeProvider.GetUtcNow()
|
||||
};
|
||||
|
||||
return Results.Ok(response);
|
||||
|
||||
@@ -50,6 +50,7 @@ internal static class TriageInboxEndpoints
|
||||
[FromQuery] string? filter,
|
||||
[FromServices] IExploitPathGroupingService groupingService,
|
||||
[FromServices] IFindingQueryService findingService,
|
||||
[FromServices] TimeProvider timeProvider,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(groupingService);
|
||||
@@ -77,7 +78,7 @@ internal static class TriageInboxEndpoints
|
||||
FilteredPaths = filteredPaths.Count,
|
||||
Filter = filter,
|
||||
Paths = filteredPaths,
|
||||
GeneratedAt = DateTimeOffset.UtcNow
|
||||
GeneratedAt = timeProvider.GetUtcNow()
|
||||
};
|
||||
|
||||
return Results.Ok(response);
|
||||
|
||||
@@ -55,6 +55,7 @@ internal static class UnknownsEndpoints
|
||||
[FromQuery] int? limit,
|
||||
IUnknownRepository repository,
|
||||
IUnknownRanker ranker,
|
||||
TimeProvider timeProvider,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// Validate and default pagination
|
||||
@@ -95,9 +96,10 @@ internal static class UnknownsEndpoints
|
||||
PageSize: pageSize);
|
||||
|
||||
var result = await repository.ListUnknownsAsync(query, cancellationToken);
|
||||
var now = timeProvider.GetUtcNow();
|
||||
|
||||
return Results.Ok(new UnknownsListResponse(
|
||||
Items: result.Items.Select(UnknownItemResponse.FromUnknownItem).ToList(),
|
||||
Items: result.Items.Select(item => UnknownItemResponse.FromUnknownItem(item, now)).ToList(),
|
||||
TotalCount: result.TotalCount,
|
||||
Page: pageNum,
|
||||
PageSize: pageSize,
|
||||
@@ -195,7 +197,7 @@ public sealed record UnknownItemResponse(
|
||||
ContainmentResponse? Containment,
|
||||
DateTimeOffset CreatedAt)
|
||||
{
|
||||
public static UnknownItemResponse FromUnknownItem(UnknownItem item) => new(
|
||||
public static UnknownItemResponse FromUnknownItem(UnknownItem item, DateTimeOffset now) => new(
|
||||
Id: Guid.TryParse(item.Id, out var id) ? id : Guid.Empty,
|
||||
SubjectRef: item.ArtifactPurl ?? item.ArtifactDigest,
|
||||
Kind: string.Join(",", item.Reasons),
|
||||
@@ -209,7 +211,7 @@ public sealed record UnknownItemResponse(
|
||||
Containment: item.Containment != null
|
||||
? new ContainmentResponse(item.Containment.Seccomp, item.Containment.Fs)
|
||||
: null,
|
||||
CreatedAt: DateTimeOffset.UtcNow); // Would come from Unknown.SysFrom
|
||||
CreatedAt: now); // Would come from Unknown.SysFrom
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -120,6 +120,7 @@ internal static class WitnessEndpoints
|
||||
private static async Task<IResult> HandleVerifyWitnessAsync(
|
||||
Guid witnessId,
|
||||
IWitnessRepository repository,
|
||||
TimeProvider timeProvider,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(repository);
|
||||
@@ -161,10 +162,11 @@ internal static class WitnessEndpoints
|
||||
}
|
||||
|
||||
// Record verification attempt
|
||||
var now = timeProvider.GetUtcNow();
|
||||
await repository.RecordVerificationAsync(new WitnessVerificationRecord
|
||||
{
|
||||
WitnessId = witnessId,
|
||||
VerifiedAt = DateTimeOffset.UtcNow,
|
||||
VerifiedAt = now,
|
||||
VerifiedBy = "api",
|
||||
VerificationStatus = verificationStatus,
|
||||
VerificationError = verificationError
|
||||
@@ -176,7 +178,7 @@ internal static class WitnessEndpoints
|
||||
WitnessHash = witness.WitnessHash,
|
||||
Status = verificationStatus,
|
||||
Error = verificationError,
|
||||
VerifiedAt = DateTimeOffset.UtcNow,
|
||||
VerifiedAt = now,
|
||||
IsSigned = !string.IsNullOrEmpty(witness.DsseEnvelope)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -306,7 +306,7 @@ public sealed class EvidenceBundleExporter : IEvidenceBundleExporter
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static async Task PrepareEvidenceFilesAsync(
|
||||
private async Task PrepareEvidenceFilesAsync(
|
||||
UnifiedEvidenceResponseDto evidence,
|
||||
List<(string path, MemoryStream stream, string contentType)> streams,
|
||||
List<ArchiveFileEntry> entries,
|
||||
@@ -621,7 +621,7 @@ public sealed class EvidenceBundleExporter : IEvidenceBundleExporter
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task CreateTarGzArchiveAsync(
|
||||
private async Task CreateTarGzArchiveAsync(
|
||||
string findingId,
|
||||
List<(string path, MemoryStream stream, string contentType)> files,
|
||||
Stream outputStream,
|
||||
@@ -660,7 +660,7 @@ public sealed class EvidenceBundleExporter : IEvidenceBundleExporter
|
||||
await gzipStream.WriteAsync(endBlocks, ct).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static byte[] CreateTarHeader(string name, long size)
|
||||
private byte[] CreateTarHeader(string name, long size)
|
||||
{
|
||||
var header = new byte[512];
|
||||
|
||||
|
||||
@@ -230,7 +230,7 @@ public sealed class GatingReasonService : IGatingReasonService
|
||||
/// <summary>
|
||||
/// Computes a composite trust score for a VEX record.
|
||||
/// </summary>
|
||||
private static double ComputeVexTrustScore(TriageEffectiveVex vex)
|
||||
private double ComputeVexTrustScore(TriageEffectiveVex vex)
|
||||
{
|
||||
// Weighted combination of trust factors
|
||||
const double IssuerWeight = 0.4;
|
||||
|
||||
@@ -29,6 +29,7 @@ public sealed class ReportSigner : IReportSigner
|
||||
private readonly ILogger<ReportSigner> logger;
|
||||
private readonly ICryptoProviderRegistry cryptoRegistry;
|
||||
private readonly ICryptoHmac cryptoHmac;
|
||||
private readonly TimeProvider timeProvider;
|
||||
private readonly ICryptoProvider? provider;
|
||||
private readonly CryptoKeyReference? keyReference;
|
||||
private readonly CryptoSignerResolution? signerResolution;
|
||||
@@ -38,11 +39,13 @@ public sealed class ReportSigner : IReportSigner
|
||||
IOptions<ScannerWebServiceOptions> options,
|
||||
ICryptoProviderRegistry cryptoRegistry,
|
||||
ICryptoHmac cryptoHmac,
|
||||
TimeProvider timeProvider,
|
||||
ILogger<ReportSigner> logger)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
this.cryptoRegistry = cryptoRegistry ?? throw new ArgumentNullException(nameof(cryptoRegistry));
|
||||
this.cryptoHmac = cryptoHmac ?? throw new ArgumentNullException(nameof(cryptoHmac));
|
||||
this.timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider));
|
||||
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
|
||||
var value = options.Value ?? new ScannerWebServiceOptions();
|
||||
@@ -79,7 +82,7 @@ public sealed class ReportSigner : IReportSigner
|
||||
reference,
|
||||
canonicalAlgorithm,
|
||||
privateKey,
|
||||
createdAt: DateTimeOffset.UtcNow);
|
||||
createdAt: timeProvider.GetUtcNow());
|
||||
|
||||
provider.UpsertSigningKey(signingKeyDescriptor);
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ public sealed class ScoreReplayService : IScoreReplayService
|
||||
private readonly IProofBundleWriter _bundleWriter;
|
||||
private readonly IScanManifestSigner _manifestSigner;
|
||||
private readonly IScoringService _scoringService;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
private readonly ILogger<ScoreReplayService> _logger;
|
||||
|
||||
public ScoreReplayService(
|
||||
@@ -31,6 +32,7 @@ public sealed class ScoreReplayService : IScoreReplayService
|
||||
IProofBundleWriter bundleWriter,
|
||||
IScanManifestSigner manifestSigner,
|
||||
IScoringService scoringService,
|
||||
TimeProvider timeProvider,
|
||||
ILogger<ScoreReplayService> logger)
|
||||
{
|
||||
_manifestRepository = manifestRepository ?? throw new ArgumentNullException(nameof(manifestRepository));
|
||||
@@ -38,6 +40,7 @@ public sealed class ScoreReplayService : IScoreReplayService
|
||||
_bundleWriter = bundleWriter ?? throw new ArgumentNullException(nameof(bundleWriter));
|
||||
_manifestSigner = manifestSigner ?? throw new ArgumentNullException(nameof(manifestSigner));
|
||||
_scoringService = scoringService ?? throw new ArgumentNullException(nameof(scoringService));
|
||||
_timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
@@ -99,7 +102,7 @@ public sealed class ScoreReplayService : IScoreReplayService
|
||||
RootHash: bundle.RootHash,
|
||||
BundleUri: bundle.BundleUri,
|
||||
ManifestHash: manifest.ComputeHash(),
|
||||
ReplayedAt: DateTimeOffset.UtcNow,
|
||||
ReplayedAt: _timeProvider.GetUtcNow(),
|
||||
Deterministic: manifest.Deterministic);
|
||||
}
|
||||
finally
|
||||
@@ -164,7 +167,7 @@ public sealed class ScoreReplayService : IScoreReplayService
|
||||
ComputedRootHash: computedRootHash,
|
||||
ManifestValid: manifestVerify.IsValid,
|
||||
LedgerValid: ledgerValid,
|
||||
VerifiedAt: DateTimeOffset.UtcNow,
|
||||
VerifiedAt: _timeProvider.GetUtcNow(),
|
||||
ErrorMessage: string.Join("; ", errors));
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ public sealed class SliceQueryService : ISliceQueryService
|
||||
private readonly SliceHasher _hasher;
|
||||
private readonly IFileContentAddressableStore _cas;
|
||||
private readonly IScanMetadataRepository _scanRepo;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
private readonly SliceQueryServiceOptions _options;
|
||||
private readonly ILogger<SliceQueryService> _logger;
|
||||
|
||||
@@ -51,6 +52,7 @@ public sealed class SliceQueryService : ISliceQueryService
|
||||
SliceHasher hasher,
|
||||
IFileContentAddressableStore cas,
|
||||
IScanMetadataRepository scanRepo,
|
||||
TimeProvider timeProvider,
|
||||
IOptions<SliceQueryServiceOptions> options,
|
||||
ILogger<SliceQueryService> logger)
|
||||
{
|
||||
@@ -61,6 +63,7 @@ public sealed class SliceQueryService : ISliceQueryService
|
||||
_hasher = hasher ?? throw new ArgumentNullException(nameof(hasher));
|
||||
_cas = cas ?? throw new ArgumentNullException(nameof(cas));
|
||||
_scanRepo = scanRepo ?? throw new ArgumentNullException(nameof(scanRepo));
|
||||
_timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider));
|
||||
_options = options?.Value ?? new SliceQueryServiceOptions();
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
@@ -121,7 +124,7 @@ public sealed class SliceQueryService : ISliceQueryService
|
||||
PathWitnesses = slice.Verdict.PathWitnesses.IsDefaultOrEmpty
|
||||
? Array.Empty<string>()
|
||||
: slice.Verdict.PathWitnesses.ToList(),
|
||||
CachedAt = DateTimeOffset.UtcNow
|
||||
CachedAt = _timeProvider.GetUtcNow()
|
||||
};
|
||||
await _cache.SetAsync(cacheKey, cacheEntry, TimeSpan.FromHours(1), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -64,6 +64,12 @@ public sealed class TestProofBundleRepository : StellaOps.Scanner.Storage.Reposi
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, ProofBundleRow> _bundlesByRootHash = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly ConcurrentDictionary<Guid, List<ProofBundleRow>> _bundlesByScanId = new();
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
public TestProofBundleRepository(TimeProvider timeProvider)
|
||||
{
|
||||
_timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider));
|
||||
}
|
||||
|
||||
public Task<ProofBundleRow?> GetByRootHashAsync(string rootHash, CancellationToken cancellationToken = default)
|
||||
{
|
||||
@@ -112,8 +118,8 @@ public sealed class TestProofBundleRepository : StellaOps.Scanner.Storage.Reposi
|
||||
public Task<int> DeleteExpiredAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
|
||||
var now = _timeProvider.GetUtcNow();
|
||||
var expired = _bundlesByRootHash.Values
|
||||
.Where(b => b.ExpiresAt.HasValue && b.ExpiresAt.Value < now)
|
||||
.ToList();
|
||||
|
||||
@@ -22,6 +22,7 @@ public sealed class UnifiedEvidenceService : IUnifiedEvidenceService
|
||||
private readonly TriageDbContext _dbContext;
|
||||
private readonly IGatingReasonService _gatingService;
|
||||
private readonly IReplayCommandService _replayService;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
private readonly ILogger<UnifiedEvidenceService> _logger;
|
||||
|
||||
private const double DefaultPolicyTrustThreshold = 0.7;
|
||||
@@ -30,11 +31,13 @@ public sealed class UnifiedEvidenceService : IUnifiedEvidenceService
|
||||
TriageDbContext dbContext,
|
||||
IGatingReasonService gatingService,
|
||||
IReplayCommandService replayService,
|
||||
TimeProvider timeProvider,
|
||||
ILogger<UnifiedEvidenceService> logger)
|
||||
{
|
||||
_dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
|
||||
_gatingService = gatingService ?? throw new ArgumentNullException(nameof(gatingService));
|
||||
_replayService = replayService ?? throw new ArgumentNullException(nameof(replayService));
|
||||
_timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
@@ -106,7 +109,7 @@ public sealed class UnifiedEvidenceService : IUnifiedEvidenceService
|
||||
ReplayCommand = replayResponse?.FullCommand?.Command,
|
||||
ShortReplayCommand = replayResponse?.ShortCommand?.Command,
|
||||
EvidenceBundleUrl = replayResponse?.Bundle?.DownloadUri,
|
||||
GeneratedAt = DateTimeOffset.UtcNow,
|
||||
GeneratedAt = _timeProvider.GetUtcNow(),
|
||||
CacheKey = cacheKey
|
||||
};
|
||||
}
|
||||
@@ -277,11 +280,11 @@ public sealed class UnifiedEvidenceService : IUnifiedEvidenceService
|
||||
AttestationsVerified = hasAttestations,
|
||||
EvidenceComplete = hasVex && hasReachability,
|
||||
Issues = issues.Count > 0 ? issues : null,
|
||||
VerifiedAt = DateTimeOffset.UtcNow
|
||||
VerifiedAt = _timeProvider.GetUtcNow()
|
||||
};
|
||||
}
|
||||
|
||||
private static double ComputeVexTrustScore(TriageEffectiveVex vex)
|
||||
private double ComputeVexTrustScore(TriageEffectiveVex vex)
|
||||
{
|
||||
const double IssuerWeight = 0.4;
|
||||
const double RecencyWeight = 0.2;
|
||||
@@ -289,7 +292,7 @@ public sealed class UnifiedEvidenceService : IUnifiedEvidenceService
|
||||
const double EvidenceWeight = 0.2;
|
||||
|
||||
var issuerTrust = GetIssuerTrust(vex.Issuer);
|
||||
var recencyTrust = GetRecencyTrust((DateTimeOffset?)vex.ValidFrom);
|
||||
var recencyTrust = GetRecencyTrust((DateTimeOffset?)vex.ValidFrom, _timeProvider.GetUtcNow());
|
||||
var justificationTrust = GetJustificationTrust(vex.PrunedSourcesJson);
|
||||
var evidenceTrust = !string.IsNullOrEmpty(vex.DsseEnvelopeHash) ? 0.8 : 0.3;
|
||||
|
||||
@@ -309,10 +312,10 @@ public sealed class UnifiedEvidenceService : IUnifiedEvidenceService
|
||||
_ => 0.5
|
||||
};
|
||||
|
||||
private static double GetRecencyTrust(DateTimeOffset? timestamp)
|
||||
private static double GetRecencyTrust(DateTimeOffset? timestamp, DateTimeOffset now)
|
||||
{
|
||||
if (timestamp is null) return 0.3;
|
||||
var age = DateTimeOffset.UtcNow - timestamp.Value;
|
||||
var age = now - timestamp.Value;
|
||||
return age.TotalDays switch { <= 7 => 1.0, <= 30 => 0.9, <= 90 => 0.7, <= 365 => 0.5, _ => 0.3 };
|
||||
}
|
||||
|
||||
|
||||
@@ -267,7 +267,7 @@ public sealed record SourceRunResponse
|
||||
Status = run.Status,
|
||||
StartedAt = run.StartedAt,
|
||||
CompletedAt = run.CompletedAt,
|
||||
DurationMs = run.DurationMs,
|
||||
DurationMs = run.GetDurationMs(),
|
||||
ItemsDiscovered = run.ItemsDiscovered,
|
||||
ItemsScanned = run.ItemsScanned,
|
||||
ItemsSucceeded = run.ItemsSucceeded,
|
||||
|
||||
@@ -84,8 +84,9 @@ public sealed class SourceTriggerDispatcher : ISourceTriggerDispatcher
|
||||
source.TenantId,
|
||||
context.Trigger,
|
||||
context.CorrelationId,
|
||||
_timeProvider,
|
||||
context.TriggerDetails);
|
||||
failedRun.Fail(canTrigger.Error!);
|
||||
failedRun.Fail(canTrigger.Error!, _timeProvider);
|
||||
await _runRepository.CreateAsync(failedRun, ct);
|
||||
|
||||
return new TriggerDispatchResult
|
||||
@@ -102,6 +103,7 @@ public sealed class SourceTriggerDispatcher : ISourceTriggerDispatcher
|
||||
source.TenantId,
|
||||
context.Trigger,
|
||||
context.CorrelationId,
|
||||
_timeProvider,
|
||||
context.TriggerDetails);
|
||||
|
||||
await _runRepository.CreateAsync(run, ct);
|
||||
@@ -112,7 +114,7 @@ public sealed class SourceTriggerDispatcher : ISourceTriggerDispatcher
|
||||
var handler = GetHandler(source.SourceType);
|
||||
if (handler == null)
|
||||
{
|
||||
run.Fail($"No handler registered for source type {source.SourceType}");
|
||||
run.Fail($"No handler registered for source type {source.SourceType}", _timeProvider);
|
||||
await _runRepository.UpdateAsync(run, ct);
|
||||
return new TriggerDispatchResult
|
||||
{
|
||||
@@ -133,9 +135,9 @@ public sealed class SourceTriggerDispatcher : ISourceTriggerDispatcher
|
||||
|
||||
if (targets.Count == 0)
|
||||
{
|
||||
run.Complete();
|
||||
run.Complete(_timeProvider);
|
||||
await _runRepository.UpdateAsync(run, ct);
|
||||
source.RecordSuccessfulRun(_timeProvider.GetUtcNow());
|
||||
source.RecordSuccessfulRun(_timeProvider.GetUtcNow(), _timeProvider);
|
||||
await _sourceRepository.UpdateAsync(source, ct);
|
||||
|
||||
return new TriggerDispatchResult
|
||||
@@ -176,13 +178,13 @@ public sealed class SourceTriggerDispatcher : ISourceTriggerDispatcher
|
||||
// 7. Complete or fail based on results
|
||||
if (run.ItemsFailed == run.ItemsDiscovered)
|
||||
{
|
||||
run.Fail("All targets failed to queue");
|
||||
source.RecordFailedRun(_timeProvider.GetUtcNow(), run.ErrorMessage!);
|
||||
run.Fail("All targets failed to queue", _timeProvider);
|
||||
source.RecordFailedRun(_timeProvider.GetUtcNow(), run.ErrorMessage!, _timeProvider);
|
||||
}
|
||||
else
|
||||
{
|
||||
run.Complete();
|
||||
source.RecordSuccessfulRun(_timeProvider.GetUtcNow());
|
||||
run.Complete(_timeProvider);
|
||||
source.RecordSuccessfulRun(_timeProvider.GetUtcNow(), _timeProvider);
|
||||
}
|
||||
|
||||
await _runRepository.UpdateAsync(run, ct);
|
||||
@@ -199,10 +201,10 @@ public sealed class SourceTriggerDispatcher : ISourceTriggerDispatcher
|
||||
{
|
||||
_logger.LogError(ex, "Dispatch failed for source {SourceId}", sourceId);
|
||||
|
||||
run.Fail(ex.Message);
|
||||
run.Fail(ex.Message, _timeProvider);
|
||||
await _runRepository.UpdateAsync(run, ct);
|
||||
|
||||
source.RecordFailedRun(_timeProvider.GetUtcNow(), ex.Message);
|
||||
source.RecordFailedRun(_timeProvider.GetUtcNow(), ex.Message, _timeProvider);
|
||||
await _sourceRepository.UpdateAsync(source, ct);
|
||||
|
||||
return new TriggerDispatchResult
|
||||
@@ -266,7 +268,7 @@ public sealed class SourceTriggerDispatcher : ISourceTriggerDispatcher
|
||||
return _handlers.FirstOrDefault(h => h.SourceType == sourceType);
|
||||
}
|
||||
|
||||
private static (bool Success, string? Error) CanTrigger(SbomSource source, TriggerContext context)
|
||||
private (bool Success, string? Error) CanTrigger(SbomSource source, TriggerContext context)
|
||||
{
|
||||
if (source.Status == SbomSourceStatus.Disabled)
|
||||
{
|
||||
@@ -292,7 +294,7 @@ public sealed class SourceTriggerDispatcher : ISourceTriggerDispatcher
|
||||
}
|
||||
}
|
||||
|
||||
if (source.IsRateLimited())
|
||||
if (source.IsRateLimited(_timeProvider))
|
||||
{
|
||||
return (false, "Source is rate limited");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user