using Microsoft.Extensions.Configuration;
using StellaOps.Doctor.Models;
using StellaOps.Doctor.Plugins;
using System.Globalization;
using System.Net.Sockets;
namespace StellaOps.Doctor.Plugins.Integration.Checks;
///
/// Verifies connectivity to LDAP/Active Directory servers.
///
public sealed class LdapConnectivityCheck : IDoctorCheck
{
///
public string CheckId => "check.integration.ldap";
///
public string Name => "LDAP/AD Connectivity";
///
public string Description => "Verifies connectivity to LDAP or Active Directory servers";
///
public DoctorSeverity DefaultSeverity => DoctorSeverity.Warn;
///
public IReadOnlyList Tags => ["connectivity", "ldap", "directory", "auth"];
///
public TimeSpan EstimatedDuration => TimeSpan.FromSeconds(5);
///
public bool CanRun(DoctorPluginContext context)
{
var host = context.Configuration.GetValue("Ldap:Host")
?? context.Configuration.GetValue("ActiveDirectory:Host")
?? context.Configuration.GetValue("Authority:Ldap:Host");
return !string.IsNullOrWhiteSpace(host);
}
///
public async Task RunAsync(DoctorPluginContext context, CancellationToken ct)
{
var result = context.CreateResult(CheckId, "stellaops.doctor.integration", DoctorCategory.Integration.ToString());
var host = context.Configuration.GetValue("Ldap:Host")
?? context.Configuration.GetValue("ActiveDirectory:Host")
?? context.Configuration.GetValue("Authority:Ldap:Host");
if (string.IsNullOrWhiteSpace(host))
{
return result
.Skip("LDAP not configured")
.WithEvidence("Configuration", e => e.Add("Ldap:Host", "(not set)"))
.Build();
}
var port = context.Configuration.GetValue("Ldap:Port")
?? context.Configuration.GetValue("ActiveDirectory:Port")
?? context.Configuration.GetValue("Authority:Ldap:Port")
?? 389;
var useSsl = context.Configuration.GetValue("Ldap:UseSsl")
?? context.Configuration.GetValue("ActiveDirectory:UseSsl")
?? false;
if (useSsl && port == 389)
{
port = 636;
}
try
{
using var client = new TcpClient();
var connectTask = client.ConnectAsync(host, port, ct);
var timeoutTask = Task.Delay(TimeSpan.FromSeconds(5), ct);
var completedTask = await Task.WhenAny(connectTask.AsTask(), timeoutTask);
if (completedTask == timeoutTask)
{
return result
.Fail($"Connection to LDAP server at {host}:{port} timed out")
.WithEvidence("LDAP connectivity", e =>
{
e.Add("Host", host);
e.Add("Port", port.ToString(CultureInfo.InvariantCulture));
e.Add("UseSsl", useSsl.ToString());
e.Add("Status", "timeout");
})
.WithCauses(
"LDAP server is not responding",
"Firewall blocking LDAP port",
"Network connectivity issues")
.WithRemediation(r => r
.AddManualStep(1, "Check LDAP server", "Verify LDAP server is running and accessible")
.AddManualStep(2, "Test connectivity", $"telnet {host} {port}")
.WithRunbookUrl("docs/doctor/articles/integration/integration-ldap.md"))
.WithVerification("stella doctor --check check.integration.ldap")
.Build();
}
await connectTask;
if (client.Connected)
{
return result
.Pass($"LDAP server reachable at {host}:{port}")
.WithEvidence("LDAP connectivity", e =>
{
e.Add("Host", host);
e.Add("Port", port.ToString(CultureInfo.InvariantCulture));
e.Add("UseSsl", useSsl.ToString());
e.Add("Status", "connected");
})
.Build();
}
return result
.Fail($"Failed to connect to LDAP server at {host}:{port}")
.WithEvidence("LDAP connectivity", e =>
{
e.Add("Host", host);
e.Add("Port", port.ToString(CultureInfo.InvariantCulture));
e.Add("Status", "connection_failed");
})
.Build();
}
catch (SocketException ex)
{
return result
.Fail($"Socket error connecting to LDAP: {ex.Message}")
.WithEvidence("LDAP connectivity", e =>
{
e.Add("Host", host);
e.Add("Port", port.ToString(CultureInfo.InvariantCulture));
e.Add("SocketErrorCode", ex.SocketErrorCode.ToString());
e.Add("Error", ex.Message);
})
.WithCauses(
"LDAP server is not running",
"DNS resolution failed",
"Network unreachable")
.WithRemediation(r => r
.AddManualStep(1, "Check LDAP configuration", "Verify Ldap:Host and Ldap:Port settings")
.AddManualStep(2, "Check DNS", $"nslookup {host}")
.WithRunbookUrl("docs/doctor/articles/integration/integration-ldap.md"))
.WithVerification("stella doctor --check check.integration.ldap")
.Build();
}
catch (Exception ex) when (ex is not OperationCanceledException)
{
return result
.Fail($"Error connecting to LDAP: {ex.Message}")
.WithEvidence("LDAP connectivity", e =>
{
e.Add("Host", host);
e.Add("Port", port.ToString(CultureInfo.InvariantCulture));
e.Add("ErrorType", ex.GetType().Name);
e.Add("Error", ex.Message);
})
.Build();
}
}
}