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(); } } }