Add live TLS certificate probing and improve restart error handling.
Configure CertificateCheckUrl per container for curl-like TLS checks, classify Sonic permission errors, and extend setup wizard for container management. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -34,7 +34,20 @@ public sealed class CertificateProbeService
|
||||
+ string.Join(", ", SupportedExtensions));
|
||||
}
|
||||
|
||||
if (!File.Exists(normalized))
|
||||
PathReachability reachability = AssessPathReachability(normalized);
|
||||
|
||||
if (reachability == PathReachability.AccessDenied)
|
||||
{
|
||||
return CertificateProbeResult.ForAccessDenied(normalized);
|
||||
}
|
||||
|
||||
if (reachability == PathReachability.Unreachable)
|
||||
{
|
||||
return CertificateProbeResult.Failed(
|
||||
"Pfad nicht erreichbar.");
|
||||
}
|
||||
|
||||
if (reachability == PathReachability.FileMissing)
|
||||
{
|
||||
return CertificateProbeResult.Missing(
|
||||
normalized,
|
||||
@@ -72,6 +85,14 @@ public sealed class CertificateProbeService
|
||||
"Das Kennwort ist falsch oder die Datei ist beschädigt.");
|
||||
}
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
return CertificateProbeResult.ForAccessDenied(normalized);
|
||||
}
|
||||
catch (IOException ex) when (IsAccessOrLogonFailure(ex))
|
||||
{
|
||||
return CertificateProbeResult.ForAccessDenied(normalized);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return CertificateProbeResult.Failed(
|
||||
@@ -151,6 +172,154 @@ public sealed class CertificateProbeService
|
||||
|| extension.Equals(".p12", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private enum PathReachability
|
||||
{
|
||||
FileExists,
|
||||
FileMissing,
|
||||
AccessDenied,
|
||||
Unreachable
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unterscheidet „Datei fehlt“ von „Share/Pfad ohne Admin-Konto nicht erreichbar“.
|
||||
/// File.Exists liefert bei fehlenden Credentials oft nur false.
|
||||
/// </summary>
|
||||
private static PathReachability AssessPathReachability(string filePath)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
return PathReachability.FileExists;
|
||||
}
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
return PathReachability.AccessDenied;
|
||||
}
|
||||
catch (IOException ex) when (IsAccessOrLogonFailure(ex))
|
||||
{
|
||||
return PathReachability.AccessDenied;
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
return PathReachability.Unreachable;
|
||||
}
|
||||
|
||||
string? directory = Path.GetDirectoryName(filePath);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(directory))
|
||||
{
|
||||
return PathReachability.Unreachable;
|
||||
}
|
||||
|
||||
return AssessDirectoryReachability(directory);
|
||||
}
|
||||
|
||||
private static PathReachability AssessDirectoryReachability(string directory)
|
||||
{
|
||||
string current = directory.TrimEnd('\\', '/');
|
||||
|
||||
while (!string.IsNullOrWhiteSpace(current))
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Directory.Exists(current))
|
||||
{
|
||||
// Exists kann bei UNC ohne Rechte „lügen“ – Auflisten erzwingen.
|
||||
_ = Directory.EnumerateFileSystemEntries(current)
|
||||
.Any();
|
||||
return PathReachability.FileMissing;
|
||||
}
|
||||
|
||||
if (current.StartsWith(@"\\", StringComparison.Ordinal))
|
||||
{
|
||||
// Exists=false: echter Zugriffsfehler oder Ordner fehlt.
|
||||
_ = Directory.GetFileSystemEntries(current);
|
||||
return PathReachability.FileMissing;
|
||||
}
|
||||
|
||||
// Lokaler Ordner fehlt → für Erst-Deployment als „Datei fehlt“ werten.
|
||||
return PathReachability.FileMissing;
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
return PathReachability.AccessDenied;
|
||||
}
|
||||
catch (IOException ex) when (IsAccessOrLogonFailure(ex))
|
||||
{
|
||||
return PathReachability.AccessDenied;
|
||||
}
|
||||
catch (DirectoryNotFoundException)
|
||||
{
|
||||
string? parent = GetParentPath(current);
|
||||
|
||||
if (parent is null || parent == current)
|
||||
{
|
||||
return current.StartsWith(@"\\", StringComparison.Ordinal)
|
||||
? PathReachability.Unreachable
|
||||
: PathReachability.FileMissing;
|
||||
}
|
||||
|
||||
current = parent;
|
||||
continue;
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
return PathReachability.Unreachable;
|
||||
}
|
||||
}
|
||||
|
||||
return PathReachability.Unreachable;
|
||||
}
|
||||
|
||||
private static string? GetParentPath(string path)
|
||||
{
|
||||
string trimmed = path.TrimEnd('\\', '/');
|
||||
|
||||
if (trimmed.StartsWith(@"\\", StringComparison.Ordinal))
|
||||
{
|
||||
// \\server\share → Stop; darunter weiter nach oben.
|
||||
string withoutPrefix = trimmed[2..];
|
||||
int slash = withoutPrefix.IndexOfAny(['\\', '/']);
|
||||
|
||||
if (slash < 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
int second = withoutPrefix.IndexOfAny(['\\', '/'], slash + 1);
|
||||
|
||||
if (second < 0)
|
||||
{
|
||||
// Bereits Share-Root \\server\share
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
string? parent = Path.GetDirectoryName(trimmed);
|
||||
return string.IsNullOrWhiteSpace(parent) ? null : parent;
|
||||
}
|
||||
|
||||
private static bool IsAccessOrLogonFailure(IOException ex)
|
||||
{
|
||||
// HRESULT-Lower-Word = Win32-Fehlercode
|
||||
int win32 = ex.HResult & 0xFFFF;
|
||||
|
||||
return win32 is
|
||||
5 or // ERROR_ACCESS_DENIED
|
||||
53 or // ERROR_BAD_NETPATH
|
||||
67 or // ERROR_BAD_NET_NAME
|
||||
86 or // ERROR_INVALID_PASSWORD
|
||||
1326 or // ERROR_LOGON_FAILURE
|
||||
59 or // ERROR_UNEXP_NET_ERR
|
||||
64 or // ERROR_NETNAME_DELETED
|
||||
1219 or // ERROR_SESSION_CREDENTIAL_CONFLICT
|
||||
1240 or // ERROR_LOGIN_WKSTA_RESTRICTION
|
||||
1245 or // ERROR_ACCOUNT_RESTRICTION
|
||||
1396; // ERROR_WRONG_TARGET_NAME
|
||||
}
|
||||
|
||||
private static string? NormalizePath(string? path)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
@@ -233,10 +402,14 @@ public sealed class CertificateProbeService
|
||||
|
||||
public sealed class CertificateProbeResult
|
||||
{
|
||||
public const string AccessDeniedShortText = "Admin nötig";
|
||||
|
||||
public bool Success { get; init; }
|
||||
|
||||
public bool FileExists { get; init; }
|
||||
|
||||
public bool AccessDenied { get; init; }
|
||||
|
||||
public string Path { get; init; } = string.Empty;
|
||||
|
||||
public string Message { get; init; } = string.Empty;
|
||||
@@ -289,6 +462,19 @@ public sealed class CertificateProbeResult
|
||||
};
|
||||
}
|
||||
|
||||
public static CertificateProbeResult ForAccessDenied(string path)
|
||||
{
|
||||
return new CertificateProbeResult
|
||||
{
|
||||
Success = false,
|
||||
FileExists = false,
|
||||
AccessDenied = true,
|
||||
Path = path,
|
||||
Message =
|
||||
"Kein Zugriff auf den Pfad – Admin-Konto nötig."
|
||||
};
|
||||
}
|
||||
|
||||
public static CertificateProbeResult PasswordRequired(string path)
|
||||
{
|
||||
return new CertificateProbeResult
|
||||
|
||||
Reference in New Issue
Block a user