Harden endpoint reachability resolution
This commit is contained in:
@@ -21,33 +21,77 @@ namespace BizTalkCheckmkPulse
|
||||
out EndpointCatalogEntry entry,
|
||||
out string reason)
|
||||
{
|
||||
entry = null;
|
||||
reason = string.Empty;
|
||||
var resolution = Analyze(candidate);
|
||||
entry = resolution.Entry;
|
||||
reason = resolution.Reason ?? string.Empty;
|
||||
return resolution.Status == EndpointResolutionStatus.Probeable;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Trennt sicher pruefbare Ziele von erwartbar lokalen/adapterinternen Adressen und echten Aufloesungsluecken.
|
||||
/// </summary>
|
||||
public static EndpointResolution Analyze(EndpointCandidate candidate)
|
||||
{
|
||||
if (candidate == null)
|
||||
{
|
||||
reason = "Endpoint-Kandidat fehlt.";
|
||||
return false;
|
||||
return Unresolved("Endpoint-Kandidat fehlt.");
|
||||
}
|
||||
|
||||
if (candidate.Dynamic)
|
||||
{
|
||||
reason = "Dynamischer Send Port besitzt kein statisch pruefbares Ziel.";
|
||||
return false;
|
||||
return Expected("Dynamischer Send Port besitzt kein statisch pruefbares Ziel.");
|
||||
}
|
||||
|
||||
var address = (candidate.Address ?? string.Empty).Trim();
|
||||
if (address.Length == 0)
|
||||
{
|
||||
reason = "Transportadresse ist leer.";
|
||||
return false;
|
||||
return Unresolved("Aktive statische Transportadresse ist leer.");
|
||||
}
|
||||
|
||||
if (LooksLikeEmailRecipients(address) || Contains(candidate.AdapterName, "SMTP"))
|
||||
{
|
||||
return Expected("SMTP-Empfaenger beziehungsweise Adapteradresse enthaelt nicht den SMTP-Server.");
|
||||
}
|
||||
|
||||
if (Regex.IsMatch(address, @"^[a-zA-Z]:[\\/]", RegexOptions.CultureInvariant)
|
||||
|| Contains(candidate.AdapterName, "FILE") && !address.StartsWith("\\\\", StringComparison.Ordinal))
|
||||
{
|
||||
return Expected("Lokaler Dateipfad benoetigt keine Netzwerkprobe.");
|
||||
}
|
||||
|
||||
if (address.StartsWith("net.pipe:", StringComparison.OrdinalIgnoreCase)
|
||||
|| address.StartsWith("npipe:", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return Expected("Named Pipes besitzen kein TCP-/UDP-Ziel.");
|
||||
}
|
||||
|
||||
if (address.StartsWith("/", StringComparison.Ordinal)
|
||||
&& IsInboundListener(candidate))
|
||||
{
|
||||
return Expected("Relative Receive-Listener-Adresse enthaelt keine eindeutige Site-Bindung.");
|
||||
}
|
||||
|
||||
EndpointCatalogEntry entry;
|
||||
string protocol;
|
||||
string host;
|
||||
int port;
|
||||
if (!TryResolve(address, candidate.AdapterName, out protocol, out host, out port, out reason))
|
||||
string reason;
|
||||
if (!TryResolve(
|
||||
address,
|
||||
candidate.AdapterName,
|
||||
IsInboundListener(candidate),
|
||||
out protocol,
|
||||
out host,
|
||||
out port,
|
||||
out reason))
|
||||
{
|
||||
return false;
|
||||
if (address.StartsWith("file:", StringComparison.OrdinalIgnoreCase)
|
||||
|| address.StartsWith("mailto:", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return Expected(reason);
|
||||
}
|
||||
|
||||
return Unresolved(reason);
|
||||
}
|
||||
|
||||
entry = new EndpointCatalogEntry
|
||||
@@ -64,7 +108,12 @@ namespace BizTalkCheckmkPulse
|
||||
Enabled = true,
|
||||
AutoDiscovered = true
|
||||
};
|
||||
return true;
|
||||
return new EndpointResolution
|
||||
{
|
||||
Status = EndpointResolutionStatus.Probeable,
|
||||
Entry = entry,
|
||||
Reason = string.Empty
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -72,52 +121,13 @@ namespace BizTalkCheckmkPulse
|
||||
/// </summary>
|
||||
public static bool IsPotentialExternalEndpoint(EndpointCandidate candidate)
|
||||
{
|
||||
if (candidate == null || candidate.Dynamic)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var address = (candidate.Address ?? string.Empty).Trim();
|
||||
if (address.Length == 0
|
||||
|| LooksLikeEmailRecipients(address)
|
||||
|| Regex.IsMatch(address, @"^[a-zA-Z]:[\\/]", RegexOptions.CultureInvariant)
|
||||
|| address.StartsWith("/", StringComparison.Ordinal)
|
||||
|| address.StartsWith("net.pipe:", StringComparison.OrdinalIgnoreCase)
|
||||
|| address.StartsWith("npipe:", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Contains(candidate.AdapterName, "SMTP")
|
||||
|| Contains(candidate.AdapterName, "FILE") && !address.StartsWith("\\\\", StringComparison.Ordinal))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (address.StartsWith("\\\\", StringComparison.Ordinal)
|
||||
|| HostPortPattern.IsMatch(address)
|
||||
|| Regex.IsMatch(address, @"(?:FORMATNAME:)?DIRECT=(?:OS|TCP):", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Uri uri;
|
||||
if (Uri.TryCreate(address, UriKind.Absolute, out uri))
|
||||
{
|
||||
return !string.Equals(uri.Scheme, "file", StringComparison.OrdinalIgnoreCase)
|
||||
&& !string.Equals(uri.Scheme, "mailto", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
return Contains(candidate.AdapterName, "SFTP")
|
||||
|| Contains(candidate.AdapterName, "FTP")
|
||||
|| Contains(candidate.AdapterName, "HTTP")
|
||||
|| Contains(candidate.AdapterName, "SOAP")
|
||||
|| Contains(candidate.AdapterName, "WCF");
|
||||
return Analyze(candidate).Status == EndpointResolutionStatus.Unresolved;
|
||||
}
|
||||
|
||||
private static bool TryResolve(
|
||||
string address,
|
||||
string adapterName,
|
||||
bool allowWildcardListener,
|
||||
out string protocol,
|
||||
out string host,
|
||||
out int port,
|
||||
@@ -128,6 +138,12 @@ namespace BizTalkCheckmkPulse
|
||||
port = 0;
|
||||
reason = string.Empty;
|
||||
|
||||
if (allowWildcardListener
|
||||
&& TryResolveWildcardListener(address, out protocol, out host, out port, out reason))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (LooksLikeEmailRecipients(address))
|
||||
{
|
||||
reason = "SMTP-Empfaengerliste ist kein pruefbarer Netzwerk-Endpunkt.";
|
||||
@@ -210,14 +226,14 @@ namespace BizTalkCheckmkPulse
|
||||
|
||||
if (Contains(adapterName, "SFTP"))
|
||||
{
|
||||
host = address.Trim('/');
|
||||
host = ExtractBareAdapterHost(address);
|
||||
port = 22;
|
||||
return Validate(host, port, out reason);
|
||||
}
|
||||
|
||||
if (Contains(adapterName, "FTP"))
|
||||
{
|
||||
host = address.Trim('/');
|
||||
host = ExtractBareAdapterHost(address);
|
||||
port = 21;
|
||||
return Validate(host, port, out reason);
|
||||
}
|
||||
@@ -248,10 +264,91 @@ namespace BizTalkCheckmkPulse
|
||||
case "imap": return 143;
|
||||
case "imaps": return 993;
|
||||
case "msmq": return 1801;
|
||||
case "net.tcp": return 808;
|
||||
case "mssql": return 1433;
|
||||
// Ohne Pfad ist der Hostteil ein tnsnames.ora-Alias und kein sicher pruefbarer DNS-Host.
|
||||
case "oracledb": return uri.AbsolutePath.Trim('/').Length > 0 ? 1521 : 0;
|
||||
default: return uri.Port > 0 ? uri.Port : 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reduziert lokale HTTP-/WCF-Wildcard-Listener auf einen nebenwirkungsfreien Loopback-Porttest.
|
||||
/// </summary>
|
||||
private static bool TryResolveWildcardListener(
|
||||
string address,
|
||||
out string protocol,
|
||||
out string host,
|
||||
out int port,
|
||||
out string reason)
|
||||
{
|
||||
protocol = "TCP";
|
||||
host = string.Empty;
|
||||
port = 0;
|
||||
reason = string.Empty;
|
||||
var match = Regex.Match(
|
||||
address,
|
||||
@"^(?<scheme>https?|net\.tcp)://(?:\+|\*)(?::(?<port>\d{1,5}))?(?:[/\\].*)?$",
|
||||
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
||||
if (!match.Success)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var scheme = match.Groups["scheme"].Value.ToLowerInvariant();
|
||||
if (!string.IsNullOrWhiteSpace(match.Groups["port"].Value)
|
||||
&& !int.TryParse(match.Groups["port"].Value, out port))
|
||||
{
|
||||
port = 0;
|
||||
}
|
||||
|
||||
if (port <= 0)
|
||||
{
|
||||
port = scheme == "https" ? 443 : scheme == "net.tcp" ? 808 : 80;
|
||||
}
|
||||
|
||||
host = "127.0.0.1";
|
||||
return Validate(host, port, out reason);
|
||||
}
|
||||
|
||||
private static string ExtractBareAdapterHost(string address)
|
||||
{
|
||||
var value = (address ?? string.Empty).Trim().Trim('/');
|
||||
var separator = value.IndexOfAny(new[] { '/', '\\' });
|
||||
return separator < 0 ? value : value.Substring(0, separator);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Erkennt Adapter, deren relative Receive-Adresse eine lokale Listenerbindung statt eines Remoteziels beschreibt.
|
||||
/// </summary>
|
||||
private static bool IsInboundListener(EndpointCandidate candidate)
|
||||
{
|
||||
return string.Equals(candidate.ArtifactType, "ReceiveLocation", StringComparison.OrdinalIgnoreCase)
|
||||
&& (Contains(candidate.AdapterName, "HTTP")
|
||||
|| Contains(candidate.AdapterName, "SOAP")
|
||||
|| Contains(candidate.AdapterName, "WCF"));
|
||||
}
|
||||
|
||||
private static EndpointResolution Expected(string reason)
|
||||
{
|
||||
return new EndpointResolution
|
||||
{
|
||||
Status = EndpointResolutionStatus.ExpectedNonProbeable,
|
||||
Reason = reason
|
||||
};
|
||||
}
|
||||
|
||||
private static EndpointResolution Unresolved(string reason)
|
||||
{
|
||||
return new EndpointResolution
|
||||
{
|
||||
Status = EndpointResolutionStatus.Unresolved,
|
||||
Reason = string.IsNullOrWhiteSpace(reason)
|
||||
? "Transportadresse konnte nicht sicher auf Host und Port reduziert werden."
|
||||
: reason
|
||||
};
|
||||
}
|
||||
|
||||
private static bool HasExplicitPort(string address)
|
||||
{
|
||||
var authorityEnd = address.IndexOfAny(new[] { '/', '?' }, address.IndexOf("://", StringComparison.Ordinal) + 3);
|
||||
|
||||
Reference in New Issue
Block a user