From 231ba6975410e7a063201ca8d2aa2029a38e046e Mon Sep 17 00:00:00 2001 From: Ochenstarik Date: Thu, 16 Jul 2026 18:43:51 +0700 Subject: [PATCH] Add restricted Link policy controls --- docs/roadmap.md | 4 +- .../MainPage.xaml | 33 ++++++++- .../MainPage.xaml.cs | 68 ++++++++++++++++--- .../MeshModels.cs | 21 +++++- .../SshMonitorService.cs | 2 +- 5 files changed, 114 insertions(+), 14 deletions(-) diff --git a/docs/roadmap.md b/docs/roadmap.md index 07be447..6abbfb8 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -48,8 +48,8 @@ - [x] направленные пары source → destination; - [x] ручное добавление и удаление nftables ACL из Windows-клиента; -- [ ] политики по CIDR, TCP/UDP и порту; -- [ ] TTL и автоматическое истечение; +- [x] политики по целевому `/32`, TCP/UDP и порту; +- [x] TTL и автоматическое истечение; - [ ] состояния Connecting, Active, Disconnecting, Partial, Disabled и Failed; - [ ] версия политики и подтверждение применения; - [ ] обязательное отключение после reconnect; diff --git a/src/ServerMonitorManager.Desktop/MainPage.xaml b/src/ServerMonitorManager.Desktop/MainPage.xaml index dcff39c..d37793a 100644 --- a/src/ServerMonitorManager.Desktop/MainPage.xaml +++ b/src/ServerMonitorManager.Desktop/MainPage.xaml @@ -244,6 +244,36 @@ Header="Целевой сервер" ItemsSource="{x:Bind MeshNodes}" /> + + + + + + + + + + + + + + @@ -265,9 +295,10 @@ + SelectionMode="Single"> diff --git a/src/ServerMonitorManager.Desktop/MainPage.xaml.cs b/src/ServerMonitorManager.Desktop/MainPage.xaml.cs index 192dc6b..05b53a2 100644 --- a/src/ServerMonitorManager.Desktop/MainPage.xaml.cs +++ b/src/ServerMonitorManager.Desktop/MainPage.xaml.cs @@ -306,9 +306,17 @@ public sealed partial class MainPage : Page continue; } var fields = line[5..].Split('|'); - if (fields.Length >= 2) + if (fields.Length >= 7 + && int.TryParse(fields[4], CultureInfo.InvariantCulture, out var port) + && long.TryParse(fields[5], CultureInfo.InvariantCulture, out var expiresUnix)) { - MeshLinks.Add(new MeshLinkViewModel(fields[0], fields[1])); + MeshLinks.Add(new MeshLinkViewModel( + fields[0], + fields[1], + fields[2], + fields[3], + port, + expiresUnix)); } } @@ -343,12 +351,53 @@ public sealed partial class MainPage : Page ShowInfo("Mesh Hub не выбран", "Сначала добавьте главный сервер с отметкой Mesh Hub.", InfoBarSeverity.Warning); return; } - if (SourceNodeBox.SelectedItem is not MeshNodeViewModel source - || TargetNodeBox.SelectedItem is not MeshNodeViewModel target) + MeshNodeViewModel? source = null; + MeshNodeViewModel? target = null; + string protocol; + int port; + int ttlMinutes; + + if (enable) { - ShowInfo("Выберите серверы", "Укажите источник и сервер назначения.", InfoBarSeverity.Warning); - return; + if (SourceNodeBox.SelectedItem is not MeshNodeViewModel selectedSource + || TargetNodeBox.SelectedItem is not MeshNodeViewModel selectedTarget) + { + ShowInfo("Выберите серверы", "Укажите источник и сервер назначения.", InfoBarSeverity.Warning); + return; + } + source = selectedSource; + target = selectedTarget; + protocol = (LinkProtocolBox.SelectedItem as ComboBoxItem)?.Tag?.ToString() ?? "tcp"; + if (double.IsNaN(LinkPortBox.Value) + || double.IsNaN(LinkTtlBox.Value) + || LinkPortBox.Value is < 1 or > 65535 + || LinkTtlBox.Value is < 0 or > 525600) + { + ShowInfo("Некорректная политика", "Проверьте порт и TTL.", InfoBarSeverity.Warning); + return; + } + port = checked((int)LinkPortBox.Value); + ttlMinutes = checked((int)LinkTtlBox.Value); } + else + { + if (MeshLinksList.SelectedItem is not MeshLinkViewModel selectedLink) + { + ShowInfo("Выберите связь", "Для отключения выберите правило в списке.", InfoBarSeverity.Warning); + return; + } + source = MeshNodes.FirstOrDefault(node => node.Name == selectedLink.Source); + target = MeshNodes.FirstOrDefault(node => node.Name == selectedLink.Target); + if (source is null || target is null) + { + ShowInfo("Узел не найден", "Обновите список Mesh и повторите попытку.", InfoBarSeverity.Warning); + return; + } + protocol = selectedLink.Protocol; + port = selectedLink.Port; + ttlMinutes = 0; + } + if (source.Name == target.Name) { ShowInfo("Некорректная связь", "Источник и назначение должны отличаться.", InfoBarSeverity.Warning); @@ -358,15 +407,18 @@ public sealed partial class MainPage : Page try { var action = enable ? "connect" : "disconnect"; + var policyArguments = enable + ? $"{protocol} {port} {ttlMinutes}" + : $"{protocol} {port}"; using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(15)); await _ssh.RunRestrictedCommandAsync( hub.Profile, - $"mesh {action} {source.Name} {target.Name}", + $"mesh {action} {source.Name} {target.Name} {policyArguments}", timeout.Token); await RefreshMeshAsync(showSuccess: false); ShowInfo( enable ? "Связь включена" : "Связь отключена", - $"{source.Name} → {target.Name}", + $"{source.Name} → {target.Name} · {protocol.ToUpperInvariant()}/{port}", enable ? InfoBarSeverity.Success : InfoBarSeverity.Warning); } catch (Exception exception) diff --git a/src/ServerMonitorManager.Desktop/MeshModels.cs b/src/ServerMonitorManager.Desktop/MeshModels.cs index 14148bc..60f6c2f 100644 --- a/src/ServerMonitorManager.Desktop/MeshModels.cs +++ b/src/ServerMonitorManager.Desktop/MeshModels.cs @@ -19,13 +19,30 @@ public sealed class MeshNodeViewModel public sealed class MeshLinkViewModel { - public MeshLinkViewModel(string source, string target) + public MeshLinkViewModel( + string source, + string target, + string cidr, + string protocol, + int port, + long expiresUnix) { Source = source; Target = target; + Cidr = cidr; + Protocol = protocol; + Port = port; + ExpiresUnix = expiresUnix; } public string Source { get; set; } public string Target { get; set; } - public string Label => $"{Source} → {Target}"; + public string Cidr { get; set; } + public string Protocol { get; set; } + public int Port { get; set; } + public long ExpiresUnix { get; set; } + public string ExpirationText => ExpiresUnix == 0 + ? "вручную" + : $"до {DateTimeOffset.FromUnixTimeSeconds(ExpiresUnix).ToLocalTime():dd.MM HH:mm}"; + public string Label => $"{Source} → {Target} · {Protocol.ToUpperInvariant()}/{Port} · {Cidr} · {ExpirationText}"; } diff --git a/src/ServerMonitorManager.Desktop/SshMonitorService.cs b/src/ServerMonitorManager.Desktop/SshMonitorService.cs index 46ad15f..0e5eac9 100644 --- a/src/ServerMonitorManager.Desktop/SshMonitorService.cs +++ b/src/ServerMonitorManager.Desktop/SshMonitorService.cs @@ -201,6 +201,6 @@ public sealed partial class SshMonitorService [GeneratedRegex("^[a-z_][a-z0-9_-]{0,31}$", RegexOptions.CultureInvariant)] private static partial Regex SafeUserRegex(); - [GeneratedRegex("^(metrics|mesh (nodes|links|status|connect [a-z0-9][a-z0-9-]{0,31} [a-z0-9][a-z0-9-]{0,31}|disconnect [a-z0-9][a-z0-9-]{0,31} [a-z0-9][a-z0-9-]{0,31}))$", RegexOptions.CultureInvariant)] + [GeneratedRegex("^(metrics|mesh (nodes|links|status|connect [a-z0-9][a-z0-9-]{0,31} [a-z0-9][a-z0-9-]{0,31} (tcp|udp) [0-9]{1,5} [0-9]{1,6}|disconnect [a-z0-9][a-z0-9-]{0,31} [a-z0-9][a-z0-9-]{0,31} (tcp|udp) [0-9]{1,5}))$", RegexOptions.CultureInvariant)] private static partial Regex SafeRestrictedCommandRegex(); }