new two advisories and sprints work on them
This commit is contained in:
@@ -0,0 +1,214 @@
|
||||
using StellaOps.Doctor.Models;
|
||||
using StellaOps.Doctor.Plugins;
|
||||
using StellaOps.Doctor.Plugins.Builders;
|
||||
using StellaOps.Doctor.Plugins.Verification.Configuration;
|
||||
|
||||
namespace StellaOps.Doctor.Plugins.Verification.Checks;
|
||||
|
||||
/// <summary>
|
||||
/// Verifies signature and attestations for test artifact.
|
||||
/// </summary>
|
||||
public sealed class SignatureVerificationCheck : VerificationCheckBase
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override string CheckId => "check.verification.signature";
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string Name => "Signature Verification";
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string Description => "Verifies signature and attestations for test artifact (DSSE in Rekor or offline bundle)";
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IReadOnlyList<string> Tags => ["verification", "signature", "dsse", "attestation", "security"];
|
||||
|
||||
/// <inheritdoc />
|
||||
public override TimeSpan EstimatedDuration => TimeSpan.FromSeconds(10);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanRun(DoctorPluginContext context)
|
||||
{
|
||||
if (!base.CanRun(context))
|
||||
return false;
|
||||
|
||||
var options = VerificationPlugin.GetOptions(context);
|
||||
return HasTestArtifactConfigured(options);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task<DoctorCheckResult> ExecuteCheckAsync(
|
||||
DoctorPluginContext context,
|
||||
VerificationPluginOptions options,
|
||||
CheckResultBuilder result,
|
||||
CancellationToken ct)
|
||||
{
|
||||
if (!HasTestArtifactConfigured(options))
|
||||
{
|
||||
return GetNoTestArtifactConfiguredResult(result, CheckId);
|
||||
}
|
||||
|
||||
// Check for offline bundle
|
||||
if (!string.IsNullOrEmpty(options.TestArtifact.OfflineBundlePath))
|
||||
{
|
||||
return await VerifyFromOfflineBundle(options, result, ct);
|
||||
}
|
||||
|
||||
// Online verification
|
||||
return await VerifyFromOnline(context, options, result, ct);
|
||||
}
|
||||
|
||||
private static Task<DoctorCheckResult> VerifyFromOfflineBundle(
|
||||
VerificationPluginOptions options,
|
||||
CheckResultBuilder result,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var bundlePath = options.TestArtifact.OfflineBundlePath!;
|
||||
|
||||
if (!File.Exists(bundlePath))
|
||||
{
|
||||
return Task.FromResult(result
|
||||
.Fail($"Offline bundle not found: {bundlePath}")
|
||||
.WithEvidence("Verification", e => e
|
||||
.Add("Mode", "Offline")
|
||||
.Add("BundlePath", bundlePath)
|
||||
.Add("FileExists", "false"))
|
||||
.WithRemediation(r => r
|
||||
.AddShellStep(1, "Export bundle", "stella verification bundle export --output " + bundlePath))
|
||||
.WithVerification($"stella doctor --check check.verification.signature")
|
||||
.Build());
|
||||
}
|
||||
|
||||
// In a real implementation, we would parse the bundle and verify signatures
|
||||
// For doctor check, we verify the bundle structure contains signature data
|
||||
|
||||
try
|
||||
{
|
||||
var content = File.ReadAllText(bundlePath);
|
||||
|
||||
// Check for signature indicators in the bundle
|
||||
var hasSignatures = content.Contains("\"signatures\"", StringComparison.OrdinalIgnoreCase)
|
||||
|| content.Contains("\"payloadType\"", StringComparison.OrdinalIgnoreCase)
|
||||
|| content.Contains("\"dsse\"", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if (!hasSignatures)
|
||||
{
|
||||
return Task.FromResult(result
|
||||
.Warn("Offline bundle may not contain signature data")
|
||||
.WithEvidence("Verification", e => e
|
||||
.Add("Mode", "Offline")
|
||||
.Add("BundlePath", bundlePath)
|
||||
.Add("SignatureDataFound", "false")
|
||||
.Add("Note", "Bundle should contain DSSE signatures for verification"))
|
||||
.WithRemediation(r => r
|
||||
.AddShellStep(1, "Re-export with signatures", "stella verification bundle export --include-signatures --output " + bundlePath))
|
||||
.WithVerification($"stella doctor --check check.verification.signature")
|
||||
.Build());
|
||||
}
|
||||
|
||||
return Task.FromResult(result
|
||||
.Pass("Offline bundle contains signature data")
|
||||
.WithEvidence("Verification", e => e
|
||||
.Add("Mode", "Offline")
|
||||
.Add("BundlePath", bundlePath)
|
||||
.Add("SignatureDataFound", "true")
|
||||
.Add("Note", "Full signature verification requires runtime attestor service"))
|
||||
.Build());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Task.FromResult(result
|
||||
.Fail($"Cannot read offline bundle: {ex.Message}")
|
||||
.WithEvidence("Verification", e => e
|
||||
.Add("Mode", "Offline")
|
||||
.Add("BundlePath", bundlePath)
|
||||
.Add("Error", ex.Message))
|
||||
.Build());
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<DoctorCheckResult> VerifyFromOnline(
|
||||
DoctorPluginContext context,
|
||||
VerificationPluginOptions options,
|
||||
CheckResultBuilder result,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var reference = options.TestArtifact.Reference!;
|
||||
var rekorUrl = context.Configuration["Sigstore:RekorUrl"] ?? "https://rekor.sigstore.dev";
|
||||
|
||||
// Note: Full signature verification requires the Attestor service
|
||||
// For doctor check, we verify that the infrastructure is in place
|
||||
|
||||
// Check if Sigstore is enabled
|
||||
var sigstoreEnabled = context.Configuration.GetValue<bool>("Sigstore:Enabled");
|
||||
|
||||
if (!sigstoreEnabled)
|
||||
{
|
||||
return result
|
||||
.Info("Signature verification skipped - Sigstore not enabled")
|
||||
.WithEvidence("Verification", e => e
|
||||
.Add("Mode", "Online")
|
||||
.Add("SigstoreEnabled", "false")
|
||||
.Add("Reference", reference)
|
||||
.Add("Note", "Enable Sigstore to verify artifact signatures"))
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Enable Sigstore", "Set Sigstore:Enabled to true")
|
||||
.AddManualStep(2, "Configure signing", "Set up signing keys or keyless mode"))
|
||||
.Build();
|
||||
}
|
||||
|
||||
// Check if Rekor is reachable (signature verification requires Rekor)
|
||||
using var httpClient = CreateHttpClient(options);
|
||||
|
||||
try
|
||||
{
|
||||
var rekorHealthUrl = $"{rekorUrl.TrimEnd('/')}/api/v1/log";
|
||||
var response = await httpClient.GetAsync(rekorHealthUrl, ct);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
return result
|
||||
.Fail($"Rekor transparency log unavailable ({(int)response.StatusCode})")
|
||||
.WithEvidence("Verification", e => e
|
||||
.Add("Mode", "Online")
|
||||
.Add("RekorUrl", rekorUrl)
|
||||
.Add("RekorStatus", ((int)response.StatusCode).ToString())
|
||||
.Add("Reference", reference))
|
||||
.WithCauses(
|
||||
"Rekor service is down",
|
||||
"Network connectivity issue")
|
||||
.WithRemediation(r => r
|
||||
.AddShellStep(1, "Test Rekor", $"curl -I {rekorHealthUrl}")
|
||||
.AddManualStep(2, "Or use offline mode", "Configure offline verification bundle"))
|
||||
.WithVerification($"stella doctor --check check.verification.signature")
|
||||
.Build();
|
||||
}
|
||||
|
||||
return result
|
||||
.Pass("Signature verification infrastructure available")
|
||||
.WithEvidence("Verification", e => e
|
||||
.Add("Mode", "Online")
|
||||
.Add("SigstoreEnabled", "true")
|
||||
.Add("RekorUrl", rekorUrl)
|
||||
.Add("RekorReachable", "true")
|
||||
.Add("Reference", reference)
|
||||
.Add("Note", "Full signature verification requires runtime attestor service"))
|
||||
.Build();
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
return result
|
||||
.Fail($"Cannot reach Rekor: {ex.Message}")
|
||||
.WithEvidence("Verification", e => e
|
||||
.Add("Mode", "Online")
|
||||
.Add("RekorUrl", rekorUrl)
|
||||
.Add("Error", ex.Message)
|
||||
.Add("Reference", reference))
|
||||
.WithCauses("Network connectivity issue")
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Check network", "Verify connectivity to Rekor")
|
||||
.AddManualStep(2, "Use offline mode", "Configure offline verification bundle"))
|
||||
.WithVerification($"stella doctor --check check.verification.signature")
|
||||
.Build();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user