feat: web-vnc single-binary browser VNC gateway with password access

- Go stdlib-only gateway: serves noVNC client, password auth, WS<->TCP relay
- auth: PBKDF2-HMAC-SHA256 password hash, HMAC session cookies, login rate limit
- relay: hand-written RFC 6455 WebSocket + transparent RFB bridge
- vncspawner: cross-OS VNC server detection/launch (Windows/Linux/macOS)
- server: /login /logout /vnc /api/status routes, session middleware, embed.FS
- scripts: get-novnc, get-vnc (zip-verified), list-ips, open-firewall
- run.bat: one-click launcher (asks only password), lists access IPs
- README/AGENTS.md (Russian), .gitignore (root-anchored binaries)
This commit is contained in:
Codex
2026-07-30 17:40:31 +03:00
commit b89477fb87
24 changed files with 2057 additions and 0 deletions
+203
View File
@@ -0,0 +1,203 @@
# web-vnc
Один бинарник, который открывает доступ к рабочему столу (VNC) через браузер
(noVNC) с защитой по **паролю**. Программа `web-vnc`:
- раздаёт HTML5-клиент noVNC,
- пускает зрителя по общему паролю,
- прокидывает WebSocket браузера к локальному VNC-серверу (RFB поверх WS),
- и (по желанию) сама запускает VNC-сервер.
Написано на чистом Go, **только стандартная библиотека** — никаких внешних
Go-модулей, собирается офлайн.
```
Браузер (noVNC) ──ws──▶ web-vnc (один бинарник) ──tcp RFB──▶ VNC-сервер (127.0.0.1:5900)
├─ вход по паролю (PBKDF2-HMAC-SHA256)
├─ HMAC session-cookie
└─ прозрачный релей WS↔TCP
```
## Самый простой запуск (Windows) — одним батником
В папке проекта есть `run.bat`. Он делает всё сам и спрашивает **только пароль**:
```bat
run.bat
```
Что делает `run.bat`:
1. Если `web-vnc.exe` отсутствует — собирает его (нужен установленный Go).
Если Go нет — подсказывает, что сделать.
2. Если noVNC-клиент ещё не встроен — пытается его скачать (нужен интернет).
Без интернета продолжит работу, но в браузере будет страница-заглушка.
3. Спрашивает пароль доступа.
4. Генерирует из него хэш.
5. Ищет VNC-сервер: если он не установлен — пытается скачать портативный
UltraVNC (`scripts\get-vnc.ps1`) и запускать его.
6. Выводит список всех адресов для подключения (`http://localhost:8080` и
`http://<каждый IPv4 машины>:8080`) и запускает сервер с флагом `--spawn`.
После запуска откройте в браузере один из выведенных адресов,
введите тот же пароль — и попадёте на рабочий стол.
## Быстрый старт вручную (Windows)
```powershell
# 1. Подтянуть noVNC-клиент во встроенную статику (один раз)
.\scripts\get-novnc.ps1
# 2. Собрать бинарник
go build -o web-vnc.exe .\cmd\web-vnc
# 3. Сгенерировать хэш пароля
$hash = .\web-vnc.exe --gen-hash "ваш-пароль"
# 4. Запустить (сам найдёт/запустит VNC-сервер и поднимется на :8080)
.\web-vnc.exe --password-hash $hash --spawn
```
## Быстрый старт (Linux / macOS)
```bash
./scripts/get-novnc.sh
go build -o web-vnc ./cmd/web-vnc
hash=$(./web-vnc --gen-hash "ваш-пароль")
./web-vnc --password-hash "$hash" --spawn
```
## Как это работает
1. Пользователь открывает `http://хост:8080/` и попадает на `/login`.
2. Вводит общий пароль. Он сверяется с **солёным PBKDF2-HMAC-SHA256**-хэшем
(120 000 итераций). При успехе сервер ставит **HMAC-подписанную,
HttpOnly**-куку сессии (по умолчанию 8 ч, без серверного хранилища).
3. Браузер загружает `/vnc.html` (только с валидной сессией). Клиент noVNC
открывает WebSocket на `/vnc`.
4. `web-vnc` проверяет сессию **до** апгрейда WebSocket и затем прозрачно
релеит байты между WebSocket и локальным VNC-сервером
(`127.0.0.1:5900` по умолчанию). RFB-протокол проходит нетронутым,
как у `websockify`.
> VNC-сервер должен слушать на **loopback (127.0.0.1)** и может работать
> **без VNC-пароля** — защита по паролю теперь на веб-гейтвее. Если же ваш
> VNC-сервер требует свой пароль, noVNC спросит и его.
## Авто-запуск VNC-сервера (`--spawn`)
С `--spawn` программа ищет установленный VNC-сервер и запускает его как
дочерний процесс (отдельно, без окна на Windows), после чего подключается к нему.
| ОС | Что ищет |
|---------|-------------------------------------------------------|
| Windows | UltraVNC (`winvnc.exe`), TightVNC (`tvnserver.exe`) |
| Linux | `x11vnc`, `tigervncserver` / `Xvnc` |
| macOS | встроенный Screen Sharing (`kickstart`) |
Переопределить авто-поиск своей командой:
```powershell
.\web-vnc.exe --password-hash $hash --spawn-command "C:\Path\To\winvnc.exe -run"
```
Без `--spawn` убедитесь, что VNC-сервер уже слушает по адресу из `--vnc`.
## Настройка
Все флаги дублируются переменными окружения (`WEBVNC_*`).
| Флаг | Env | По умолчанию | Описание |
|--------------------|---------------------------|--------------------|------------------------------------------------|
| `--listen` | `WEBVNC_LISTEN` | `:8080` | адрес HTTP/WS |
| `--vnc` | `WEBVNC_VNC` | `127.0.0.1:5900` | адрес вышестоящего VNC-сервера |
| `--password-hash` | `WEBVNC_PASSWORD_HASH` | (обязателен) | хэш PBKDF2 из `--gen-hash` |
| `--session-secret` | `WEBVNC_SESSION_SECRET` | случайный при старте | HMAC-ключ для подписи куки сессии |
| `--session-ttl` | `WEBVNC_SESSION_TTL` | `8h` | время жизни куки сессии |
| `--spawn` | `WEBVNC_SPAWN` | false | авто-запуск найденного VNC-сервера |
| `--spawn-command` | `WEBVNC_SPAWN_COMMAND` | (нет) | явная команда запуска VNC-сервера |
| `--web-root` | `WEBVNC_WEB_ROOT` | (встроенные) | раздавать статику с диска вместо embed |
| `--novnc-path` | | `/vnc.html` | путь страницы noVNC-клиента |
| `--relay-path` | | `/vnc` | endpoint WebSocket-релея |
### Сгенерировать хэш пароля
```bash
web-vnc --gen-hash "ваш-пароль"
# напечатает, например: pbkdf2-sha256$120000$<соль>$<ключ>
```
Формат самодокументируемый:
`pbkdf2-sha256$<итерации>$<base64-соль>$<base64-ключ>`.
### Запуск одной строкой
```powershell
.\web-vnc.exe --password-hash (. \web-vnc.exe --gen-hash "secret") --spawn
```
## Безопасность
- **Без TLS**: рассчитано на **приватную сеть**. Если выставляете наружу —
поставьте перед ним reverse-proxy с TLS (nginx/caddy).
- Логин с лимитом попыток (5 в минуту на IP, в памяти).
- Кука сессии подписана `--session-secret`. Задайте фиксированный
`--session-secret`, чтобы сессии переживали перезапуск (иначе секрет
меняется при каждом старте, и старые сессии инвалидируются).
- VNC-сервер держите на `127.0.0.1`, чтобы до него нельзя было достучаться
в обход гейтвея.
## Устранение неполадок
- **После ввода пароля — ошибка / нет картинки.**
Значит, веб-гейтвей не смог подключиться к VNC-серверу (по адресу `--vnc`,
по умолчанию `127.0.0.1:5900`). На странице теперь показывается понятное
сообщение вместо криптографической ошибки noVNC.
Решение: должен работать VNC-сервер, который отдаёт рабочий стол:
- Windows: установите **UltraVNC** или **TightVNC**, либо запустите
`scripts\get-vnc.ps1` (скачает портативный UltraVNC в папку `vnc\`, и
`run.bat` сам его запустит).
- Linux: `x11vnc` или `TigerVNC`.
- macOS: встроенный Screen Sharing.
Важно: VNC-сервер должен слушать на `127.0.0.1:5900`. На Windows для захвата
экрана может потребоваться запуск от имени администратора (UAC).
- **С другого компьютера страница не открывается (таймаут/недоступно).**
По умолчанию Windows Firewall блокирует входящие подключения. Один раз
выполните от имени администратора:
```bat
scripts\open-firewall.bat
```
Это откроет входящий TCP-порт 8080. (`run.bat` выводит адреса и подсказку.)
- **noVNC-клиент не встроен (страница-заглушка).**
Запустите `scripts\get-novnc.ps1` (нужен интернет), пересоберите и перезапустите.
## Структура проекта
```
cmd/web-vnc/main.go точка входа CLI: флаги, спавн VNC, запуск сервера
internal/config конфигурация (флаги + env)
internal/auth хэш пароля PBKDF2, HMAC-куки сессии, rate-limit
internal/relay WebSocket на stdlib (RFC 6455) + мост WS↔TCP
internal/vncspawner кросс-ОС поиск и запуск VNC-сервера
internal/server HTTP-роуты, middleware сессии, встроенная статика
internal/server/static встроенные веб-ассеты (vnc.html + core/app/vendor noVNC)
scripts/get-novnc.{ps1,sh} скачать noVNC во встроенную статику
scripts/get-vnc.ps1 скачать портативный VNC-сервер (UltraVNC) в папку vnc/
scripts/list-ips.ps1 список IPv4 машины (используется run.bat)
scripts/open-firewall.bat открыть порт 8080 в Windows Firewall (один раз, от админа)
run.bat запуск в один клик (спрашивает только пароль)
```
## Кросс-компиляция под другую ОС
```bash
GOOS=linux GOARCH=amd64 go build -o web-vnc-linux ./cmd/web-vnc
GOOS=windows GOARCH=amd64 go build -o web-vnc.exe ./cmd/web-vnc
GOOS=darwin GOARCH=arm64 go build -o web-vnc-macos ./cmd/web-vnc
```
## Лицензия
MIT. Встроенные ассеты noVNC сохраняют свою лицензию (MPL-2.0) —
см. `internal/server/static/LICENSE` после запуска `get-novnc`.