Add Windows SSH monitoring MVP #1

Merged
ochenstarik-ui merged 23 commits from agent/windows-ssh-monitoring into main 2026-07-16 15:25:19 +00:00
4 changed files with 257 additions and 134 deletions
Showing only changes of commit 27e71a9831 - Show all commits

View file

@ -1,51 +1,64 @@
# Архитектура Server Monitor Manager
## 1. Компоненты
## 1. Выбранная топология
Для первого тестирования используется переходный режим **SSH pull**: Windows-клиент вызывает на сервере ограниченный forced-command и получает snapshot. Постоянный агент и control node ниже описывают следующий этап после проверки UX на трёх серверах.
Первый рабочий вариант использует один **Mesh Hub** на Linux-сервере с белым IP. Остальные узлы не требуют входящего публичного порта и устанавливают исходящее WireGuard-соединение с Hub.
### Desktop client
```text
Windows client -- ограниченный SSH --> Mesh Hub (публичный UDP)
^
|
исходящие WireGuard-туннели
|
AI-agent / Home / Server2 / ...
```
Windows-first приложение на WinUI 3. Оно показывает состояние всех серверов, графики, события, терминалы и связи. Клиент хранит пользовательские секреты в Windows Credential Locker/DPAPI и не передаёт приватные SSH-ключи control node.
Hub выполняет две разные функции:
### Control node
- control plane первого MVP: список узлов, политики Links и команды управления через ограниченный SSH forced-command;
- data plane первого MVP: маршрутизация WireGuard-трафика с обязательной фильтрацией nftables.
Небольшой координационный сервис, который можно установить на одном из своих серверов. Он принимает исходящие mTLS-соединения агентов, хранит инвентарь, короткую историю метрик, правила оповещений, команды и аудит. Для одного пользователя достаточно SQLite; внешний PostgreSQL не является требованием MVP.
Это осознанная звёздная топология. Прямые peer-to-peer соединения, relay и отказоустойчивый второй Hub не входят в первый MVP.
Control node не становится обязательным маршрутизатором трафика между серверами. При возможности соединение идёт напрямую. Relay добавляется позже как явно включаемый режим для узлов за NAT.
## 2. Текущий переходный режим
### Linux agent
### Windows client
Один статически собранный бинарный файл. Агент читает `/proc`, `/sys` и разрешённые systemd-состояния, агрегирует метрики локально и отправляет их наружу. По умолчанию он запускается непривилегированным пользователем и не открывает входящих портов.
Packaged WinUI 3 приложение хранит профили серверов локально, создаёт отдельную Ed25519 identity мониторинга и вызывает только разрешённые SSH-команды. Клиент показывает snapshot метрик, узлы Mesh и направленные Links.
Привилегированные операции выносятся в маленький отдельный helper с белым списком действий. Агент мониторинга не должен постоянно иметь `root` или `CAP_NET_ADMIN`.
### SSH monitoring endpoint
## 2. Потоки данных
На Hub и Node создаётся непривилегированный пользователь `ochenstarik-monitor`. Его ключ привязан к root-owned forced-command и не даёт shell, PTY или forwarding. В текущем протоколе доступны `metrics` и ограниченные команды `mesh` на Hub.
### Метрики
### Mesh Hub
Агент отправляет компактный snapshot каждые 515 секунд. Control node сохраняет сырые точки недолго, затем выполняет downsampling. При потере сети агент держит ограниченный дисковый буфер и не заполняет диск.
Hub хранит:
Минимальный snapshot:
- публичные WireGuard identities узлов;
- выданные внутренние адреса;
- желаемое и фактическое состояние Links;
- политики CIDR, протокола, порта и TTL;
- журнал управляющих операций.
- load average и использование CPU;
- использованная/доступная память и swap;
- заполнение и inode выбранных файловых систем;
- сетевые счётчики интерфейсов;
- uptime, температура при наличии датчиков;
- состояние выбранных systemd units;
- только метаданные процессов, разрешённые политикой.
Приватный WireGuard-ключ каждого Node создаётся на самом Node и никогда не передаётся Hub. Временный enrollment token не является ключом узла.
### Команды и терминал
### Node
Произвольная shell-команда не является обычной командой агента. Для безопасных операций используются типизированные действия: перезапуск разрешённой службы, чтение журнала с ограничением, получение списка процессов. Полный терминал создаётся как отдельная SSH-сессия от desktop client.
Node устанавливает исходящее WireGuard-соединение с Hub. Входящий публичный порт не нужен. Node применяет выданную конфигурацию, подтверждает её версию и хранит приватный ключ с правами `0600`.
### Links между серверами
## 3. Метрики
Link — отдельный ресурс, а не свойство группы серверов:
Текущий SSH snapshot содержит CPU/load, память, корневой диск, uptime и задержку. Следующая версия добавляет swap, inode, сетевые счётчики и выбранные systemd units.
Постоянный Linux agent появится после стабилизации трёхсерверного сценария. Он будет отправлять метрики исходящим mTLS-соединением, вести ограниченный локальный буфер и не открывать публичный API.
## 4. Links между серверами
Link — направленный ресурс:
```text
Draft -> Connecting -> Active -> Disconnecting -> Disabled
\-> Partial
\-> Failed
Active -> Expired -> Disabled
```
@ -53,33 +66,42 @@ Active -> Expired -> Disabled
Каждый Link содержит:
- source и destination;
- разрешённые IP/CIDR и TCP/UDP-порты;
- режим `manual` или TTL;
- WireGuard peer identities;
- владельца, причину и запись аудита;
- фактическое состояние обоих узлов.
- разрешённый IP или CIDR;
- протокол TCP/UDP и список портов;
- ручной режим или TTL;
- причину, владельца и запись аудита;
- версию политики и подтверждение применения.
Control node передаёт каждому узлу только его часть конфигурации. Приватный WireGuard-ключ никогда не покидает узел. `Disconnect` удаляет peer и маршруты с обеих сторон; при недоступности одного узла команда остаётся обязательной и применяется при его следующем подключении.
Пустая политика не означает `allow all`. Ответный трафик существующего соединения разрешается stateful-правилом, обратное новое соединение требует отдельного Link.
## 3. Сценарий AI-агента
При `Disconnect` Hub сначала блокирует направление в nftables, затем фиксирует `Disabled`. Если подтверждение узла недоступно, интерфейс показывает `Partial`, а обязательное состояние применяется после reconnect.
1. На домашнем сервере создаётся отдельный пользователь, например `ai-agent-dev`, без root-доступа.
2. Рабочие каталоги и разрешённые команды ограничиваются Unix-правами, группами, контейнером или sandbox-профилем.
3. В Server Monitor Manager создаётся Link от сервера с AI-агентом к домашнему серверу только для адреса домашнего узла и SSH-порта.
4. AI-агент использует собственный SSH-ключ или короткоживущий SSH-сертификат.
5. Пользователь нажимает `Connect`; после разработки — `Disconnect`.
6. Отключение Link разрывает сетевую достижимость, а отзыв SSH-сертификата/ключа остаётся вторым независимым барьером.
## 5. Сценарий AI-агента
## 4. Режимы развёртывания
1. На целевом сервере создаётся отдельный Unix-пользователь, например `ai-agent-dev`, без root-доступа.
2. Рабочие каталоги и команды ограничиваются Unix-правами, группами, контейнером или sandbox-профилем.
3. Создаётся Link от узла с AI-агентом к целевому адресу и только необходимому SSH-порту.
4. AI-агент использует отдельную SSH identity, не ключ мониторинга приложения.
5. Пользователь включает Link на ограниченное время и после работы отключает его.
- **Single owner:** один control node, один пользователь, SQLite. Основной MVP.
- **Family/team:** несколько пользователей, роли и подтверждения. После MVP.
- **Direct local:** desktop подключается к агентам внутри одной сети без публичного control node. Возможен позже, но использует тот же протокол идентичности.
## 6. Следующий control layer
## 5. Целевые ограничения MVP
После проверки текущей топологии SSH-команды управления заменяются небольшим control service:
- агент: один бинарник, idle RAM до 50 МБ, CPU в простое менее 1%;
- control node: запуск без Kubernetes и обязательного Docker;
- отсутствие открытого agent API в интернет;
- работа при 50100 серверах на одном небольшом control node;
- деградация без потери управления: графики могут иметь пробелы, но команды не выполняются повторно без idempotency key.
- SQLite для инвентаря, политик, истории и аудита;
- одноразовая enrollment-регистрация;
- исходящие mTLS-сессии агентов;
- WebSocket/stream событий для desktop client;
- idempotency key и защита от replay.
Hub остаётся маршрутизатором Mesh первого поколения. Разделение control plane и data plane возможно позже без изменения модели направленных Links.
## 7. Целевые ограничения MVP
- без Kubernetes и обязательного Docker;
- отсутствие публичного agent API;
- вторичные серверы работают без белого IP;
- 50100 узлов на одном небольшом Hub;
- Node agent: idle RAM до 50 МБ и CPU менее 1%;
- команды не повторяются без idempotency key;
- потеря истории метрик не должна приводить к потере управления Links.

