warnings fixes, tests fixes, sprints completions
This commit is contained in:
@@ -2,10 +2,12 @@
|
||||
// Sprint: SPRINT_20251226_003_BE_exception_approval
|
||||
// Task: EXCEPT-05, EXCEPT-06, EXCEPT-07 - Exception approval API endpoints
|
||||
|
||||
using System.Globalization;
|
||||
using System.Text.Json;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using StellaOps.Auth.Abstractions;
|
||||
using StellaOps.Auth.ServerIntegration;
|
||||
using StellaOps.Determinism.Abstractions;
|
||||
using StellaOps.Policy.Engine.Services;
|
||||
using StellaOps.Policy.Persistence.Postgres.Models;
|
||||
using StellaOps.Policy.Persistence.Postgres.Repositories;
|
||||
@@ -89,6 +91,8 @@ public static class ExceptionApprovalEndpoints
|
||||
CreateApprovalRequestDto request,
|
||||
IExceptionApprovalRepository repository,
|
||||
IExceptionApprovalRulesService rulesService,
|
||||
TimeProvider timeProvider,
|
||||
IGuidProvider guidProvider,
|
||||
ILogger<ExceptionApprovalRequestEntity> logger,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -110,7 +114,8 @@ public static class ExceptionApprovalEndpoints
|
||||
}
|
||||
|
||||
// Generate request ID
|
||||
var requestId = $"EAR-{DateTimeOffset.UtcNow:yyyyMMdd}-{Guid.NewGuid().ToString("N")[..8].ToUpperInvariant()}";
|
||||
var now = timeProvider.GetUtcNow();
|
||||
var requestId = $"EAR-{now.ToString("yyyyMMdd", CultureInfo.InvariantCulture)}-{guidProvider.NewGuid().ToString("N", CultureInfo.InvariantCulture)[..8].ToUpperInvariant()}";
|
||||
|
||||
// Parse gate level
|
||||
if (!Enum.TryParse<GateLevel>(request.GateLevel, ignoreCase: true, out var gateLevel))
|
||||
@@ -139,10 +144,9 @@ public static class ExceptionApprovalEndpoints
|
||||
});
|
||||
}
|
||||
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var entity = new ExceptionApprovalRequestEntity
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Id = guidProvider.NewGuid(),
|
||||
RequestId = requestId,
|
||||
TenantId = tenantId,
|
||||
ExceptionId = request.ExceptionId,
|
||||
@@ -204,7 +208,7 @@ public static class ExceptionApprovalEndpoints
|
||||
// Record audit entry
|
||||
await repository.RecordAuditAsync(new ExceptionApprovalAuditEntity
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Id = guidProvider.NewGuid(),
|
||||
RequestId = requestId,
|
||||
TenantId = tenantId,
|
||||
SequenceNumber = 1,
|
||||
|
||||
@@ -8,6 +8,7 @@ using System.Security.Claims;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using StellaOps.Auth.Abstractions;
|
||||
using StellaOps.Auth.ServerIntegration;
|
||||
using StellaOps.Determinism.Abstractions;
|
||||
using StellaOps.Policy.Exceptions.Models;
|
||||
using StellaOps.Policy.Exceptions.Repositories;
|
||||
using StellaOps.Policy.Gateway.Contracts;
|
||||
@@ -134,6 +135,8 @@ public static class ExceptionEndpoints
|
||||
CreateExceptionRequest request,
|
||||
HttpContext context,
|
||||
IExceptionRepository repository,
|
||||
TimeProvider timeProvider,
|
||||
IGuidProvider guidProvider,
|
||||
CancellationToken cancellationToken) =>
|
||||
{
|
||||
if (request is null)
|
||||
@@ -145,8 +148,10 @@ public static class ExceptionEndpoints
|
||||
});
|
||||
}
|
||||
|
||||
var now = timeProvider.GetUtcNow();
|
||||
|
||||
// Validate expiry is in future
|
||||
if (request.ExpiresAt <= DateTimeOffset.UtcNow)
|
||||
if (request.ExpiresAt <= now)
|
||||
{
|
||||
return Results.BadRequest(new ProblemDetails
|
||||
{
|
||||
@@ -157,7 +162,7 @@ public static class ExceptionEndpoints
|
||||
}
|
||||
|
||||
// Validate expiry is not more than 1 year
|
||||
if (request.ExpiresAt > DateTimeOffset.UtcNow.AddYears(1))
|
||||
if (request.ExpiresAt > now.AddYears(1))
|
||||
{
|
||||
return Results.BadRequest(new ProblemDetails
|
||||
{
|
||||
@@ -170,7 +175,7 @@ public static class ExceptionEndpoints
|
||||
var actorId = GetActorId(context);
|
||||
var clientInfo = GetClientInfo(context);
|
||||
|
||||
var exceptionId = $"EXC-{Guid.NewGuid():N}"[..20];
|
||||
var exceptionId = $"EXC-{guidProvider.NewGuid():N}"[..20];
|
||||
|
||||
var exception = new ExceptionObject
|
||||
{
|
||||
@@ -188,8 +193,8 @@ public static class ExceptionEndpoints
|
||||
},
|
||||
OwnerId = request.OwnerId,
|
||||
RequesterId = actorId,
|
||||
CreatedAt = DateTimeOffset.UtcNow,
|
||||
UpdatedAt = DateTimeOffset.UtcNow,
|
||||
CreatedAt = now,
|
||||
UpdatedAt = now,
|
||||
ExpiresAt = request.ExpiresAt,
|
||||
ReasonCode = ParseReasonRequired(request.ReasonCode),
|
||||
Rationale = request.Rationale,
|
||||
@@ -210,6 +215,7 @@ public static class ExceptionEndpoints
|
||||
UpdateExceptionRequest request,
|
||||
HttpContext context,
|
||||
IExceptionRepository repository,
|
||||
TimeProvider timeProvider,
|
||||
CancellationToken cancellationToken) =>
|
||||
{
|
||||
var existing = await repository.GetByIdAsync(id, cancellationToken);
|
||||
@@ -238,7 +244,7 @@ public static class ExceptionEndpoints
|
||||
var updated = existing with
|
||||
{
|
||||
Version = existing.Version + 1,
|
||||
UpdatedAt = DateTimeOffset.UtcNow,
|
||||
UpdatedAt = timeProvider.GetUtcNow(),
|
||||
Rationale = request.Rationale ?? existing.Rationale,
|
||||
EvidenceRefs = request.EvidenceRefs?.ToImmutableArray() ?? existing.EvidenceRefs,
|
||||
CompensatingControls = request.CompensatingControls?.ToImmutableArray() ?? existing.CompensatingControls,
|
||||
@@ -258,6 +264,7 @@ public static class ExceptionEndpoints
|
||||
ApproveExceptionRequest? request,
|
||||
HttpContext context,
|
||||
IExceptionRepository repository,
|
||||
TimeProvider timeProvider,
|
||||
CancellationToken cancellationToken) =>
|
||||
{
|
||||
var existing = await repository.GetByIdAsync(id, cancellationToken);
|
||||
@@ -290,12 +297,13 @@ public static class ExceptionEndpoints
|
||||
});
|
||||
}
|
||||
|
||||
var now = timeProvider.GetUtcNow();
|
||||
var updated = existing with
|
||||
{
|
||||
Version = existing.Version + 1,
|
||||
Status = ExceptionStatus.Approved,
|
||||
UpdatedAt = DateTimeOffset.UtcNow,
|
||||
ApprovedAt = DateTimeOffset.UtcNow,
|
||||
UpdatedAt = now,
|
||||
ApprovedAt = now,
|
||||
ApproverIds = existing.ApproverIds.Add(actorId)
|
||||
};
|
||||
|
||||
@@ -310,6 +318,7 @@ public static class ExceptionEndpoints
|
||||
string id,
|
||||
HttpContext context,
|
||||
IExceptionRepository repository,
|
||||
TimeProvider timeProvider,
|
||||
CancellationToken cancellationToken) =>
|
||||
{
|
||||
var existing = await repository.GetByIdAsync(id, cancellationToken);
|
||||
@@ -335,7 +344,7 @@ public static class ExceptionEndpoints
|
||||
{
|
||||
Version = existing.Version + 1,
|
||||
Status = ExceptionStatus.Active,
|
||||
UpdatedAt = DateTimeOffset.UtcNow
|
||||
UpdatedAt = timeProvider.GetUtcNow()
|
||||
};
|
||||
|
||||
var result = await repository.UpdateAsync(
|
||||
@@ -350,6 +359,7 @@ public static class ExceptionEndpoints
|
||||
ExtendExceptionRequest request,
|
||||
HttpContext context,
|
||||
IExceptionRepository repository,
|
||||
TimeProvider timeProvider,
|
||||
CancellationToken cancellationToken) =>
|
||||
{
|
||||
var existing = await repository.GetByIdAsync(id, cancellationToken);
|
||||
@@ -384,7 +394,7 @@ public static class ExceptionEndpoints
|
||||
var updated = existing with
|
||||
{
|
||||
Version = existing.Version + 1,
|
||||
UpdatedAt = DateTimeOffset.UtcNow,
|
||||
UpdatedAt = timeProvider.GetUtcNow(),
|
||||
ExpiresAt = request.NewExpiresAt
|
||||
};
|
||||
|
||||
@@ -400,6 +410,7 @@ public static class ExceptionEndpoints
|
||||
[FromBody] RevokeExceptionRequest? request,
|
||||
HttpContext context,
|
||||
IExceptionRepository repository,
|
||||
TimeProvider timeProvider,
|
||||
CancellationToken cancellationToken) =>
|
||||
{
|
||||
var existing = await repository.GetByIdAsync(id, cancellationToken);
|
||||
@@ -425,7 +436,7 @@ public static class ExceptionEndpoints
|
||||
{
|
||||
Version = existing.Version + 1,
|
||||
Status = ExceptionStatus.Revoked,
|
||||
UpdatedAt = DateTimeOffset.UtcNow
|
||||
UpdatedAt = timeProvider.GetUtcNow()
|
||||
};
|
||||
|
||||
var result = await repository.UpdateAsync(
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
// Sprint: SPRINT_20251226_001_BE_cicd_gate_integration
|
||||
// Task: CICD-GATE-01 - Create POST /api/v1/policy/gate/evaluate endpoint
|
||||
|
||||
using System.Globalization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using StellaOps.Auth.Abstractions;
|
||||
using StellaOps.Auth.ServerIntegration;
|
||||
using StellaOps.Determinism.Abstractions;
|
||||
using StellaOps.Policy.Audit;
|
||||
using StellaOps.Policy.Deltas;
|
||||
using StellaOps.Policy.Engine.Gates;
|
||||
@@ -39,6 +41,8 @@ public static class GateEndpoints
|
||||
IBaselineSelector baselineSelector,
|
||||
IGateBypassAuditor bypassAuditor,
|
||||
IMemoryCache cache,
|
||||
TimeProvider timeProvider,
|
||||
IGuidProvider guidProvider,
|
||||
ILogger<DriftGateEvaluator> logger,
|
||||
CancellationToken cancellationToken) =>
|
||||
{
|
||||
@@ -79,12 +83,12 @@ public static class GateEndpoints
|
||||
|
||||
return Results.Ok(new GateEvaluateResponse
|
||||
{
|
||||
DecisionId = $"gate:{DateTimeOffset.UtcNow:yyyyMMddHHmmss}:{Guid.NewGuid():N}",
|
||||
DecisionId = $"gate:{timeProvider.GetUtcNow().ToString("yyyyMMddHHmmss", CultureInfo.InvariantCulture)}:{guidProvider.NewGuid():N}",
|
||||
Status = GateStatus.Pass,
|
||||
ExitCode = GateExitCodes.Pass,
|
||||
ImageDigest = request.ImageDigest,
|
||||
BaselineRef = request.BaselineRef,
|
||||
DecidedAt = DateTimeOffset.UtcNow,
|
||||
DecidedAt = timeProvider.GetUtcNow(),
|
||||
Summary = "First build - no baseline for comparison",
|
||||
Advisory = "This appears to be a first build. Future builds will be compared against this baseline."
|
||||
});
|
||||
@@ -224,7 +228,7 @@ public static class GateEndpoints
|
||||
.WithDescription("Retrieve a previous gate evaluation decision by ID");
|
||||
|
||||
// GET /api/v1/policy/gate/health - Health check for gate service
|
||||
gates.MapGet("/health", () => Results.Ok(new { status = "healthy", timestamp = DateTimeOffset.UtcNow }))
|
||||
gates.MapGet("/health", (TimeProvider timeProvider) => Results.Ok(new { status = "healthy", timestamp = timeProvider.GetUtcNow() }))
|
||||
.WithName("GateHealth")
|
||||
.WithDescription("Health check for the gate evaluation service");
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Text.Json;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using StellaOps.Determinism.Abstractions;
|
||||
|
||||
namespace StellaOps.Policy.Gateway.Endpoints;
|
||||
|
||||
@@ -104,6 +106,7 @@ public static class GovernanceEndpoints
|
||||
{
|
||||
var tenant = tenantId ?? GetTenantId(httpContext) ?? "default";
|
||||
var state = SealedModeStates.GetOrAdd(tenant, _ => new SealedModeState());
|
||||
var timeProvider = httpContext.RequestServices.GetRequiredService<TimeProvider>();
|
||||
|
||||
var response = new SealedModeStatusResponse
|
||||
{
|
||||
@@ -118,7 +121,7 @@ public static class GovernanceEndpoints
|
||||
.Select(MapOverrideToResponse)
|
||||
.ToList(),
|
||||
VerificationStatus = "verified",
|
||||
LastVerifiedAt = DateTimeOffset.UtcNow.ToString("O")
|
||||
LastVerifiedAt = timeProvider.GetUtcNow().ToString("O")
|
||||
};
|
||||
|
||||
return Task.FromResult(Results.Ok(response));
|
||||
@@ -144,9 +147,9 @@ public static class GovernanceEndpoints
|
||||
{
|
||||
var tenant = GetTenantId(httpContext) ?? "default";
|
||||
var actor = GetActorId(httpContext) ?? "system";
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
|
||||
var state = SealedModeStates.GetOrAdd(tenant, _ => new SealedModeState());
|
||||
var timeProvider = httpContext.RequestServices.GetRequiredService<TimeProvider>();
|
||||
var guidProvider = httpContext.RequestServices.GetRequiredService<IGuidProvider>();
|
||||
var now = timeProvider.GetUtcNow();
|
||||
|
||||
if (request.Enable)
|
||||
{
|
||||
@@ -173,7 +176,7 @@ public static class GovernanceEndpoints
|
||||
|
||||
// Audit
|
||||
RecordAudit(tenant, actor, "sealed_mode_toggled", "sealed-mode", "system_config",
|
||||
$"{(request.Enable ? "Enabled" : "Disabled")} sealed mode: {request.Reason}");
|
||||
$"{(request.Enable ? "Enabled" : "Disabled")} sealed mode: {request.Reason}", timeProvider, guidProvider);
|
||||
|
||||
var response = new SealedModeStatusResponse
|
||||
{
|
||||
@@ -197,9 +200,11 @@ public static class GovernanceEndpoints
|
||||
{
|
||||
var tenant = GetTenantId(httpContext) ?? "default";
|
||||
var actor = GetActorId(httpContext) ?? "system";
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var timeProvider = httpContext.RequestServices.GetRequiredService<TimeProvider>();
|
||||
var guidProvider = httpContext.RequestServices.GetRequiredService<IGuidProvider>();
|
||||
var now = timeProvider.GetUtcNow();
|
||||
|
||||
var overrideId = $"override-{Guid.NewGuid():N}";
|
||||
var overrideId = $"override-{guidProvider.NewGuid():N}";
|
||||
var entity = new SealedModeOverrideEntity
|
||||
{
|
||||
Id = overrideId,
|
||||
@@ -207,7 +212,7 @@ public static class GovernanceEndpoints
|
||||
Type = request.Type,
|
||||
Target = request.Target,
|
||||
Reason = request.Reason,
|
||||
ApprovalId = $"approval-{Guid.NewGuid():N}",
|
||||
ApprovalId = $"approval-{guidProvider.NewGuid():N}",
|
||||
ApprovedBy = [actor],
|
||||
ExpiresAt = now.AddHours(request.DurationHours).ToString("O"),
|
||||
CreatedAt = now.ToString("O"),
|
||||
@@ -217,7 +222,7 @@ public static class GovernanceEndpoints
|
||||
Overrides[overrideId] = entity;
|
||||
|
||||
RecordAudit(tenant, actor, "sealed_mode_override_created", overrideId, "sealed_mode_override",
|
||||
$"Created override for {request.Target}: {request.Reason}");
|
||||
$"Created override for {request.Target}: {request.Reason}", timeProvider, guidProvider);
|
||||
|
||||
return Task.FromResult(Results.Ok(MapOverrideToResponse(entity)));
|
||||
}
|
||||
@@ -229,6 +234,8 @@ public static class GovernanceEndpoints
|
||||
{
|
||||
var tenant = GetTenantId(httpContext) ?? "default";
|
||||
var actor = GetActorId(httpContext) ?? "system";
|
||||
var timeProvider = httpContext.RequestServices.GetRequiredService<TimeProvider>();
|
||||
var guidProvider = httpContext.RequestServices.GetRequiredService<IGuidProvider>();
|
||||
|
||||
if (!Overrides.TryGetValue(overrideId, out var entity) || entity.TenantId != tenant)
|
||||
{
|
||||
@@ -243,7 +250,7 @@ public static class GovernanceEndpoints
|
||||
Overrides[overrideId] = entity;
|
||||
|
||||
RecordAudit(tenant, actor, "sealed_mode_override_revoked", overrideId, "sealed_mode_override",
|
||||
$"Revoked override: {request.Reason}");
|
||||
$"Revoked override: {request.Reason}", timeProvider, guidProvider);
|
||||
|
||||
return Task.FromResult(Results.NoContent());
|
||||
}
|
||||
@@ -293,9 +300,11 @@ public static class GovernanceEndpoints
|
||||
{
|
||||
var tenant = GetTenantId(httpContext) ?? "default";
|
||||
var actor = GetActorId(httpContext) ?? "system";
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var timeProvider = httpContext.RequestServices.GetRequiredService<TimeProvider>();
|
||||
var guidProvider = httpContext.RequestServices.GetRequiredService<IGuidProvider>();
|
||||
var now = timeProvider.GetUtcNow();
|
||||
|
||||
var profileId = $"profile-{Guid.NewGuid():N}";
|
||||
var profileId = $"profile-{guidProvider.NewGuid():N}";
|
||||
var entity = new RiskProfileEntity
|
||||
{
|
||||
Id = profileId,
|
||||
@@ -317,7 +326,7 @@ public static class GovernanceEndpoints
|
||||
RiskProfiles[profileId] = entity;
|
||||
|
||||
RecordAudit(tenant, actor, "risk_profile_created", profileId, "risk_profile",
|
||||
$"Created risk profile: {request.Name}");
|
||||
$"Created risk profile: {request.Name}", timeProvider, guidProvider);
|
||||
|
||||
return Task.FromResult(Results.Created($"/api/v1/governance/risk-profiles/{profileId}", MapProfileToResponse(entity)));
|
||||
}
|
||||
@@ -329,7 +338,9 @@ public static class GovernanceEndpoints
|
||||
{
|
||||
var tenant = GetTenantId(httpContext) ?? "default";
|
||||
var actor = GetActorId(httpContext) ?? "system";
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var timeProvider = httpContext.RequestServices.GetRequiredService<TimeProvider>();
|
||||
var guidProvider = httpContext.RequestServices.GetRequiredService<IGuidProvider>();
|
||||
var now = timeProvider.GetUtcNow();
|
||||
|
||||
if (!RiskProfiles.TryGetValue(profileId, out var existing))
|
||||
{
|
||||
@@ -354,7 +365,7 @@ public static class GovernanceEndpoints
|
||||
RiskProfiles[profileId] = entity;
|
||||
|
||||
RecordAudit(tenant, actor, "risk_profile_updated", profileId, "risk_profile",
|
||||
$"Updated risk profile: {entity.Name}");
|
||||
$"Updated risk profile: {entity.Name}", timeProvider, guidProvider);
|
||||
|
||||
return Task.FromResult(Results.Ok(MapProfileToResponse(entity)));
|
||||
}
|
||||
@@ -365,6 +376,8 @@ public static class GovernanceEndpoints
|
||||
{
|
||||
var tenant = GetTenantId(httpContext) ?? "default";
|
||||
var actor = GetActorId(httpContext) ?? "system";
|
||||
var timeProvider = httpContext.RequestServices.GetRequiredService<TimeProvider>();
|
||||
var guidProvider = httpContext.RequestServices.GetRequiredService<IGuidProvider>();
|
||||
|
||||
if (!RiskProfiles.TryRemove(profileId, out var removed))
|
||||
{
|
||||
@@ -376,7 +389,7 @@ public static class GovernanceEndpoints
|
||||
}
|
||||
|
||||
RecordAudit(tenant, actor, "risk_profile_deleted", profileId, "risk_profile",
|
||||
$"Deleted risk profile: {removed.Name}");
|
||||
$"Deleted risk profile: {removed.Name}", timeProvider, guidProvider);
|
||||
|
||||
return Task.FromResult(Results.NoContent());
|
||||
}
|
||||
@@ -387,7 +400,9 @@ public static class GovernanceEndpoints
|
||||
{
|
||||
var tenant = GetTenantId(httpContext) ?? "default";
|
||||
var actor = GetActorId(httpContext) ?? "system";
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var timeProvider = httpContext.RequestServices.GetRequiredService<TimeProvider>();
|
||||
var guidProvider = httpContext.RequestServices.GetRequiredService<IGuidProvider>();
|
||||
var now = timeProvider.GetUtcNow();
|
||||
|
||||
if (!RiskProfiles.TryGetValue(profileId, out var existing))
|
||||
{
|
||||
@@ -408,7 +423,7 @@ public static class GovernanceEndpoints
|
||||
RiskProfiles[profileId] = entity;
|
||||
|
||||
RecordAudit(tenant, actor, "risk_profile_activated", profileId, "risk_profile",
|
||||
$"Activated risk profile: {entity.Name}");
|
||||
$"Activated risk profile: {entity.Name}", timeProvider, guidProvider);
|
||||
|
||||
return Task.FromResult(Results.Ok(MapProfileToResponse(entity)));
|
||||
}
|
||||
@@ -420,7 +435,9 @@ public static class GovernanceEndpoints
|
||||
{
|
||||
var tenant = GetTenantId(httpContext) ?? "default";
|
||||
var actor = GetActorId(httpContext) ?? "system";
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var timeProvider = httpContext.RequestServices.GetRequiredService<TimeProvider>();
|
||||
var guidProvider = httpContext.RequestServices.GetRequiredService<IGuidProvider>();
|
||||
var now = timeProvider.GetUtcNow();
|
||||
|
||||
if (!RiskProfiles.TryGetValue(profileId, out var existing))
|
||||
{
|
||||
@@ -442,7 +459,7 @@ public static class GovernanceEndpoints
|
||||
RiskProfiles[profileId] = entity;
|
||||
|
||||
RecordAudit(tenant, actor, "risk_profile_deprecated", profileId, "risk_profile",
|
||||
$"Deprecated risk profile: {entity.Name} - {request.Reason}");
|
||||
$"Deprecated risk profile: {entity.Name} - {request.Reason}", timeProvider, guidProvider);
|
||||
|
||||
return Task.FromResult(Results.Ok(MapProfileToResponse(entity)));
|
||||
}
|
||||
@@ -542,7 +559,7 @@ public static class GovernanceEndpoints
|
||||
{
|
||||
if (RiskProfiles.IsEmpty)
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow.ToString("O");
|
||||
var now = TimeProvider.System.GetUtcNow().ToString("O", System.Globalization.CultureInfo.InvariantCulture);
|
||||
RiskProfiles["profile-default"] = new RiskProfileEntity
|
||||
{
|
||||
Id = "profile-default",
|
||||
@@ -582,15 +599,15 @@ public static class GovernanceEndpoints
|
||||
?? httpContext.Request.Headers["X-StellaOps-Actor"].FirstOrDefault();
|
||||
}
|
||||
|
||||
private static void RecordAudit(string tenantId, string actor, string eventType, string targetId, string targetType, string summary)
|
||||
private static void RecordAudit(string tenantId, string actor, string eventType, string targetId, string targetType, string summary, TimeProvider timeProvider, IGuidProvider guidProvider)
|
||||
{
|
||||
var id = $"audit-{Guid.NewGuid():N}";
|
||||
var id = $"audit-{guidProvider.NewGuid():N}";
|
||||
AuditEntries[id] = new GovernanceAuditEntry
|
||||
{
|
||||
Id = id,
|
||||
TenantId = tenantId,
|
||||
Type = eventType,
|
||||
Timestamp = DateTimeOffset.UtcNow.ToString("O"),
|
||||
Timestamp = timeProvider.GetUtcNow().ToString("O", System.Globalization.CultureInfo.InvariantCulture),
|
||||
Actor = actor,
|
||||
ActorType = "user",
|
||||
TargetResource = targetId,
|
||||
|
||||
@@ -50,6 +50,7 @@ internal static class RegistryWebhookEndpoints
|
||||
private static async Task<Results<Accepted<WebhookAcceptedResponse>, ProblemHttpResult>> HandleDockerRegistryWebhook(
|
||||
[FromBody] DockerRegistryNotification notification,
|
||||
IGateEvaluationQueue evaluationQueue,
|
||||
TimeProvider timeProvider,
|
||||
ILogger<RegistryWebhookEndpointMarker> logger,
|
||||
CancellationToken ct)
|
||||
{
|
||||
@@ -77,7 +78,7 @@ internal static class RegistryWebhookEndpoints
|
||||
Tag = evt.Target.Tag,
|
||||
RegistryUrl = evt.Request?.Host,
|
||||
Source = "docker-registry",
|
||||
Timestamp = evt.Timestamp ?? DateTimeOffset.UtcNow
|
||||
Timestamp = evt.Timestamp ?? timeProvider.GetUtcNow()
|
||||
}, ct);
|
||||
|
||||
jobs.Add(jobId);
|
||||
@@ -100,6 +101,7 @@ internal static class RegistryWebhookEndpoints
|
||||
private static async Task<Results<Accepted<WebhookAcceptedResponse>, ProblemHttpResult>> HandleHarborWebhook(
|
||||
[FromBody] HarborWebhookEvent notification,
|
||||
IGateEvaluationQueue evaluationQueue,
|
||||
TimeProvider timeProvider,
|
||||
ILogger<RegistryWebhookEndpointMarker> logger,
|
||||
CancellationToken ct)
|
||||
{
|
||||
@@ -136,7 +138,7 @@ internal static class RegistryWebhookEndpoints
|
||||
Tag = resource.Tag,
|
||||
RegistryUrl = notification.EventData.Repository?.RepoFullName,
|
||||
Source = "harbor",
|
||||
Timestamp = notification.OccurAt ?? DateTimeOffset.UtcNow
|
||||
Timestamp = notification.OccurAt ?? timeProvider.GetUtcNow()
|
||||
}, ct);
|
||||
|
||||
jobs.Add(jobId);
|
||||
@@ -159,6 +161,7 @@ internal static class RegistryWebhookEndpoints
|
||||
private static async Task<Results<Accepted<WebhookAcceptedResponse>, ProblemHttpResult>> HandleGenericWebhook(
|
||||
[FromBody] GenericRegistryWebhook notification,
|
||||
IGateEvaluationQueue evaluationQueue,
|
||||
TimeProvider timeProvider,
|
||||
ILogger<RegistryWebhookEndpointMarker> logger,
|
||||
CancellationToken ct)
|
||||
{
|
||||
@@ -177,7 +180,7 @@ internal static class RegistryWebhookEndpoints
|
||||
RegistryUrl = notification.RegistryUrl,
|
||||
BaselineRef = notification.BaselineRef,
|
||||
Source = notification.Source ?? "generic",
|
||||
Timestamp = DateTimeOffset.UtcNow
|
||||
Timestamp = timeProvider.GetUtcNow()
|
||||
}, ct);
|
||||
|
||||
logger.LogInformation(
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
using System.Collections.Immutable;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StellaOps.Determinism.Abstractions;
|
||||
using StellaOps.Policy.Exceptions.Models;
|
||||
using StellaOps.Policy.Exceptions.Repositories;
|
||||
|
||||
@@ -21,6 +22,7 @@ public sealed class ExceptionService : IExceptionService
|
||||
private readonly IExceptionRepository _repository;
|
||||
private readonly IExceptionNotificationService _notificationService;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
private readonly IGuidProvider _guidProvider;
|
||||
private readonly ILogger<ExceptionService> _logger;
|
||||
|
||||
/// <summary>
|
||||
@@ -30,11 +32,13 @@ public sealed class ExceptionService : IExceptionService
|
||||
IExceptionRepository repository,
|
||||
IExceptionNotificationService notificationService,
|
||||
TimeProvider timeProvider,
|
||||
IGuidProvider guidProvider,
|
||||
ILogger<ExceptionService> logger)
|
||||
{
|
||||
_repository = repository;
|
||||
_notificationService = notificationService;
|
||||
_timeProvider = timeProvider;
|
||||
_guidProvider = guidProvider;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
@@ -537,10 +541,10 @@ public sealed class ExceptionService : IExceptionService
|
||||
id.StartsWith("GO-", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static string GenerateExceptionId()
|
||||
private string GenerateExceptionId()
|
||||
{
|
||||
// Format: EXC-{random alphanumeric}
|
||||
return $"EXC-{Guid.NewGuid():N}"[..20];
|
||||
return $"EXC-{_guidProvider.NewGuid():N}"[..20];
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -5,9 +5,11 @@
|
||||
// Description: In-memory queue for gate evaluation jobs with background processing
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System.Globalization;
|
||||
using System.Threading.Channels;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StellaOps.Determinism.Abstractions;
|
||||
using StellaOps.Policy.Engine.Gates;
|
||||
using StellaOps.Policy.Gateway.Endpoints;
|
||||
|
||||
@@ -21,11 +23,15 @@ public sealed class InMemoryGateEvaluationQueue : IGateEvaluationQueue
|
||||
{
|
||||
private readonly Channel<GateEvaluationJob> _channel;
|
||||
private readonly ILogger<InMemoryGateEvaluationQueue> _logger;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
private readonly IGuidProvider _guidProvider;
|
||||
|
||||
public InMemoryGateEvaluationQueue(ILogger<InMemoryGateEvaluationQueue> logger)
|
||||
public InMemoryGateEvaluationQueue(ILogger<InMemoryGateEvaluationQueue> logger, TimeProvider timeProvider, IGuidProvider guidProvider)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(logger);
|
||||
_logger = logger;
|
||||
_timeProvider = timeProvider;
|
||||
_guidProvider = guidProvider;
|
||||
|
||||
// Bounded channel to prevent unbounded memory growth
|
||||
_channel = Channel.CreateBounded<GateEvaluationJob>(new BoundedChannelOptions(1000)
|
||||
@@ -46,7 +52,7 @@ public sealed class InMemoryGateEvaluationQueue : IGateEvaluationQueue
|
||||
{
|
||||
JobId = jobId,
|
||||
Request = request,
|
||||
QueuedAt = DateTimeOffset.UtcNow
|
||||
QueuedAt = _timeProvider.GetUtcNow()
|
||||
};
|
||||
|
||||
await _channel.Writer.WriteAsync(job, cancellationToken).ConfigureAwait(false);
|
||||
@@ -65,11 +71,11 @@ public sealed class InMemoryGateEvaluationQueue : IGateEvaluationQueue
|
||||
/// </summary>
|
||||
public ChannelReader<GateEvaluationJob> Reader => _channel.Reader;
|
||||
|
||||
private static string GenerateJobId()
|
||||
private string GenerateJobId()
|
||||
{
|
||||
// Format: gate-{timestamp}-{random}
|
||||
var timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||
var random = Guid.NewGuid().ToString("N")[..8];
|
||||
var timestamp = _timeProvider.GetUtcNow().ToUnixTimeMilliseconds().ToString(CultureInfo.InvariantCulture);
|
||||
var random = _guidProvider.NewGuid().ToString("N", CultureInfo.InvariantCulture)[..8];
|
||||
return $"gate-{timestamp}-{random}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using StellaOps.Determinism.Abstractions;
|
||||
using StellaOps.Policy.Gateway.Options;
|
||||
|
||||
namespace StellaOps.Policy.Gateway.Services;
|
||||
@@ -17,6 +18,7 @@ internal sealed class PolicyGatewayDpopProofGenerator : IDisposable
|
||||
private readonly IHostEnvironment hostEnvironment;
|
||||
private readonly IOptionsMonitor<PolicyGatewayOptions> optionsMonitor;
|
||||
private readonly TimeProvider timeProvider;
|
||||
private readonly IGuidProvider guidProvider;
|
||||
private readonly ILogger<PolicyGatewayDpopProofGenerator> logger;
|
||||
private DpopKeyMaterial? keyMaterial;
|
||||
private readonly object sync = new();
|
||||
@@ -25,11 +27,13 @@ internal sealed class PolicyGatewayDpopProofGenerator : IDisposable
|
||||
IHostEnvironment hostEnvironment,
|
||||
IOptionsMonitor<PolicyGatewayOptions> optionsMonitor,
|
||||
TimeProvider timeProvider,
|
||||
IGuidProvider guidProvider,
|
||||
ILogger<PolicyGatewayDpopProofGenerator> logger)
|
||||
{
|
||||
this.hostEnvironment = hostEnvironment ?? throw new ArgumentNullException(nameof(hostEnvironment));
|
||||
this.optionsMonitor = optionsMonitor ?? throw new ArgumentNullException(nameof(optionsMonitor));
|
||||
this.timeProvider = timeProvider ?? TimeProvider.System;
|
||||
this.guidProvider = guidProvider ?? throw new ArgumentNullException(nameof(guidProvider));
|
||||
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
@@ -85,7 +89,7 @@ internal sealed class PolicyGatewayDpopProofGenerator : IDisposable
|
||||
["htm"] = method.Method.ToUpperInvariant(),
|
||||
["htu"] = NormalizeTarget(targetUri),
|
||||
["iat"] = epochSeconds,
|
||||
["jti"] = Guid.NewGuid().ToString("N")
|
||||
["jti"] = guidProvider.NewGuid().ToString("N")
|
||||
};
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(accessToken))
|
||||
|
||||
Reference in New Issue
Block a user