new two advisories and sprints work on them
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
using StellaOps.Doctor.Models;
|
||||
using StellaOps.Doctor.Plugins;
|
||||
using StellaOps.Doctor.Plugins.Attestation.Configuration;
|
||||
using StellaOps.Doctor.Plugins.Builders;
|
||||
|
||||
namespace StellaOps.Doctor.Plugins.Attestation.Checks;
|
||||
|
||||
/// <summary>
|
||||
/// Verifies system clock is within acceptable range for signature verification.
|
||||
/// </summary>
|
||||
public sealed class ClockSkewCheck : AttestationCheckBase
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override string CheckId => "check.attestation.clock.skew";
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string Name => "Clock Skew Sanity";
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string Description => "Verifies system clock is synchronized within acceptable range for signature verification";
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DoctorSeverity DefaultSeverity => DoctorSeverity.Warn;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IReadOnlyList<string> Tags => ["quick", "attestation", "security", "time"];
|
||||
|
||||
/// <inheritdoc />
|
||||
public override TimeSpan EstimatedDuration => TimeSpan.FromSeconds(3);
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task<DoctorCheckResult> ExecuteCheckAsync(
|
||||
DoctorPluginContext context,
|
||||
AttestationPluginOptions options,
|
||||
CheckResultBuilder result,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var localTime = context.TimeProvider.GetUtcNow();
|
||||
TimeSpan? skew = null;
|
||||
string? referenceSource = null;
|
||||
DateTimeOffset? referenceTime = null;
|
||||
|
||||
// Try to get reference time from Rekor if available
|
||||
if (options.Mode != AttestationMode.Offline && !string.IsNullOrEmpty(options.RekorUrl))
|
||||
{
|
||||
try
|
||||
{
|
||||
using var httpClient = CreateHttpClient(options);
|
||||
var response = await httpClient.GetAsync($"{options.RekorUrl.TrimEnd('/')}/api/v1/log", ct);
|
||||
|
||||
if (response.IsSuccessStatusCode && response.Headers.Date.HasValue)
|
||||
{
|
||||
referenceTime = response.Headers.Date.Value;
|
||||
skew = localTime - referenceTime.Value;
|
||||
referenceSource = "Rekor server";
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Rekor unavailable, try alternative methods
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to well-known time endpoint if Rekor failed
|
||||
if (skew is null)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var httpClient = CreateHttpClient(options);
|
||||
var response = await httpClient.GetAsync("https://www.google.com/", ct);
|
||||
|
||||
if (response.Headers.Date.HasValue)
|
||||
{
|
||||
referenceTime = response.Headers.Date.Value;
|
||||
skew = localTime - referenceTime.Value;
|
||||
referenceSource = "HTTP Date header (google.com)";
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Network unavailable
|
||||
}
|
||||
}
|
||||
|
||||
// If we couldn't get a reference time, check against a reasonable expectation
|
||||
if (skew is null)
|
||||
{
|
||||
// In offline mode or network failure, we can only warn that we couldn't verify
|
||||
return result
|
||||
.Info("Clock skew could not be verified (no reference time source available)")
|
||||
.WithEvidence("Time check", e => e
|
||||
.Add("LocalTime", localTime.ToString("O"))
|
||||
.Add("ReferenceSource", "(none)")
|
||||
.Add("Mode", options.Mode.ToString())
|
||||
.Add("Note", "Clock skew verification skipped - no network reference available"))
|
||||
.WithRemediation(r => r
|
||||
.AddShellStep(1, "Check system time", GetTimeCheckCommand())
|
||||
.AddManualStep(2, "Configure NTP", "Ensure NTP is configured for time synchronization"))
|
||||
.Build();
|
||||
}
|
||||
|
||||
var skewSeconds = Math.Abs(skew.Value.TotalSeconds);
|
||||
|
||||
// Evaluate against thresholds
|
||||
if (skewSeconds > options.ClockSkewFailThresholdSeconds)
|
||||
{
|
||||
return result
|
||||
.Fail($"System clock is off by {skewSeconds:F1} seconds (threshold: {options.ClockSkewFailThresholdSeconds}s)")
|
||||
.WithEvidence("Time comparison", e => e
|
||||
.Add("LocalTime", localTime.ToString("O"))
|
||||
.Add("ReferenceTime", referenceTime!.Value.ToString("O"))
|
||||
.Add("ReferenceSource", referenceSource!)
|
||||
.Add("SkewSeconds", skewSeconds.ToString("F1"))
|
||||
.Add("WarnThreshold", options.ClockSkewWarnThresholdSeconds.ToString())
|
||||
.Add("FailThreshold", options.ClockSkewFailThresholdSeconds.ToString()))
|
||||
.WithCauses(
|
||||
"System clock is not synchronized",
|
||||
"NTP service is not running",
|
||||
"NTP server is unreachable",
|
||||
"Hardware clock is misconfigured")
|
||||
.WithRemediation(r => r
|
||||
.AddShellStep(1, "Check current time", GetTimeCheckCommand())
|
||||
.AddShellStep(2, "Force NTP sync", GetNtpSyncCommand())
|
||||
.AddManualStep(3, "Configure NTP", "Ensure NTP is properly configured and the NTP service is running"))
|
||||
.WithVerification($"stella doctor --check {CheckId}")
|
||||
.Build();
|
||||
}
|
||||
|
||||
if (skewSeconds > options.ClockSkewWarnThresholdSeconds)
|
||||
{
|
||||
return result
|
||||
.Warn($"System clock is off by {skewSeconds:F1} seconds (threshold: {options.ClockSkewWarnThresholdSeconds}s)")
|
||||
.WithEvidence("Time comparison", e => e
|
||||
.Add("LocalTime", localTime.ToString("O"))
|
||||
.Add("ReferenceTime", referenceTime!.Value.ToString("O"))
|
||||
.Add("ReferenceSource", referenceSource!)
|
||||
.Add("SkewSeconds", skewSeconds.ToString("F1"))
|
||||
.Add("WarnThreshold", options.ClockSkewWarnThresholdSeconds.ToString())
|
||||
.Add("FailThreshold", options.ClockSkewFailThresholdSeconds.ToString()))
|
||||
.WithCauses(
|
||||
"NTP synchronization drift",
|
||||
"Infrequent NTP sync interval")
|
||||
.WithRemediation(r => r
|
||||
.AddShellStep(1, "Check NTP status", GetNtpStatusCommand())
|
||||
.AddShellStep(2, "Force NTP sync", GetNtpSyncCommand()))
|
||||
.WithVerification($"stella doctor --check {CheckId}")
|
||||
.Build();
|
||||
}
|
||||
|
||||
return result
|
||||
.Pass($"System clock synchronized (skew: {skewSeconds:F1}s)")
|
||||
.WithEvidence("Time comparison", e => e
|
||||
.Add("LocalTime", localTime.ToString("O"))
|
||||
.Add("ReferenceTime", referenceTime!.Value.ToString("O"))
|
||||
.Add("ReferenceSource", referenceSource!)
|
||||
.Add("SkewSeconds", skewSeconds.ToString("F1"))
|
||||
.Add("WarnThreshold", options.ClockSkewWarnThresholdSeconds.ToString()))
|
||||
.Build();
|
||||
}
|
||||
|
||||
private static string GetTimeCheckCommand()
|
||||
{
|
||||
return OperatingSystem.IsWindows()
|
||||
? "w32tm /query /status"
|
||||
: "timedatectl status";
|
||||
}
|
||||
|
||||
private static string GetNtpSyncCommand()
|
||||
{
|
||||
return OperatingSystem.IsWindows()
|
||||
? "w32tm /resync"
|
||||
: "sudo systemctl restart systemd-timesyncd || sudo ntpdate -u pool.ntp.org";
|
||||
}
|
||||
|
||||
private static string GetNtpStatusCommand()
|
||||
{
|
||||
return OperatingSystem.IsWindows()
|
||||
? "w32tm /query /peers"
|
||||
: "timedatectl timesync-status || ntpq -p";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user