View file

@ -1,25 +1,74 @@
# Контракт Linux-установщика
Файл `ochenstarik-server-monitor-manager.sh` находится в `ochenstarik-ui/lightweight-server`; синхронная копия хранится в `deploy/` этого репозитория.
Единственный исходный файл `ochenstarik-server-monitor-manager.sh` хранится в `ochenstarik-ui/lightweight-server`. В репозитории desktop client не должна находиться устаревающая копия.
## Первый тестовый этап: SSH pull
## Поддерживаемые роли
До появления постоянного агента Windows-клиент получает метрики через OpenSSH. Установщик:
### Monitor only
- поддерживает Ubuntu/Debian с systemd;
- запрашивает публичный ключ `ssh-ed25519`, созданный Windows-клиентом;
- читает интерактивный ключ из `/dev/tty`, поэтому поддерживает установку через `curl | sudo bash`;
- поддерживает повторяемый режим через `SERVER_MONITOR_PUBLIC_KEY`;
- проверяет синтаксис ключа через `ssh-keygen`;
- устанавливает и включает OpenSSH Server, не меняя текущий SSH-порт;
- создаёт отдельного системного пользователя `ochenstarik-monitor` без пароля;
- устанавливает root-owned metrics command;
- записывает ключ как `restrict,command="..."`;
- не создаёт UFW-правил и не открывает дополнительных портов;
- поддерживает `install`, `status` и `uninstall`;
- допускает безопасный повторный запуск для замены ключа мониторинга.
Режим `install` устанавливает SSH monitoring endpoint без WireGuard:
Forced-command отдаёт только строки `KEY=VALUE`:
- Ubuntu/Debian с systemd;
- публичный ключ `ssh-ed25519` из Windows-клиента;
- отдельный системный пользователь `ochenstarik-monitor` без пароля;
- root-owned forced-command;
- сохранение существующего SSH-порта;
- отсутствие нового публичного API.
### Hub
Режим `hub` дополнительно:
- устанавливает WireGuard и nftables;
- запрашивает публичный IPv4/домен и UDP-порт;
- создаёт `smm0` с адресом `10.77.0.1/24`;
- включает IPv4 forwarding;
- устанавливает минимальный root helper и systemd restore unit;
- хранит публичные identities Node и политики Links;
- разрешает транзит только по явной политике.
### Node
Режим `node`:
- локально генерирует WireGuard keypair;
- принимает одноразовый enrollment token;
- отправляет Hub только публичный ключ;
- получает внутренний адрес и подписанную конфигурацию;
- создаёт только исходящее WireGuard-соединение;
- не требует белого IP или входящего публичного порта.
## Команды жизненного цикла
Целевой интерфейс:
```text
install-monitor
install-hub
install-node
status
update
rollback
uninstall-monitor
uninstall-node
uninstall-hub
```
Каждая установка и обновление должны быть идемпотентными. Перед изменением рабочей конфигурации создаётся root-only backup. Ошибка проверки или запуска автоматически восстанавливает последнюю рабочую версию.
Удаление роли должно убрать только принадлежащие ей файлы, units, интерфейсы и правила. Удаление Hub требует отдельного подтверждения и не должно молча оставлять включённый forwarding или nftables ACL.
## Forced-command
Ключ мониторинга допускает только:
- `metrics`;
- read-only `mesh nodes`, `mesh links`, `mesh status` на Hub;
- строго типизированные изменения Link с проверкой параметров.
Он не должен позволять shell, PTY, agent forwarding, TCP forwarding или произвольную команду. Полный SSH-терминал использует отдельную identity.
## Минимальный metrics snapshot
```text
PROTOCOL=1
@ -29,13 +78,23 @@ LOAD1=0.42
CPU_COUNT=4
MEM_TOTAL_KB=...
MEM_AVAILABLE_KB=...
SWAP_TOTAL_KB=...
SWAP_FREE_KB=...
DISK_TOTAL_KB=...
DISK_AVAILABLE_KB=...
DISK_INODES_TOTAL=...
DISK_INODES_FREE=...
NETWORK_RX_BYTES=...
NETWORK_TX_BYTES=...
KERNEL=...
```
Ключ мониторинга не должен позволять shell, PTY, agent forwarding, TCP forwarding или выполнение переданной клиентом команды. Полноценный SSH-терминал использует отдельную пользовательскую identity и не входит в этот ключ.
## Проверки перед применением
## Будущий этап: постоянный агент
После проверки UX на нескольких серверах SSH pull будет дополнен статическим агентом для потоковых метрик, systemd events, короткого локального буфера и управляемых WireGuard Links. Агент будет регистрироваться одноразовым token и работать по исходящему mTLS-соединению без входящего API.
- `bash -n` и ShellCheck;
- проверка SSH-ключа через `ssh-keygen`;
- `sshd -t` перед reload;
- `wg-quick strip` и пробный запуск конфигурации;
- `nft --check` перед заменой таблицы;
- проверка systemd unit;
- сохранение активной SSH-сессии до подтверждения нового доступа.

