107 lines
3.7 KiB
C#
107 lines
3.7 KiB
C#
|
|
using Microsoft.AspNetCore.SignalR;
|
||
|
|
using Microsoft.EntityFrameworkCore;
|
||
|
|
using Quartz;
|
||
|
|
using OTSSignsOrchestrator.Server.Data;
|
||
|
|
using OTSSignsOrchestrator.Server.Data.Entities;
|
||
|
|
using OTSSignsOrchestrator.Server.Hubs;
|
||
|
|
|
||
|
|
namespace OTSSignsOrchestrator.Server.Jobs;
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Quartz job that runs daily to check BYOI certificate expiry dates across all enabled
|
||
|
|
/// ByoiConfig entries. Alerts at 60, 30, and 7 day thresholds via FleetHub and logs to AuditLog.
|
||
|
|
///
|
||
|
|
/// Severity escalation:
|
||
|
|
/// - > 7 days remaining → "Warning"
|
||
|
|
/// - ≤ 7 days remaining → "Critical"
|
||
|
|
/// </summary>
|
||
|
|
// IMMUTABLE AuditLog — this job only appends, never updates or deletes audit records.
|
||
|
|
[DisallowConcurrentExecution]
|
||
|
|
public sealed class ByoiCertExpiryJob : IJob
|
||
|
|
{
|
||
|
|
/// <summary>Alert thresholds in days. Alerts fire when remaining days ≤ threshold.</summary>
|
||
|
|
internal static readonly int[] AlertThresholdDays = [60, 30, 7];
|
||
|
|
|
||
|
|
/// <summary>Days at or below which severity escalates to "Critical".</summary>
|
||
|
|
internal const int CriticalThresholdDays = 7;
|
||
|
|
|
||
|
|
private readonly IServiceProvider _services;
|
||
|
|
private readonly ILogger<ByoiCertExpiryJob> _logger;
|
||
|
|
|
||
|
|
public ByoiCertExpiryJob(
|
||
|
|
IServiceProvider services,
|
||
|
|
ILogger<ByoiCertExpiryJob> logger)
|
||
|
|
{
|
||
|
|
_services = services;
|
||
|
|
_logger = logger;
|
||
|
|
}
|
||
|
|
|
||
|
|
public async Task Execute(IJobExecutionContext context)
|
||
|
|
{
|
||
|
|
await using var scope = _services.CreateAsyncScope();
|
||
|
|
var db = scope.ServiceProvider.GetRequiredService<OrchestratorDbContext>();
|
||
|
|
var hub = scope.ServiceProvider.GetRequiredService<IHubContext<FleetHub, IFleetClient>>();
|
||
|
|
|
||
|
|
var configs = await db.ByoiConfigs
|
||
|
|
.AsNoTracking()
|
||
|
|
.Include(b => b.Instance)
|
||
|
|
.ThenInclude(i => i.Customer)
|
||
|
|
.Where(b => b.Enabled)
|
||
|
|
.ToListAsync(context.CancellationToken);
|
||
|
|
|
||
|
|
foreach (var config in configs)
|
||
|
|
{
|
||
|
|
var daysRemaining = (config.CertExpiry - DateTime.UtcNow).TotalDays;
|
||
|
|
var abbrev = config.Instance.Customer.Abbreviation;
|
||
|
|
|
||
|
|
if (!ShouldAlert(daysRemaining))
|
||
|
|
continue;
|
||
|
|
|
||
|
|
var severity = GetSeverity(daysRemaining);
|
||
|
|
var daysInt = (int)Math.Floor(daysRemaining);
|
||
|
|
var message = daysRemaining <= 0
|
||
|
|
? $"BYOI cert for {abbrev} has EXPIRED."
|
||
|
|
: $"BYOI cert for {abbrev} expires in {daysInt} days.";
|
||
|
|
|
||
|
|
_logger.LogWarning("BYOI cert expiry alert: {Severity} — {Message}", severity, message);
|
||
|
|
|
||
|
|
await hub.Clients.All.SendAlertRaised(severity, message);
|
||
|
|
|
||
|
|
// Append-only audit log
|
||
|
|
db.AuditLogs.Add(new AuditLog
|
||
|
|
{
|
||
|
|
Id = Guid.NewGuid(),
|
||
|
|
InstanceId = config.InstanceId,
|
||
|
|
Actor = "ByoiCertExpiryJob",
|
||
|
|
Action = "CertExpiryAlert",
|
||
|
|
Target = config.Slug,
|
||
|
|
Outcome = severity,
|
||
|
|
Detail = message,
|
||
|
|
OccurredAt = DateTime.UtcNow,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
await db.SaveChangesAsync(context.CancellationToken);
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Determines whether an alert should fire based on remaining days.
|
||
|
|
/// Alerts at ≤ 60, ≤ 30, ≤ 7 days (or already expired).
|
||
|
|
/// </summary>
|
||
|
|
internal static bool ShouldAlert(double daysRemaining)
|
||
|
|
{
|
||
|
|
foreach (var threshold in AlertThresholdDays)
|
||
|
|
{
|
||
|
|
if (daysRemaining <= threshold)
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Returns "Critical" when ≤ 7 days remain, otherwise "Warning".
|
||
|
|
/// </summary>
|
||
|
|
internal static string GetSeverity(double daysRemaining) =>
|
||
|
|
daysRemaining <= CriticalThresholdDays ? "Critical" : "Warning";
|
||
|
|
}
|