add edit and all in one menu for hosts and services
This commit is contained in:
1 parent
8c1b93c14e
commit
e39ad275b5
15 files changed
+1136
-675
No files matched your search
+50
-25
@@ -19,15 +19,26 @@ TUI работает поверх уже существующего SSH+`sudo`-
|
||||
реализовать вручную (см. «Конкурентность» ниже). Для небольшой команды
|
||||
администраторов это разумный компромисс.
|
||||
|
||||
## Модель доступа: Host → Service → Forward
|
||||
## Модель доступа: каталог хостов → группы → Forward
|
||||
|
||||
Хост (`Host`) — это адрес (например, `172.16.1.20`). На одном хосте может
|
||||
быть несколько сервисов (`Service`) — независимых TCP-портов: `ssh` на 22,
|
||||
какое-то приложение на 7888, и так далее. Группа (`Group`) — это набор
|
||||
хостов (со всеми их сервисами), пользователь может состоять в нескольких
|
||||
группах; его реальный эффективный доступ — объединение сервисов всех хостов
|
||||
всех его групп, это и называется `Forward` (`internal/model/types.go`,
|
||||
`State.AllowedForwards`).
|
||||
Хосты и их сервисы — **общий каталог** (`State.Hosts`), определённый один
|
||||
раз, а не заново в каждой группе. Хост (`Host`) — это адрес (например,
|
||||
`172.16.1.20`) с описанием; на одном хосте может быть несколько сервисов
|
||||
(`Service`) — независимых TCP-портов: `ssh` на 22, какое-то приложение на
|
||||
7888, и так далее. Группа (`Group`) не хранит хосты сама — она хранит
|
||||
только список **имён** хостов из каталога (`Group.Hosts []string`),
|
||||
которые в неё входят (все сервисы такого хоста включаются целиком). Если
|
||||
одни и те же хосты/сервисы нужны нескольким группам — они описываются один
|
||||
раз в каталоге, а группы просто отмечают их галочками, вместо повторного
|
||||
набора адресов и портов.
|
||||
|
||||
Пользователь может состоять в нескольких группах; его реальный эффективный
|
||||
доступ — объединение сервисов всех хостов всех его групп, это и называется
|
||||
`Forward` (`internal/model/types.go`, `State.AllowedForwards`). Переименование
|
||||
хоста автоматически обновляет ссылки на него во всех группах
|
||||
(`State.RenameHost`); удаление хоста снимает его со всех групп, которые его
|
||||
использовали (`State.DeleteHost`) — ссылочная целостность каталога держится
|
||||
в модели, а не проверяется вручную в UI.
|
||||
|
||||
Ключевой момент: пользователь **никогда не подключается по SSH к целевому
|
||||
хосту напрямую** (ни через `ProxyJump`, ни как-то ещё) — он открывает одно
|
||||
@@ -85,17 +96,19 @@ Reverse/remote forwarding (`-R`) в этой модели не выдаётся
|
||||
|
||||
Пользователю нужно пробросить сразу много сервисов за одно соединение
|
||||
(например, 10 хостов, на каждом порт 22 и порт 7888 — 20 форвардов сразу).
|
||||
Локально на его машине два разных удалённых назначения не могут занимать
|
||||
один и тот же локальный порт одновременно, а несколько хостов с одинаковым
|
||||
портом (22 или 7888 у всех десяти) — обычная ситуация. Поэтому локальный
|
||||
порт **не всегда равен удалённому**: `internal/keymgr/portalloc.go`
|
||||
(`AllocateLocalPorts`) для каждого форварда сначала пробует занять локальный
|
||||
порт, совпадающий с удалённым (удобно и предсказуемо, когда порт встречается
|
||||
один раз), а при конфликте — берёт следующий свободный из диапазона `20000+`.
|
||||
Присвоение детерминировано (зависит только от порядка и состава форвардов
|
||||
пользователя), поэтому повторный экспорт архива с тем же набором хостов даёт
|
||||
те же локальные порты. Итоговое соответствие "локальный порт → сервис"
|
||||
попадает в `INSTRUCTIONS.txt` внутри архива пользователя.
|
||||
Локальный порт **никогда не равен удалённому порту** — не только чтобы
|
||||
избежать коллизий между несколькими хостами с одинаковым портом (обычная
|
||||
ситуация: 22 или 7888 у всех десяти), но и потому, что многие удалённые
|
||||
порты привилегированные (`< 1024`, в первую очередь сам ssh на 22) —
|
||||
слушателя на таком порту локально можно открыть только с правами
|
||||
администратора, которых у обычного пользователя на его Windows-машине нет.
|
||||
`internal/keymgr/portalloc.go` (`AllocateLocalPorts`) поэтому просто выдаёт
|
||||
локальные порты последовательно из диапазона `20000+`, без попытки
|
||||
переиспользовать номер удалённого порта. Присвоение детерминировано
|
||||
(зависит только от порядка и состава форвардов пользователя), поэтому
|
||||
повторный экспорт архива с тем же набором хостов даёт те же локальные
|
||||
порты. Итоговое соответствие "локальный порт → сервис" попадает в
|
||||
`INSTRUCTIONS.txt` внутри архива пользователя.
|
||||
|
||||
## Жизненный цикл приватного ключа
|
||||
|
||||
@@ -115,6 +128,19 @@ fingerprint. Это осознанное решение: кража бэкапа
|
||||
ключей, выпущенных в конкретной партии, — передавайте его получателям
|
||||
отдельным от архива каналом (голосом, в личку, не в общий чат).
|
||||
|
||||
## Подтверждение деструктивных действий
|
||||
|
||||
Блокировка/удаление пользователя, удаление хоста/сервиса/группы и
|
||||
перевыпуск уже выданного ключа (экспорт поверх существующего) — все эти
|
||||
действия либо необратимы, либо мгновенно и широко режут доступ, поэтому
|
||||
подтверждаются не клавишей (`enter`/`y`, которую легко нажать рефлекторно),
|
||||
а вводом заново сгенерированной случайной строки (`internal/ui/confirm.go`,
|
||||
`generateConfirmToken` — 5 латинских букв, дефис, 3 цифры). Неверный ввод
|
||||
просто сбрасывает поле с пояснением и оставляет на этом же экране; выйти без
|
||||
последствий можно только через `esc`. Выдача **нового** ключа пользователю,
|
||||
у которого его ещё не было, ничего не ломает и подтверждения не требует —
|
||||
трение нужно только там, где есть что сломать.
|
||||
|
||||
## Бэкап и аварийное восстановление
|
||||
|
||||
Каталог состояния (`/var/lib/ssh-access-tui/state`) — git-репозиторий.
|
||||
@@ -172,12 +198,11 @@ session" вместо тихой порчи данных. Если реальн
|
||||
|
||||
## Известные ограничения / что дальше
|
||||
|
||||
- Обновление клиентского конфига пользователя при появлении **нового**
|
||||
разрешённого хоста в его группе требует повторного экспорта архива
|
||||
(что заодно ротирует ключ пользователя — так и задумано: ротация ключа
|
||||
время от времени — отдельная хорошая практика, просто в MVP она не
|
||||
отделена от "обновить конфиг"). Удаление хоста из группы вступает в силу
|
||||
немедленно на сервере (permitopen), без участия клиента.
|
||||
- Обновление клиентского конфига пользователя при появлении нового
|
||||
сервиса/хоста в его группе — отдельный ручной шаг («обновить конфиг»,
|
||||
не требует переиздания ключа); права доступа на сервере (`permitopen=`)
|
||||
обновляются немедленно и автоматически в любом случае, независимо от
|
||||
того, обновил ли пользователь свой локальный `config`.
|
||||
- Архивы в `export_dir` не удаляются автоматически после доставки —
|
||||
администратор должен удалить их вручную, когда получатель подтвердил
|
||||
получение.
|
||||
|
||||
Reference in new issue
Block a user