View file

@ -1,52 +1,85 @@
# План разработки
## Этап 0 — фундамент
## Этап 0 — репозиторий и контракт
- [x] переименовать проект в Server Monitor Manager;
- [x] разделить мониторинг, терминал и Links в архитектуре;
- [x] определить безопасный контракт установки агента;
- [x] выбрать звёздную архитектуру Hub/Node для первого Mesh;
- [x] разделить monitoring identity, terminal identity и AI-agent identity;
- [x] описать направленные Links и kill switch;
- [ ] выбрать лицензию;
- [ ] настроить CI, форматирование, тесты и релизные checksum.
- [ ] добавить CI, форматирование, тесты и release checksum;
- [ ] объединить PR приложения и установщика в `main`.
## Этап 1 — Windows MVP
## Этап 1 — Windows SSH MVP
- [x] установить .NET SDK и официальный WinUI 3 toolchain;
- [x] создать packaged WinUI 3 приложение с воспроизводимым CLI-запуском через WinApp;
- [x] реализовать shell: Overview, Servers, Links, Sessions, Settings;
- [x] добавить локальные demo-данные и адаптивный dashboard;
- [x] генерировать отдельный Ed25519 SSH-ключ и хранить его в локальном каталоге packaged-приложения;
- [x] сохранять профили серверов локально без паролей;
- [x] собрать и реально запустить x64-приложение.
- [x] создать packaged WinUI 3 приложение;
- [x] добавить адаптивный Overview и профили нескольких серверов;
- [x] генерировать отдельный Ed25519 SSH-ключ;
- [x] сохранять профили локально без паролей;
- [x] получать CPU/load, RAM, disk, uptime и latency;
- [x] собрать и реально запустить x64-приложение;
- [ ] редактирование и удаление серверов;
- [ ] единственный изменяемый Hub;
- [ ] настоящие страницы Servers, Links, Sessions и Settings.
## Этап 2 — мониторинг одного сервера
## Этап 2 — установщик Hub/Node
- [x] первый бездемонный Linux endpoint через SSH forced-command;
- [ ] статический постоянный Linux agent для amd64/arm64;
- [ ] control node с SQLite, mTLS enrollment и WebSocket событиями;
- [ ] CPU, RAM, disk, network, uptime и systemd units;
- [ ] ограниченный локальный буфер и downsampling;
- [ ] installer, update, rollback и uninstall;
- [ ] предупреждения о диске, памяти и недоступности.
- [x] SSH forced-command для Ubuntu/Debian;
- [x] роли `hub` и `node`;
- [x] WireGuard Hub и исходящие Node-соединения;
- [x] постоянный nftables ACL на Hub;
- [x] список узлов и handshake-состояние;
- [ ] явные зависимости `sudo`, `visudo` и `ping` для минимального Debian;
- [ ] безопасные `update` и `rollback`;
- [ ] полный `uninstall-monitor`, `uninstall-node` и `uninstall-hub`;
- [ ] интеграционный тест повторной установки и reboot.
## Этап 3 — несколько серверов и терминал
## Этап 3 — безопасная регистрация
- [ ] группы, теги, поиск и сводные статусы;
- [ ] прямой SSH из Windows-клиента;
- [ ] known_hosts pinning и отдельные profiles;
- [ ] типизированные безопасные действия с аудитом;
- [ ] экспорт диагностики без секретов.
- [ ] локальная генерация WireGuard-ключа на Node;
- [ ] одноразовый enrollment token;
- [ ] срок действия не более 10 минут;
- [ ] атомарное погашение token;
- [ ] отзыв и повторная регистрация Node;
- [ ] подтверждение fingerprint Hub;
- [ ] защита desktop SSH-ключа через DPAPI.
## Этап 4 — Links и AI-агенты
## Этап 4 — управляемые Links
- [ ] WireGuard peer helper с минимальными привилегиями;
- [ ] policies по CIDR, протоколу, порту и TTL;
- [ ] connect/disconnect с подтверждением обоих узлов;
- [x] направленные пары source → destination;
- [x] ручное добавление и удаление nftables ACL из Windows-клиента;
- [ ] политики по CIDR, TCP/UDP и порту;
- [ ] TTL и автоматическое истечение;
- [ ] состояния Connecting, Active, Disconnecting, Partial, Disabled и Failed;
- [ ] версия политики и подтверждение применения;
- [ ] обязательное отключение после reconnect;
- [ ] отдельная automation identity для AI-агента;
- [ ] append-only аудит;
- [ ] интеграционные тесты kill switch и частичных отказов.
## Этап 5 — другие платформы
## Этап 5 — мониторинг и терминал
- [ ] macOS и Linux desktop clients после стабилизации Core/API;
- [ ] Android/iOS как companion-клиенты;
- [ ] push-уведомления без передачи административных секретов стороннему push-провайдеру.
- [ ] swap, inode, network и выбранные systemd units;
- [ ] предупреждения по диску, памяти и недоступности;
- [ ] автоматическое обновление и короткая локальная история;
- [ ] графики и экспорт диагностики без секретов;
- [ ] отдельный прямой SSH-терминал;
- [ ] отдельная terminal identity и подтверждение пользователя;
- [ ] отдельная automation identity для AI-агента.
## Этап 6 — постоянный control layer
- [ ] статический Linux agent для amd64/arm64;
- [ ] SQLite inventory, policies, history и audit;
- [ ] исходящие mTLS agent sessions;
- [ ] WebSocket/stream событий для desktop client;
- [ ] ограниченный локальный буфер и downsampling;
- [ ] idempotency key и защита от replay;
- [ ] тест нагрузки 50100 Node на одном Hub.
## Этап 7 — релиз и другие платформы
- [ ] подписанный Windows installer и GitHub Release;
- [ ] checksum Linux-установщика и бинарников;
- [ ] macOS и Linux desktop после стабилизации Core/API;
- [ ] Android/iOS companion clients;
- [ ] push-уведомления без административных секретов у push-провайдера.

