diff --git a/README.md b/README.md index 4787a64..7d9c3fd 100644 --- a/README.md +++ b/README.md @@ -1,74 +1,130 @@ # LPRA - LongPolling Remote Access Лёгкий self-hosted сервис для доступа к Linux-машинам **без белого IP** (за NAT). -Агент на клиенте сам исходяще подключается к серверу по HTTP long-polling; админ -в веб-панели видит список машин и открывает по каждой **терминал в браузере** (по умолчанию от root, пользователя можно сменить). +Агент на клиенте сам исходяще подключается к серверу - по WebSocket, а где он не проходит, +по HTTP long-polling. В веб-панели видно список машин, и по каждой есть терминал в браузере, +файловый менеджер, проброс портов и статус. ``` -браузер ──WS──> [nginx :8445 TLS] ──> [Go server 127.0.0.1:8091] <──long-poll── агент (root, за NAT) - │ - SQLite +браузер ──HTTPS/WS──> [Cloudflare] ──> [nginx TLS] ──> [Go server 127.0.0.1:8091] <──WS / long-poll── агент (за NAT) + │ ▲ + SQLite └──── прямой TCP/UDP-туннель :8446 (проброс tcp/udp) ``` +## Возможности +- **Терминал** - вкладки на нескольких машинах сразу, до четырёх терминалов в одной вкладке + (перетаскиванием, с изменяемыми размерами), переименование вкладок. На телефоне - панель + клавиш (Esc, Ctrl, Alt, стрелки и т.д.). +- **SSH-терминал** - в меню «+»: агент открывает SSH-соединение к другому хосту (по паролю, + ключу - можно загрузить файл - или своими ключами из `~/.ssh`), шелл открывается во вкладке, + а шаги подключения видны в статусе. Ключ хоста запоминается при первом входе + (`ssh_known_hosts` рядом с `state.json` агента); если он сменится, вход откажет. +- **Файлы** - просмотр, редактор с подсветкой, hex-редактор, предпросмотр картинок, видео, PDF, + таблиц SQLite; загрузка и скачивание до 512 МБ кусками по 4 МБ (проходит через Cloudflare); + свойства (chmod/chown), zip/unzip. +- **Перемещение и копирование** - внутри машины или, кнопкой «сеть» в том же окне, **на другую + машину или на SSH-хост**: папка уходит потоком tar без промежуточных файлов, с прогрессом, + скоростью и отменой. «Переместить» удаляет исходник после успешной отправки. Правила для + существующего: сообщить / заменить / оставить как есть. +- **Проброс портов** - `http` через панель, `tcp`/`udp` через прямой туннель. +- **Статус** - нагрузка, память, диски, сеть, тест скорости канала до сервера. +- **Вход** - пароль и/или ключи доступа (passkeys); пароль можно выключить, когда есть ключ. +- **PIN-код** (необязательный, 4 цифры) - спрашивается в каждой новой вкладке браузера, но не при + перезагрузке. На устройстве можно не спрашивать его 5/15/30/60 минут после ввода. + Пять неверных попыток - выход из сессии. + ## Компоненты -- **Сервер** (`cmd/server`) - админ-панель + точка встречи для агентов. За nginx (TLS). -- **Агент** (`cmd/agent`) - Linux-бинарь, systemd-сервис от root, PTY по запросу. -- **install.sh** - интерактивный установщик, отдаётся сервером как статика. +- **Сервер** (`cmd/server`) - панель и точка встречи для агентов. Ставится за nginx (TLS). +- **Агент** (`cmd/agent`) - бинарь для Linux (amd64/arm64), systemd-сервис от root. +- **install.sh** - установщик агента, отдаётся сервером. ## Сборка ```sh ./build.sh ``` -Получаются `bin/lpra-server` и `dist/agent-linux-{amd64,arm64}`. +Получаются `bin/lpra-server*` и `dist/agent-linux-{amd64,arm64}`. +Версия задаётся в одном месте - `VERSION.txt`, она вшивается в сервер и агент. ## Запуск сервера ```sh ./bin/lpra-server -addr 127.0.0.1:8091 -dist ./dist -db ./lpra.db ``` -Поставьте перед ним nginx по `deploy/nginx.conf.example` (TLS, long-poll/WS). -Локально без TLS можно добавить `-insecure-cookies`. +| Флаг | Переменная | По умолчанию | | +|---|---|---|---| +| `-addr` | `LPRA_ADDR` | `127.0.0.1:8091` | адрес HTTP (за nginx) | +| `-db` | `LPRA_DB` | `lpra.db` | база SQLite | +| `-dist` | `LPRA_DIST` | `dist` | бинарники агента для `/download/` | +| `-tunnel` | `LPRA_TUNNEL_ADDR` | `:8446` | прямой туннель TCP+UDP (пусто - выключен) | +| `-insecure-cookies` | `LPRA_INSECURE_COOKIES=1` | | куки без Secure - только для локального запуска без TLS | -Первый запуск: откройте `/setup` - мастер создаст администратора. Дальше `/settings`: -адрес хоста (по умолчанию `https://localhost:8445`), enroll-секрет и готовая -команда установки. +Пример юнита - `deploy/lpra-server.service`. -## Установка агента на клиент -Одной командой (под root): -```sh -curl -fsSL https://localhost:8445/install.sh | sudo HOST=https://localhost:8445 ENROLL_SECRET=<секрет> sh +Первый запуск: откройте `/setup` - мастер создаст администратора. Дальше в `/settings`: +адрес хоста, enroll-секрет, короткий код установки и готовая команда. + +### nginx +Конфиг - `deploy/nginx.conf.example`. Обязательно: +- заголовки `Upgrade` / `Connection` (через `map $http_upgrade $connection_upgrade`) - без них + WebSocket не пройдёт, и агенты и терминалы останутся на long-polling (работает, но медленнее + на больших выводах); +- `proxy_read_timeout` больше 25 с (удержание long-poll), `proxy_buffering off`. + +### Cloudflare +Работает за проксирующим Cloudflare: WebSocket он пропускает, файлы идут кусками меньше лимита +на запрос, статика с хешем в имени кэшируется. Чтобы панель видела настоящие IP (ограничение +попыток входа и кода установки считается по адресу), включите в nginx real IP: +```nginx +real_ip_header CF-Connecting-IP; +set_real_ip_from 173.245.48.0/20; # и остальные диапазоны с https://www.cloudflare.com/ips/ ``` -Или интерактивно (спросит секрет, а если агент уже стоит - предложит удалить): +Прямой туннель (`:8446`) - это не HTTP, через проксирующий Cloudflare он не идёт: в +«Настройки → Прямой туннель» укажите для агентов IP хоста или DNS-имя без проксирования. + +## Установка агента +Короткой командой из `/settings` (код из 3-24 букв и цифр, кнопка «Новый код» меняет его): ```sh -curl -fsSL https://localhost:8445/install.sh | sudo sh +curl -fsSL https://panel.example/i/<код> | sudo sh ``` +Или полной: +```sh +curl -fsSL https://panel.example/install.sh | sudo HOST=https://panel.example ENROLL_SECRET=<секрет> sh +``` +Без параметров установщик спросит секрет, а если агент уже стоит - предложит удалить. +Обновить агента можно кнопкой в панели («Изменить» → обновить). + +## Связь агента с сервером +В «Изменить» → «Связь с сервером» для каждой машины: +- **auto** (по умолчанию) - WebSocket; если он не подключается, long-polling ровно минуту, потом + снова попытка WebSocket; +- **ws** - только WebSocket; +- **lp** - только long-polling. ## Проброс портов и прямой туннель -Правила проброса создаются на странице машины. Режим `http` идёт через адрес панели (long-poll), -режимы `tcp`/`udp` - через прямой туннель: агент держит исходящее соединение к порту туннеля хоста -(флаг `-tunnel` / `LPRA_TUNNEL_ADDR`). +Правила создаются на странице машины «Порты». Режим `http` идёт через адрес панели, +`tcp`/`udp` - через прямой туннель: агент держит исходящее соединение к порту туннеля хоста, +по нему мультиплексируются все соединения. -Порт туннеля и хост, к которому для него подключаются агенты (например, IP вместо домена панели), -меняются в `/settings` → «Прямой туннель». Агенты забирают адрес с панели сами - при подключении и -при каждом опросе, переустановка не нужна. Локальные `TUNNEL_ADDR` / `TUNNEL_PORT` в окружении агента -имеют приоритет над настройкой панели (`TUNNEL_ADDR=off` отключает туннель). +Порт туннеля и хост для агентов меняются в `/settings` → «Прямой туннель»; агенты забирают их +сами, переустановка не нужна. Локальные `TUNNEL_ADDR` / `TUNNEL_PORT` в окружении агента имеют +приоритет (`TUNNEL_ADDR=off` отключает туннель). -Если на хосте активен ufw и сервер запущен от root, панель сама открывает порт хоста правила -(tcp для `http`/`tcp`, udp для `udp`) и порт туннеля для прямых режимов, а при удалении правила - закрывает. -При `ProtectSystem=strict` в systemd-юните ufw нужны права на запись: `ReadWritePaths=/etc/ufw /run`. - -## Версия -Версия задаётся в одном месте - `VERSION.txt`; файл вшивается в сервер и агент при сборке. +Если на хосте активен ufw и сервер запущен от root, панель сама открывает порты правил и порт +туннеля, а при удалении правила закрывает. При `ProtectSystem=strict` в юните нужно +`ReadWritePaths=/etc/ufw /run`. ## Пользователь терминала -По умолчанию терминал открывается от пользователя, под которым запущен агент (на Linux - root). -В окне «Изменить машину» можно выбрать другого пользователя машины или создать нового -(имя, пароль, опционально - добавить в sudo). Смена и создание работают, только если агент запущен от root. -Файловый менеджер по-прежнему работает от пользователя агента. +По умолчанию терминал открывается от пользователя агента (root). В «Изменить машину» можно выбрать +другого пользователя или создать нового (с паролем, по желанию с sudo) - если агент запущен от root. +Файловый менеджер работает от пользователя агента. ## Безопасность -- Пароли админов - bcrypt; сессии в HttpOnly/Secure/SameSite куках; CSRF на формах; rate-limit логина. -- Агенты: общий enroll-секрет → индивидуальный токен (хранится хешем, сверка constant-time). -- Открытие терминала пишется в таблицу `audit` (терминал даёт root-доступ), как и смена/создание пользователя. +- Пароли админов - bcrypt; сессии в HttpOnly/Secure/SameSite куках; CSRF на формах; + ограничение попыток входа, PIN и кода установки. +- Ключи доступа (WebAuthn) - вход без пароля; пароль можно выключить совсем. +- Агенты: общий enroll-секрет → индивидуальный токен (хранится хешем, сверка constant-time); + туннель авторизуется HMAC от токена. +- SSH-пароли и ключи передаются агенту один раз для подключения и нигде не сохраняются. +- Открытие терминала и SSH, перенос файлов на другие машины, смена пользователя пишутся в `audit`. ## Скриншоты image