sln build fix (again), tests fixes, audit work and doctors work

This commit is contained in:
master
2026-01-12 22:15:51 +02:00
parent 9873f80830
commit 9330c64349
812 changed files with 48051 additions and 3891 deletions

View File

@@ -0,0 +1,166 @@
using System.Globalization;
using System.Security.Cryptography.X509Certificates;
using Microsoft.Extensions.Configuration;
using StellaOps.Doctor.Models;
using StellaOps.Doctor.Plugins;
namespace StellaOps.Doctor.Plugins.Security.Checks;
/// <summary>
/// Validates TLS certificate configuration and expiration.
/// </summary>
public sealed class TlsCertificateCheck : IDoctorCheck
{
/// <inheritdoc />
public string CheckId => "check.security.tls.certificate";
/// <inheritdoc />
public string Name => "TLS Certificate";
/// <inheritdoc />
public string Description => "Validates TLS certificate validity and expiration";
/// <inheritdoc />
public DoctorSeverity DefaultSeverity => DoctorSeverity.Fail;
/// <inheritdoc />
public IReadOnlyList<string> Tags => ["security", "tls", "certificate"];
/// <inheritdoc />
public TimeSpan EstimatedDuration => TimeSpan.FromSeconds(5);
/// <inheritdoc />
public bool CanRun(DoctorPluginContext context)
{
var certPath = context.Configuration.GetValue<string>("Tls:CertificatePath")
?? context.Configuration.GetValue<string>("Kestrel:Certificates:Default:Path");
return !string.IsNullOrWhiteSpace(certPath);
}
/// <inheritdoc />
public Task<DoctorCheckResult> RunAsync(DoctorPluginContext context, CancellationToken ct)
{
var result = context.CreateResult(CheckId, "stellaops.doctor.security", DoctorCategory.Security.ToString());
var certPath = context.Configuration.GetValue<string>("Tls:CertificatePath")
?? context.Configuration.GetValue<string>("Kestrel:Certificates:Default:Path");
if (string.IsNullOrWhiteSpace(certPath))
{
return Task.FromResult(result
.Skip("TLS certificate path not configured")
.WithEvidence("Configuration", e => e.Add("CertificatePath", "(not set)"))
.Build());
}
if (!File.Exists(certPath))
{
return Task.FromResult(result
.Fail($"TLS certificate file not found: {certPath}")
.WithEvidence("TLS configuration", e =>
{
e.Add("CertificatePath", certPath);
e.Add("FileExists", "false");
})
.WithCauses("Certificate file path is incorrect", "Certificate file was deleted")
.WithRemediation(r => r
.AddManualStep(1, "Verify path", "Check Tls:CertificatePath configuration")
.AddManualStep(2, "Generate certificate", "Generate or obtain a valid TLS certificate"))
.WithVerification("stella doctor --check check.security.tls.certificate")
.Build());
}
try
{
var certPassword = context.Configuration.GetValue<string>("Tls:CertificatePassword")
?? context.Configuration.GetValue<string>("Kestrel:Certificates:Default:Password");
using var cert = string.IsNullOrEmpty(certPassword)
? X509CertificateLoader.LoadCertificateFromFile(certPath)
: X509CertificateLoader.LoadPkcs12FromFile(certPath, certPassword);
var now = context.TimeProvider.GetUtcNow();
var daysUntilExpiry = (cert.NotAfter - now.DateTime).TotalDays;
if (now.DateTime < cert.NotBefore)
{
return Task.FromResult(result
.Fail("TLS certificate is not yet valid")
.WithEvidence("TLS certificate", e =>
{
e.Add("Subject", cert.Subject);
e.Add("Issuer", cert.Issuer);
e.Add("NotBefore", cert.NotBefore.ToString("O", CultureInfo.InvariantCulture));
e.Add("NotAfter", cert.NotAfter.ToString("O", CultureInfo.InvariantCulture));
})
.WithCauses("Certificate validity period has not started")
.Build());
}
if (now.DateTime > cert.NotAfter)
{
return Task.FromResult(result
.Fail("TLS certificate has expired")
.WithEvidence("TLS certificate", e =>
{
e.Add("Subject", cert.Subject);
e.Add("Issuer", cert.Issuer);
e.Add("ExpiredOn", cert.NotAfter.ToString("O", CultureInfo.InvariantCulture));
e.Add("DaysExpired", Math.Abs(daysUntilExpiry).ToString("F0", CultureInfo.InvariantCulture));
})
.WithCauses("Certificate has exceeded its validity period")
.WithRemediation(r => r
.AddManualStep(1, "Renew certificate", "Obtain a new TLS certificate")
.AddManualStep(2, "Update configuration", "Update Tls:CertificatePath with new certificate"))
.WithVerification("stella doctor --check check.security.tls.certificate")
.Build());
}
if (daysUntilExpiry < 30)
{
return Task.FromResult(result
.Warn($"TLS certificate expires in {daysUntilExpiry:F0} days")
.WithEvidence("TLS certificate", e =>
{
e.Add("Subject", cert.Subject);
e.Add("Issuer", cert.Issuer);
e.Add("NotAfter", cert.NotAfter.ToString("O", CultureInfo.InvariantCulture));
e.Add("DaysUntilExpiry", daysUntilExpiry.ToString("F0", CultureInfo.InvariantCulture));
})
.WithCauses("Certificate is approaching expiration")
.WithRemediation(r => r
.AddManualStep(1, "Plan renewal", "Schedule certificate renewal before expiration"))
.Build());
}
return Task.FromResult(result
.Pass($"TLS certificate valid for {daysUntilExpiry:F0} days")
.WithEvidence("TLS certificate", e =>
{
e.Add("Subject", cert.Subject);
e.Add("Issuer", cert.Issuer);
e.Add("NotBefore", cert.NotBefore.ToString("O", CultureInfo.InvariantCulture));
e.Add("NotAfter", cert.NotAfter.ToString("O", CultureInfo.InvariantCulture));
e.Add("DaysUntilExpiry", daysUntilExpiry.ToString("F0", CultureInfo.InvariantCulture));
e.Add("Thumbprint", cert.Thumbprint);
})
.Build());
}
catch (Exception ex)
{
return Task.FromResult(result
.Fail($"Failed to load TLS certificate: {ex.Message}")
.WithEvidence("TLS configuration", e =>
{
e.Add("CertificatePath", certPath);
e.Add("ErrorType", ex.GetType().Name);
e.Add("Error", ex.Message);
})
.WithCauses(
"Certificate file is corrupted",
"Certificate password is incorrect",
"Certificate format not supported")
.Build());
}
}
}