View file

@ -2,55 +2,64 @@
## Защищаемые данные
- учётные записи и сессии пользователей;
- device, agent, SSH и WireGuard identities;
- SSH, WireGuard, device и agent identities;
- topology, внутренние адреса, метрики и состояния Links;
- команды, терминальные сессии и аудит;
- topology серверов, адреса и метрики;
- права автоматизаций, включая AI-агентов.
- права пользователей и AI-агентов.
## Границы доверия
Desktop client, control node и каждый agent считаются отдельными субъектами. Компрометация одного агента не должна выдавать ключи других узлов или право создавать новые Links. Control node координирует публичные параметры и политики, но не хранит приватные WireGuard/SSH-ключи узлов.
Windows client, Hub и каждый Node считаются отдельными субъектами. Компрометация одного Node не должна выдавать ключи других узлов или право создавать новые Links.
В первом SSH-MVP control node отсутствует. Windows-клиент генерирует отдельный Ed25519-ключ, а сервер привязывает его к `restrict,command`. Этот ключ предназначен только для чтения ограниченного snapshot метрик и не используется для интерактивного терминала или AI-агента.
Hub хранит только публичные WireGuard-ключи Node. Приватный ключ Node генерируется локально, не включается в enrollment token и не записывается на Hub.
## Регистрация агента
## SSH monitoring MVP
1. Пользователь создаёт одноразовый enrollment token со сроком жизни не более 10 минут.
2. Агент генерирует ключевую пару локально.
3. По TLS агент предъявляет token и публичный ключ.
4. Control node выдаёт agent certificate и помечает token использованным.
5. Последующие соединения требуют mTLS. Повторное использование token отклоняется.
Windows-клиент использует отдельный Ed25519-ключ только для forced-command. Этот ключ не используется для интерактивного терминала или AI-агента. Первый host key требует явного подтверждения fingerprint; последующие подключения проверяют сохранённый `known_hosts`.
## Авторизация
Приватный ключ клиента защищается Windows DPAPI и доступом текущего пользователя. Профили серверов не содержат паролей.
- read-only мониторинг отделён от управления;
- команды проверяются по типу ресурса и действию;
- полный терминал требует свежего подтверждения пользователя;
- создание Link требует точного набора маршрутов/портов, пустой набор не означает `allow all`;
## Регистрация Node
1. На Hub создаётся случайный enrollment token со сроком жизни не более 10 минут.
2. Node локально генерирует WireGuard-ключ.
3. Node предъявляет token и публичный ключ по защищённому каналу.
4. Hub атомарно помечает token использованным, назначает адрес и возвращает подписанную конфигурацию.
5. Повторное использование, просроченный token и смена публичного ключа отклоняются.
До появления mTLS enrollment выполняется через отдельную ограниченную SSH-команду. Token не должен содержать приватный WireGuard-ключ.
## Авторизация Links
- мониторинг read-only отделён от управления;
- Link всегда содержит source, destination, CIDR, протокол и порт;
- пустой список портов не означает разрешение всех портов;
- automation identity не наследует интерактивные права владельца;
- команды имеют срок действия, nonce/idempotency key и защиту от replay.
- команды имеют TTL, nonce/idempotency key и версию политики;
- Hub проверяет имена и параметры повторно, независимо от проверки desktop client.
## Аудит
Записываются входы, регистрация устройств, изменение прав, команды, начало и завершение терминала, создание/подключение/отключение Link. Секреты и полный ввод терминала по умолчанию не журналируются. Аудит экспортируется во внешний append-only storage; локальный администратор control node всё равно считается способным изменить локальную БД.
Записываются регистрация и удаление узла, изменение прав, создание, включение, истечение и отключение Link, а также ошибки применения. Секреты, приватные ключи и полный ввод терминала не журналируются.
Для первого MVP используется append-only JSONL с ограниченными правами и ротацией. После появления control service аудит переносится в SQLite с возможностью внешнего экспорта.
## Kill switch
Отключение Link должно:
1. перевести желаемое состояние в `Disabled`;
2. удалить peer и маршруты на доступных узлах;
3. заблокировать повторное автоматическое включение;
4. сохранить обязательную команду для временно недоступного узла;
5. показать пользователю разницу между желаемым и фактическим состоянием.
Кнопка не должна сообщать «отключено», пока оба узла не подтвердили применение или интерфейс явно не показывает частичное отключение.
1. немедленно удалить разрешающее правило на Hub;
2. записать желаемое состояние `Disabled` до отправки дополнительных команд;
3. запретить автоматическое восстановление после reboot/reconnect;
4. получить подтверждение применённой версии политики;
5. показывать `Partial`, если подтверждение одного из узлов отсутствует;
6. сохранить обязательную операцию для временно недоступного Node.
## Не входит в первый MVP
- выполнение произвольных root-команд из веб/API;
- хранение пользовательских приватных SSH-ключей на control node;
- автоматическое соединение всех серверов группы в одну flat network;
- публичный входящий порт агента;
- собственная криптография вместо TLS, SSH и WireGuard.
- выполнение произвольных root-команд;
- хранение пользовательских приватных SSH-ключей на Hub;
- автоматическое объединение всех серверов в flat network;
- публичный входящий API на Node;
- собственная криптография вместо SSH, TLS и WireGuard;
- автоматический failover между несколькими Hub.