Add firewall script for managing inbound rules; support for domain profiles and interface aliases
This commit is contained in:
+37
@@ -36,6 +36,43 @@ powershell -NoProfile -ExecutionPolicy Bypass -File "$env:LOCALAPPDATA\Programs\
|
|||||||
Правила брандмауэра установщик не меняет. Для подключения из домашней сети
|
Правила брандмауэра установщик не меняет. Для подключения из домашней сети
|
||||||
потребуется разрешённый доступ к TCP-порту агента и UDP 5353 в частной сети.
|
потребуется разрешённый доступ к TCP-порту агента и UDP 5353 в частной сети.
|
||||||
|
|
||||||
|
## Брандмауэр
|
||||||
|
|
||||||
|
Установленный `Firewall.ps1` создаёт два входящих правила для конкретного exe:
|
||||||
|
TCP 8765 (HTTPS/WSS) и UDP 5353 (mDNS). Только профиль Private и адреса
|
||||||
|
LocalSubnet; Edge Traversal запрещён. Профили сети и чужие правила не меняются.
|
||||||
|
По умолчанию в доменной или публичной сети эти правила не разрешат подключение.
|
||||||
|
Если домашний Ethernet имеет доменный профиль, можно явно передать
|
||||||
|
`-Profile Domain -InterfaceAlias Ethernet`. Для Domain обязательно точное имя
|
||||||
|
интерфейса; маски и `Any` запрещены. Адреса остаются ограничены LocalSubnet.
|
||||||
|
Профиль Public не поддерживается. Повторное применение заменяет профиль и
|
||||||
|
интерфейс собственных правил для этого exe, а не добавляет ещё одну пару правил.
|
||||||
|
|
||||||
|
Просмотр без изменений:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
& "$env:LOCALAPPDATA\Programs\MusicBridge\Firewall.ps1" -Action Preview
|
||||||
|
```
|
||||||
|
|
||||||
|
Применение из PowerShell администратора:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
& "$env:LOCALAPPDATA\Programs\MusicBridge\Firewall.ps1" -Action Apply
|
||||||
|
```
|
||||||
|
|
||||||
|
Для нестандартной установки указать абсолютный `-ProgramPath` к exe;
|
||||||
|
для другого порта — `-Port`. `-Action Apply -WhatIf` показывает план без изменений.
|
||||||
|
Повторное применение обновляет собственные правила этого пути, включая порт.
|
||||||
|
Скрипт не сужает доступ, который уже разрешён другими правилами Windows.
|
||||||
|
Перед удалением приложения можно удалить его правила из PowerShell администратора:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
& "$env:LOCALAPPDATA\Programs\MusicBridge\Firewall.ps1" -Action Remove
|
||||||
|
```
|
||||||
|
|
||||||
|
Удаление правил выполняется отдельно: установщик и деинсталлятор не запрашивают
|
||||||
|
административные права. Для нестандартной установки передать тот же `-ProgramPath`.
|
||||||
|
|
||||||
## Для разработчика
|
## Для разработчика
|
||||||
|
|
||||||
Собрать пакет: `powershell -NoProfile -File Packaging/Publish.ps1`.
|
Собрать пакет: `powershell -NoProfile -File Packaging/Publish.ps1`.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# MusicBridge — план проекта
|
# MusicBridge — план проекта
|
||||||
|
|
||||||
Обновлено: 2026-09-09. Репозиторий находится в `MusicBridge.Agent/`.
|
Обновлено: 2026-09-10. Репозиторий находится в `MusicBridge.Agent/`.
|
||||||
|
|
||||||
## Цель
|
## Цель
|
||||||
|
|
||||||
@@ -86,6 +86,9 @@ Windows Core Audio → системная громкость устройств
|
|||||||
- [x] Автозапуск текущего пользователя через меню трея; скрытый старт `--minimized`.
|
- [x] Автозапуск текущего пользователя через меню трея; скрытый старт `--minimized`.
|
||||||
- [x] Самостоятельный Windows x64 пакет со встроенными Runtime, ZIP-установка,
|
- [x] Самостоятельный Windows x64 пакет со встроенными Runtime, ZIP-установка,
|
||||||
ярлык в меню Пуск и скрипт удаления. Это не MSI и не подписанный установщик.
|
ярлык в меню Пуск и скрипт удаления. Это не MSI и не подписанный установщик.
|
||||||
|
- [x] Скрипт брандмауэра: просмотр, применение и удаление собственных правил;
|
||||||
|
только конкретный exe, Private + LocalSubnet, TCP порта агента и UDP 5353.
|
||||||
|
- [x] Восстановление опроса после ошибок чтения; освобождение WSS после обрывов.
|
||||||
|
|
||||||
### 4. Нативное приложение iPhone — отложено пользователем
|
### 4. Нативное приложение iPhone — отложено пользователем
|
||||||
|
|
||||||
@@ -112,8 +115,40 @@ Windows Core Audio → системная громкость устройств
|
|||||||
|
|
||||||
## Проверки и следующий шаг
|
## Проверки и следующий шаг
|
||||||
|
|
||||||
|
2026-09-10:
|
||||||
|
|
||||||
|
- Пользователь подтвердил: Ethernet подключён к домашней сети. В `Firewall.ps1`
|
||||||
|
добавлены `-Profile Domain -InterfaceAlias Ethernet`; по умолчанию Private.
|
||||||
|
Domain требует точный интерфейс, Public и маски запрещены. LocalSubnet,
|
||||||
|
привязка к exe и порты сохранены. Preview/WhatIf и проверки ограничений прошли.
|
||||||
|
- После повторного запроса пользователя правила применены через Windows UAC.
|
||||||
|
В ActiveStore проверены оба правила: включены, Inbound Allow, Domain,
|
||||||
|
интерфейс Ethernet, LocalSubnet, EdgeTraversal Block, TCP 8765 и UDP 5353.
|
||||||
|
Привязаны к `bin/Release/net10.0-windows10.0.19041.0/MusicBridge.Agent.exe`;
|
||||||
|
имена `MusicBridge-0F2EC7194BF18EBC-HTTPS` и `MusicBridge-0F2EC7194BF18EBC-mDNS`.
|
||||||
|
Агент во время проверки не запущен. При установке в другой каталог нужны
|
||||||
|
правила для нового пути. Подключение со второго устройства ещё не проверено.
|
||||||
|
|
||||||
2026-09-09:
|
2026-09-09:
|
||||||
|
|
||||||
|
- Последний этап — устойчивость: **112 проверок прошли**, сборка без ошибок и
|
||||||
|
предупреждений. Конфигурация `Resilience`, параметры `--desktop-smoke
|
||||||
|
--qr-window-smoke --mdns-smoke --live-read`. Работающий Release-агент не остановлен.
|
||||||
|
Проверены ошибки первого и последующих чтений, восстановление состояния,
|
||||||
|
очистка устаревших данных при ошибке и 12 последовательных обрывов WSS.
|
||||||
|
Чтение получает отмену через 5 секунд; отправка WSS ограничена 5 секундами,
|
||||||
|
keepalive — 20 секунд с ожиданием ответа 10 секунд. После длинного перерыва
|
||||||
|
mDNS пересоздаёт объявления. Реальный сон/пробуждение ещё не проверен.
|
||||||
|
- `Packaging/Test-Firewall.ps1` проверил Preview и Apply -WhatIf без изменения
|
||||||
|
системы. Правила ещё не применены: Ethernet имеет профиль DomainAuthenticated,
|
||||||
|
Tailscale — Private, tun2 — Public. Правила Private не откроют доступ через
|
||||||
|
Ethernet. Категории сети не менять автоматически; доступ для Domain требует
|
||||||
|
отдельного решения пользователя.
|
||||||
|
- ZIP пересобран с изменениями устойчивости и `Firewall.ps1`. Повторная проверка
|
||||||
|
установки, запуска со встроенным Runtime, обновления, защиты занятого exe и
|
||||||
|
удаления прошла. Проверка правил в режиме WhatIf также прошла; реальных
|
||||||
|
изменений брандмауэра и установки в рабочую папку пользователя не выполнялось.
|
||||||
|
|
||||||
- Последний этап — автозапуск и упаковка: **107 проверок прошло** с
|
- Последний этап — автозапуск и упаковка: **107 проверок прошло** с
|
||||||
`-c Packaging --no-build -- --desktop-smoke --qr-window-smoke --mdns-smoke --live-read`.
|
`-c Packaging --no-build -- --desktop-smoke --qr-window-smoke --mdns-smoke --live-read`.
|
||||||
Проверены запись/удаление тестовой записи автозапуска и скрытый старт окна.
|
Проверены запись/удаление тестовой записи автозапуска и скрытый старт окна.
|
||||||
@@ -194,7 +229,8 @@ Windows Core Audio → системная громкость устройств
|
|||||||
объявляется на домашних IPv4-адаптерах с multicast. Автоизменения брандмауэра нет.
|
объявляется на домашних IPv4-адаптерах с multicast. Автоизменения брандмауэра нет.
|
||||||
|
|
||||||
Следующий шаг: проверка с другого устройства и устойчивости при смене сети,
|
Следующий шаг: проверка с другого устройства и устойчивости при смене сети,
|
||||||
проверка входа Windows с автозапуском и подготовка правил частного брандмауэра.
|
проверка входа Windows с автозапуском. Правила для домашнего Ethernet применены
|
||||||
|
и проверены для текущего пути Release-сборки (см. запись от 2026-09-10).
|
||||||
Трей, автозапуск и ZIP-упаковка реализованы. iPhone-клиент пока не начинать.
|
Трей, автозапуск и ZIP-упаковка реализованы. iPhone-клиент пока не начинать.
|
||||||
Точные границы реализации и следующие шаги — в `CONNECTION-ROADMAP.md`.
|
Точные границы реализации и следующие шаги — в `CONNECTION-ROADMAP.md`.
|
||||||
Начать разработку iOS только после новой команды пользователя.
|
Начать разработку iOS только после новой команды пользователя.
|
||||||
@@ -221,6 +257,9 @@ Windows Core Audio → системная громкость устройств
|
|||||||
- `Desktop/DesktopHost.cs`: STA-поток, значок/меню трея и жизненный цикл окна.
|
- `Desktop/DesktopHost.cs`: STA-поток, значок/меню трея и жизненный цикл окна.
|
||||||
- `Desktop/StartupSettings.cs`: HKCU Run, экранированная команда автозапуска.
|
- `Desktop/StartupSettings.cs`: HKCU Run, экранированная команда автозапуска.
|
||||||
- `Packaging/`: сборка standalone ZIP, установка/удаление и изолированный smoke-тест.
|
- `Packaging/`: сборка standalone ZIP, установка/удаление и изолированный smoke-тест.
|
||||||
|
- `Packaging/Firewall.ps1`: Preview/Apply/Remove правил LocalSubnet; Private по
|
||||||
|
умолчанию, Domain только с явно указанным интерфейсом домашней сети.
|
||||||
|
- `Packaging/Test-Firewall.ps1`: проверка определений правил без изменения системы.
|
||||||
- `INSTALL.md`: установка, автозапуск, обновление и удаление.
|
- `INSTALL.md`: установка, автозапуск, обновление и удаление.
|
||||||
- `Network/LocalAddresses.cs`: подходящие домашние IPv4-адреса.
|
- `Network/LocalAddresses.cs`: подходящие домашние IPv4-адреса.
|
||||||
- `Network/DiscoveryDescriptor.cs`: публичные поля DNS-SD.
|
- `Network/DiscoveryDescriptor.cs`: публичные поля DNS-SD.
|
||||||
|
|||||||
@@ -235,6 +235,11 @@ Windows-окно с QR; `B` создаёт новый код. При `--lan` а
|
|||||||
Поддерживается до 8 одновременных WebSocket. После потери соединения клиент
|
Поддерживается до 8 одновременных WebSocket. После потери соединения клиент
|
||||||
переподключается с задержкой и получает полный снимок. Медленный клиент получает
|
переподключается с задержкой и получает полный снимок. Медленный клиент получает
|
||||||
последнее состояние; промежуточные снимки не накапливаются в неограниченной очереди.
|
последнее состояние; промежуточные снимки не накапливаются в неограниченной очереди.
|
||||||
|
Отправка снимка ограничена 5 секундами. Keepalive отправляется через 20 секунд,
|
||||||
|
ожидание ответа ограничено 10 секундами; оборванные соединения освобождают слот.
|
||||||
|
При ошибке чтения медиасессии сервер публикует пустое состояние с `mediaError`
|
||||||
|
и продолжает опрос. После восстановления очередной снимок содержит свежие данные.
|
||||||
|
На чтение передаётся отмена через 5 секунд.
|
||||||
Запросы с Origin
|
Запросы с Origin
|
||||||
отклоняются: браузерные клиенты в проекте не поддерживаются.
|
отклоняются: браузерные клиенты в проекте не поддерживаются.
|
||||||
|
|
||||||
|
|||||||
+13
-4
@@ -2,9 +2,17 @@
|
|||||||
param(
|
param(
|
||||||
[ValidateSet('Preview','Apply','Remove')][string]$Action = 'Preview',
|
[ValidateSet('Preview','Apply','Remove')][string]$Action = 'Preview',
|
||||||
[string]$ProgramPath = (Join-Path $env:LOCALAPPDATA 'Programs\MusicBridge\MusicBridge.Agent.exe'),
|
[string]$ProgramPath = (Join-Path $env:LOCALAPPDATA 'Programs\MusicBridge\MusicBridge.Agent.exe'),
|
||||||
[ValidateRange(1,65535)][int]$Port = 8765
|
[ValidateRange(1,65535)][int]$Port = 8765,
|
||||||
|
[ValidateSet('Private','Domain')][string]$Profile = 'Private',
|
||||||
|
[string]$InterfaceAlias
|
||||||
)
|
)
|
||||||
$ErrorActionPreference = 'Stop'
|
$ErrorActionPreference = 'Stop'
|
||||||
|
if ($Profile -eq 'Domain' -and [string]::IsNullOrWhiteSpace($InterfaceAlias)) {
|
||||||
|
throw 'Domain access requires an explicit home network InterfaceAlias.'
|
||||||
|
}
|
||||||
|
if ($InterfaceAlias -and ([string]::IsNullOrWhiteSpace($InterfaceAlias) -or $InterfaceAlias -eq 'Any' -or [Management.Automation.WildcardPattern]::ContainsWildcardCharacters($InterfaceAlias))) {
|
||||||
|
throw 'Select one exact network interface, without wildcards.'
|
||||||
|
}
|
||||||
if (![IO.Path]::IsPathRooted($ProgramPath)) { throw 'ProgramPath must be absolute.' }
|
if (![IO.Path]::IsPathRooted($ProgramPath)) { throw 'ProgramPath must be absolute.' }
|
||||||
$exe = [IO.Path]::GetFullPath($ProgramPath)
|
$exe = [IO.Path]::GetFullPath($ProgramPath)
|
||||||
if ([IO.Path]::GetFileName($exe) -ne 'MusicBridge.Agent.exe') { throw 'Select MusicBridge.Agent.exe.' }
|
if ([IO.Path]::GetFileName($exe) -ne 'MusicBridge.Agent.exe') { throw 'Select MusicBridge.Agent.exe.' }
|
||||||
@@ -13,12 +21,13 @@ $sha = [Security.Cryptography.SHA256]::Create()
|
|||||||
try { $id = ([BitConverter]::ToString($sha.ComputeHash([Text.Encoding]::UTF8.GetBytes($exe.ToUpperInvariant())))).Replace('-','').Substring(0,16) }
|
try { $id = ([BitConverter]::ToString($sha.ComputeHash([Text.Encoding]::UTF8.GetBytes($exe.ToUpperInvariant())))).Replace('-','').Substring(0,16) }
|
||||||
finally { $sha.Dispose() }
|
finally { $sha.Dispose() }
|
||||||
$definitions = @(
|
$definitions = @(
|
||||||
@{Name="MusicBridge-$id-HTTPS"; DisplayName='MusicBridge HTTPS (private LAN)'; Protocol='TCP'; LocalPort=$Port},
|
@{Name="MusicBridge-$id-HTTPS"; DisplayName='MusicBridge HTTPS (local subnet)'; Protocol='TCP'; LocalPort=$Port},
|
||||||
@{Name="MusicBridge-$id-mDNS"; DisplayName='MusicBridge mDNS (private LAN)'; Protocol='UDP'; LocalPort=5353}
|
@{Name="MusicBridge-$id-mDNS"; DisplayName='MusicBridge mDNS (local subnet)'; Protocol='UDP'; LocalPort=5353}
|
||||||
)
|
)
|
||||||
foreach ($definition in $definitions) {
|
foreach ($definition in $definitions) {
|
||||||
$definition.Program = $exe
|
$definition.Program = $exe
|
||||||
$definition.Profile = 'Private'
|
$definition.Profile = $Profile
|
||||||
|
$definition.InterfaceAlias = if ($InterfaceAlias) { $InterfaceAlias } else { 'Any' }
|
||||||
$definition.Direction = 'Inbound'
|
$definition.Direction = 'Inbound'
|
||||||
$definition.Action = 'Allow'
|
$definition.Action = 'Allow'
|
||||||
$definition.RemoteAddress = 'LocalSubnet'
|
$definition.RemoteAddress = 'LocalSubnet'
|
||||||
|
|||||||
@@ -13,4 +13,16 @@ if (($rules | Where-Object Protocol -eq UDP).LocalPort -ne 5353) { throw 'mDNS p
|
|||||||
$again = @(& $script -Action Preview -ProgramPath $exe -Port 8765)
|
$again = @(& $script -Action Preview -ProgramPath $exe -Port 8765)
|
||||||
if (($rules.Name -join ',') -ne ($again.Name -join ',')) { throw 'Changing port would leave old rule names behind.' }
|
if (($rules.Name -join ',') -ne ($again.Name -join ',')) { throw 'Changing port would leave old rule names behind.' }
|
||||||
& $script -Action Apply -ProgramPath $exe -WhatIf
|
& $script -Action Apply -ProgramPath $exe -WhatIf
|
||||||
|
$domain = @(& $script -Action Preview -ProgramPath $exe -Profile Domain -InterfaceAlias Ethernet)
|
||||||
|
foreach ($rule in $domain) {
|
||||||
|
if ($rule.Profile -ne 'Domain' -or $rule.InterfaceAlias -ne 'Ethernet' -or $rule.RemoteAddress -ne 'LocalSubnet') { throw 'Domain scope mismatch.' }
|
||||||
|
}
|
||||||
|
if (($domain.Name -join ',') -ne ($rules.Name -join ',')) { throw 'Changing profile would leave old rules behind.' }
|
||||||
|
foreach ($badAlias in @('', 'Any', 'Ether*', 'Ether?et')) {
|
||||||
|
$rejected = $false
|
||||||
|
try { & $script -Action Preview -ProgramPath $exe -Profile Domain -InterfaceAlias $badAlias | Out-Null }
|
||||||
|
catch { $rejected = $true }
|
||||||
|
if (!$rejected) { throw 'Domain scope accepted an unrestricted interface.' }
|
||||||
|
}
|
||||||
|
& $script -Action Apply -ProgramPath $exe -Profile Domain -InterfaceAlias Ethernet -WhatIf
|
||||||
Write-Host 'PASS: private profile, local subnet, executable binding, ports, stable names and dry-run. No firewall changes.'
|
Write-Host 'PASS: private profile, local subnet, executable binding, ports, stable names and dry-run. No firewall changes.'
|
||||||
|
|||||||
Reference in New Issue
Block a user