add ProxyJump function

This commit is contained in:
redlineo committed 2026-07-23 12:38:13 +03:00
1 parent 56e20e5464
commit 7f0e0fdb23
48 files changed
+3875 -44

No files matched your search

+55 -15
View File
@@ -46,13 +46,20 @@ TUI работает поверх уже существующего SSH+`sudo`-
(`State.DeleteHost`, `State.RemoveServiceRefs`) — переименование/удаление
одного сервиса никогда не задевает остальные сервисы того же хоста.
Ключевой момент: пользователь **никогда не подключается по SSH к целевому
хосту напрямую** (ни через `ProxyJump`, ни как-то ещё) — он открывает одно
соединение к самому jump-хосту и локально пробрасывает (`LocalForward`)
порты к каждому нужному сервису через это соединение. Не важно, слушает ли
на целевом порту настоящий SSH (22) или произвольный TCP-сервис (7888) —
для форвардинга это не имеет значения, оба варианта — это просто
"локальный порт → host:port через jump".
Ключевой момент: и в `LocalForward`, и в `ProxyJump`-режиме клиентского
конфига (`model.User.ProxyJump`, см. ниже) канал до целевого хоста —
это всегда один и тот же серверный примитив, `direct-tcpip` через
jump-хост: пользователь никогда не получает интерактивный шелл на целевом
хосте через jump, только форвардинг порта до него. В `LocalForward`-режиме
он открывает одно соединение к самому jump-хосту и локально пробрасывает
порты к каждому нужному сервису через это соединение; в `ProxyJump`-режиме
клиентский `ssh` сам открывает по одному `direct-tcpip`-каналу через то же
соединение к jump-хосту на время каждой команды (`ssh <alias>`), без
постоянно висящего туннеля и без локальных портов — разница только в том,
как клиент организует один и тот же тип канала, а не в том, что jump-хост
разрешает. Не важно, слушает ли на целевом порту настоящий SSH (22) или
произвольный TCP-сервис (7888) — для форвардинга это не имеет значения,
оба варианта — это просто "порт → host:port через jump".
## Как на самом деле работает ограничение "только прыжок/форвардинг"
@@ -84,14 +91,41 @@ TUI работает поверх уже существующего SSH+`sudo`-
повреждены/потеряны, а не основной механизм.
2. **Клиент (только удобство).** Файл `~/.ssh/config`, который уезжает
пользователю в архиве, — это один блок `Host jump` с набором строк
`LocalForward <локальный_порт> <host>:<port>` (`internal/keymgr/clientconfig.go`).
Он ничего не запрещает: пользователь может отредактировать его или
вызвать `ssh` руками с любым хостом. Единственная причина, по которой это
не работает, — сервер отклонит `permitopen`, которого нет в списке.
Список форвардов в клиентском конфиге должен генерироваться из того же
источника правды (группы в `state.yaml`), что и `permitopen=`, — иначе
конфиг и реальные права разойдутся.
пользователю в архиве, зависит от `model.User.ProxyJump` (переключается
чекбоксом на экране групп пользователя, `internal/ui/users.go`) и
генерируется одним из двух рендереров
(`internal/keymgr/clientconfig.go`), но оба **одинаково не являются
границей безопасности**:
- `false` (по умолчанию, `LocalForward`) — один блок `Host jump` с
набором строк `LocalForward <локальный_порт> <host>:<port>`, одно
всегда открытое соединение сразу даёт локальные порты на все сервисы
(`RenderClientConfig`).
- `true` (`ProxyJump`) — отдельный блок `Host <alias>` на каждый
разрешённый сервис с `ProxyJump jump` вместо `HostName jump`, без
локальных портов и без постоянно открытого соединения — каждый вызов
`ssh <alias>` сам поднимает и закрывает канал по требованию
(`RenderClientConfigProxyJump`).
Ни один из двух вариантов ничего не запрещает: пользователь может
отредактировать любой из них или вызвать `ssh` руками с любым хостом.
Единственная причина, по которой это не работает, — сервер отклонит
`permitopen`, которого нет в списке, и это ограничение **идентично для
обоих режимов**: `BuildUserArchive`/`BuildConfigOnlyArchive` в обоих
случаях берут один и тот же `[]model.Forward` (`State.AllowedForwards`)
и не трогают `internal/sysadm/authorizedkeys.go` — переключение
`ProxyJump` меняет только то, как выглядит клиентский конфиг, а не какие
`permitopen=` попадают в `authorized_keys`. Список форвардов в
клиентском конфиге (в любом из двух форматов) должен генерироваться из
того же источника правды (группы в `state.yaml`), что и `permitopen=`, —
иначе конфиг и реальные права разойдутся.
Переключение режима у уже существующего пользователя не применяется
автоматически: `ForwardsHash`/`IsConfigStale` отслеживают только
изменения состава форвардов, а не смену стиля конфига (само
переключение `ProxyJump` без изменения групп не помечает конфиг как
устаревший) — администратор должен вручную прогнать «Обновить конфиги»
после переключения, если хочет, чтобы у пользователя оказался конфиг
нового формата.
Reverse/remote forwarding (`-R`) в этой модели не выдаётся вообще
(`AllowStreamLocalForwarding no`, никаких `permitlisten=`) — по вашему же
@@ -249,3 +283,9 @@ session" вместо тихой порчи данных. Если реальн
получение.
- Полностью параллельная работа нескольких админов не поддерживается
(см. «Конкурентность»).
- Переключение `ProxyJump`/`LocalForward` у уже существующего пользователя
не переиздаёт и не помечает его конфиг устаревшим автоматически (см.
выше, «Клиент (только удобство)») — после переключения нужно вручную
прогнать «Обновить конфиги» для этого пользователя, иначе у него на
диске останется конфиг старого формата (что ничего не ломает: доступ на
сервере не зависит от этого режима, только удобство пользователя).