sln build fix (again), tests fixes, audit work and doctors work
This commit is contained in:
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user