391 lines
29 KiB
Markdown
391 lines
29 KiB
Markdown
# Lemana VPN
|
||
|
||
macOS-пакет для подключения к корпоративному VPN `vpn.lemanapro.ru`.
|
||
|
||
В приложении два способа входа:
|
||
|
||
- **Вручную**: логин и пароль берутся с этого Mac, одноразовый код вводится вручную в видимом окне.
|
||
- **Автоматически**: данные подставляются и форма отправляется в скрытом браузере. Источник выбирается отдельно: **Ключ на этом Mac** или **Bitwarden**.
|
||
|
||
Кнопка **Подключить** всегда использует сохранённую настройку. Изменение способа входа не запускает VPN и применяется при следующем подключении. Свежая установка по умолчанию выбирает ручной ввод кода.
|
||
|
||
## Быстрая Установка
|
||
|
||
```sh
|
||
curl -fsSL https://git.dokops.ru/dokril/lemana-vpn/raw/branch/main/install.sh | sh
|
||
```
|
||
|
||
После установки:
|
||
|
||
```sh
|
||
exec zsh
|
||
vpn --status
|
||
vpn
|
||
```
|
||
|
||
Открыть приложение:
|
||
|
||
```sh
|
||
open ~/Applications/LemanaVPN.app
|
||
```
|
||
|
||
## Режимы Установки
|
||
|
||
Интерактивный установщик сначала предлагает способ входа, затем источник для автоматического режима:
|
||
|
||
```text
|
||
Какой режим использовать?
|
||
|
||
1) Ручной вход: запомнить логин и пароль на этом Mac (рекомендуется)
|
||
Логин и пароль подставятся из macOS Keychain. Одноразовый код вводится руками каждый раз.
|
||
|
||
2) Автоматический вход
|
||
Пароль и код подставляются автоматически, форма отправляется без ручного ввода.
|
||
|
||
Источник данных: 1) Ключ на этом Mac, 2) Bitwarden [1/2, Enter=1]
|
||
```
|
||
|
||
Неинтерактивно:
|
||
|
||
```sh
|
||
sh install.sh --non-interactive --mode manual-keychain --configure-login
|
||
sh install.sh --non-interactive --mode keychain-auto --import-yandex-key
|
||
sh install.sh --non-interactive --mode bitwarden-auto
|
||
```
|
||
|
||
Проверить действия без изменений:
|
||
|
||
```sh
|
||
sh install.sh --dry-run --non-interactive --mode manual-keychain
|
||
sh install.sh --dry-run --non-interactive --mode bitwarden-auto
|
||
```
|
||
|
||
## Ручной Режим
|
||
|
||
По умолчанию `manual-keychain` хранит только LDAP username/password:
|
||
|
||
- service `openconnect-lite`, account `<LDAP username>` - LDAP password;
|
||
- TOTP/current 6-digit code не сохраняется и не требуется.
|
||
|
||
Настроить или обновить логин и пароль:
|
||
|
||
```sh
|
||
vpn --configure-login
|
||
```
|
||
|
||
Из меню LemanaVPN.app доступна команда `Логин и пароль…`; она открывает Terminal и запускает тот же CLI-путь, чтобы пароль вводился в нормальном TTY.
|
||
|
||
Подключиться:
|
||
|
||
```sh
|
||
vpn
|
||
```
|
||
|
||
Ожидаемое поведение: откроется видимое окно входа, логин и пароль подставятся, одноразовый код вводится руками, submit не нажимается автоматически.
|
||
|
||
## Одноразовый код из Яндекс Ключа
|
||
|
||
В Яндекс Ключе (Яндекс ID) откройте **Настройки → Резервная копия данных → Сохранить в файл** и перенесите файл на Mac. Это штатный [локальный экспорт Яндекса](https://yandex.ru/support/id/ru/authorization/twofa-copy). Он содержит секреты всех записей: не отправляйте его в чат и не загружайте в онлайн-конвертеры.
|
||
|
||
В обычном установщике есть отдельный вопрос об импорте. Запустить установку сразу с этим шагом:
|
||
|
||
```sh
|
||
sh install.sh --mode keychain-auto --import-yandex-key
|
||
```
|
||
|
||
Для уже установленного CLI после обновления:
|
||
|
||
```sh
|
||
vpn --configure-login # если логин и пароль ещё не сохранены
|
||
vpn --import-yandex-key
|
||
vpn --status
|
||
```
|
||
|
||
Мастер спросит путь к файлу, покажет имена записей и предложит выбрать запись Lemana/VPN. Перед сохранением он покажет LDAP-аккаунт назначения и попросит текущий код с телефона. Проверка выполняется локально; введённый код скрыт. LDAP-логин в настройках CLI и профиле SSO должен совпадать.
|
||
|
||
После успешной проверки только выбранный секрет сохраняется в macOS Keychain: service `openconnect-lite`, account `totp/<LDAP username>`. В `env` выбирается `keychain-auto`: автоматический вход с ключом на этом Mac. Пароль остаётся прежним. Команда `vpn` подставит логин, пароль и код и отправит форму в скрытом браузере. Bitwarden для этого не нужен. Импорт не запускает VPN.
|
||
|
||
Поддерживается локальный JSON-экспорт (`key_backup` или `key_backup.txt`), TOTP / SHA1 / 6 цифр / 30 секунд. Другие алгоритмы, HOTP и собственные коды аккаунта Яндекса отклоняются. Лимиты импортёра: файл до 4 МиБ, до 1000 записей. Формат проверен по [импортёру GNOME Authenticator](https://world.pages.gitlab.gnome.org/Authenticator/src/authenticator/backup/yandex.rs.html); облачные копии и QR-коды этот мастер не читает.
|
||
|
||
Отмена или неверный код ничего не сохраняют. При ошибке включения настройки импортёр пытается вернуть прежнюю запись Keychain и сообщает, если откат не удался. Исходный файл не копируется и автоматически не удаляется. При выключении компьютера или принудительном завершении процесса между записями проверьте состояние через `vpn --status` и при необходимости повторите импорт.
|
||
|
||
Чтобы снова вводить код вручную, выберите «Вручную» в меню или выполните `vpn --set-auth=manual`. Сохранённый секрет останется в Keychain. `vpn --manual-full` по-прежнему отключает любую подстановку. Обновление сохраняет выбранную настройку; режим `bitwarden-auto` использует свой прежний путь.
|
||
|
||
## Автоматический вход через Bitwarden
|
||
|
||
`bitwarden-auto` использует Bitwarden как источник данных:
|
||
|
||
- Bitwarden item: `LM LDAP`;
|
||
- Bitwarden username - LDAP username;
|
||
- Bitwarden password - LDAP/domain password, не master password от vault;
|
||
- Bitwarden TOTP field - permanent 2FA setup secret или `otpauth://...`, не текущие 6 цифр;
|
||
- Touch ID helper может открыть master password для Bitwarden;
|
||
- runtime sync кладёт username в config, а пароль и permanent 2FA secret в macOS Keychain для `openconnect-lite`.
|
||
|
||
В этом режиме Lemana VPN не спрашивает LDAP username/password вручную. Если `~/.config/lemana-vpn/env` содержит пустой `LEMANA_VPN_USERNAME=""`, это нормально: username заполнится из `LM LDAP` после успешного Bitwarden unlock/sync.
|
||
|
||
Подключиться:
|
||
|
||
```sh
|
||
vpn
|
||
```
|
||
|
||
Разово запустить вход через Bitwarden из CLI:
|
||
|
||
```sh
|
||
vpn-auto
|
||
vpn bitwarden-auto
|
||
```
|
||
|
||
Если Bitwarden не залогинен:
|
||
|
||
```sh
|
||
bw login
|
||
```
|
||
|
||
Если автоматический вход не проходит, сначала проверь статус и логи:
|
||
|
||
```sh
|
||
vpn --status
|
||
tail -f ~/Library/Logs/LemanaVPN-openconnect-lite.log
|
||
```
|
||
|
||
## Статус
|
||
|
||
Меню приложения открывается в компактном окне. Пункт **Импорт из Яндекс Ключа…** запускает существующий мастер в Terminal; приложение не читает секреты. В меню всегда видны сохранённый способ входа и источник. Стрелки с разовыми режимами у «Подключить» нет; настройки изменяются отдельными переключателями. Во время сохранения нельзя запустить подключение или второй запрос изменения.
|
||
|
||
В разделе **Значок в строке меню** можно выбрать:
|
||
|
||
- **Щит**: значок 16 pt с яркой заливкой оставшегося времени и прогрессом по контуру; вариант по умолчанию.
|
||
- **Кольцо**: круг с такой же заливкой оставшегося времени и прогрессом по контуру.
|
||
- **Часы**: щит с циферблатом и движущейся стрелкой.
|
||
- **Время**: щит и компактное оставшееся время `H:MM`.
|
||
|
||
Выбор сохраняется между запусками, в том числе ранее выбранные кольцо, часы и время. Значки зелёные, пока остаётся больше часа; янтарные в последний час, красные в последние 15 минут. Нейтральная подложка и более плотная цветная заливка делают прогресс различимым в строке меню. Точное время видно в меню и подсказке значка. Начало и конец берутся из CLI, фиксированной длительности нет. Со старым CLI начало отсчитывается от первого события подключения. Если срок неизвестен или некорректен, прогресс не выдумывается. Истечение таймера само по себе не запускает новое подключение. Значки состояний, действий и источников входа используют единый векторный набор.
|
||
|
||
Индикаторы «Щит» и «Кольцо» начинают с полной яркой заливки. По мере расхода времени яркая часть уменьшается сверху вниз, а израсходованная остаётся тусклой и видимой.
|
||
|
||
«Подключено» появляется после выдачи адреса туннеля, независимо от наличия срока сессии. CLI понимает оба формата даты OpenConnect, включая числовой часовой пояс. Срок может прийти до или после адреса; последующие сообщения не сдвигают начало сессии. До получения срока меню показывает «Срок сессии неизвестен». `vpn --status` также различает подключение и переподключение.
|
||
|
||
### Переподключение и защита входа
|
||
|
||
В автоматическом режиме (Keychain или Bitwarden) после завершения поднятого туннеля CLI проверяет остановку процессов и восстановление DNS, затем повторяет подключение через 5 секунд. При неудачных попытках до поднятия туннеля допускаются пять повторов с паузами 5, 10, 20, 40 и 60 секунд. Приложение отображает эти состояния; собственного второго цикла повторов у него нет. Ручной режим и отмена входа не запускают автоматические повторы.
|
||
|
||
Каждый этап автоматического входа отправляется один раз: переход от пароля к одноразовому коду разрешён. Видимая ошибка формы или повторный запрос уже отправленного этапа останавливает вход и все автоматические повторы. Ошибка остаётся в меню после очистки DNS. Проверьте пароль и код, затем запустите подключение вручную. Защита не вводит локальную блокировку по времени и не меняет ручную отправку формы.
|
||
|
||
Если проверка доступности обнаружит зависший туннель, приложение просит helper завершить только `openconnect` своей сессии. Запоздалый запрос не прерывает уже восстановившийся туннель. CLI остаётся работать и выполняет ту же проверку DNS перед повтором. При активном чужом VPN, незавершённых процессах или ошибке восстановления новая попытка не запускается. «Отключить» и «Выход» отменяют и ожидающий повтор.
|
||
|
||
### Восстановление DNS после перезагрузки
|
||
|
||
При запуске приложение проверяет, не остались ли настройки DNS после аварийно завершённого VPN. Перед подключением root-owned helper записывает исходные DNS сетевых сервисов и DNS, выданные VPN, в `/var/db/lemana-vpn-dns`. После обычного отключения или следующего запуска он возвращает прежние значения, включая пользовательские DNS; для автоматических настроек возвращается DHCP.
|
||
|
||
После «Отключить» root-helper отправляет `TERM` только `openconnect` зарегистрированной сессии. Проверяются пользователь, идентификатор сессии, загрузка системы, PID и время запуска; чужие VPN не останавливаются. До появления туннеля отменяются только пользовательские процессы входа этой сессии. Запись об отмене не позволяет запоздавшему дочернему процессу настроить сеть.
|
||
|
||
Меню показывает «Отключение», пока завершаются процессы VPN и проверяется восстановление DNS. Если дочерние процессы ещё выходят, CLI повторяет проверку до 30 раз с интервалом в секунду. «Выход» и Cmd+Q ждут завершения отключения и проверки DNS. Повторное нажатие не прерывает очистку. При ошибке или ожидании дольше 45 секунд приложение остаётся открытым с кнопкой «Повторить отключение»; принудительный `KILL` не применяется. Для этого должны быть обновлены вместе CLI, helper и приложение через `sh install.sh`.
|
||
|
||
Восстановление не запускает VPN, не трогает активное подключение и не сбрасывает произвольные адреса `10.x`. Проверяются процессы, сохранённая сетевая конфигурация и совпадение текущих DNS с записанными. Изменённые пользователем DNS сохраняются. Ошибка восстановления видна в меню; «Обновить статус» повторяет проверку. Пока проверка идёт или требуется исправление, «Подключить» недоступна.
|
||
|
||
```sh
|
||
vpn --repair-dns # Проверить и восстановить без подключения
|
||
vpn --repair-dns --json # Результат для приложения
|
||
```
|
||
|
||
`vpn-fix-dns` вызывает ту же проверку. Обычный `sh install.sh` обновляет helper, CLI и приложение; без обновления старого helper новое подключение заблокировано. Функция восстановления требует установленного правила sudoers и работает без запроса пароля.
|
||
|
||
Настройки подключения, сделанного **до этого обновления**, ещё не имеют сохранённого исходного DNS. Такие остатки автоматически не удаляются: по одному адресу нельзя отличить настройки VPN от пользовательских. Если DNS уже зависли от старой версии, сначала нужно один раз вернуть обычные настройки DNS, затем подключаться обновлённой версией. После этого защита сохраняет их для будущих восстановлений. При смене сетевой конфигурации (Location) незавершённая запись сохраняется до возврата к ней; новая сессия не перезаписывает её.
|
||
|
||
```sh
|
||
vpn --status
|
||
vpn --status --json
|
||
```
|
||
|
||
Human output показывает активный режим:
|
||
|
||
```text
|
||
Modules: ✅ core=ok, 🔐 mode=manual-keychain, ⏭️ bitwarden=off, ⏭️ touchid=off, ...
|
||
VPN disconnected
|
||
```
|
||
|
||
JSON содержит публичный mode contract:
|
||
|
||
```json
|
||
{
|
||
"modules": {
|
||
"mode": {
|
||
"id": "manual-keychain",
|
||
"label": "Вручную",
|
||
"ready": false,
|
||
"setup_hint": "Настройте логин и пароль: vpn --configure-login"
|
||
},
|
||
"auth": {
|
||
"mode": "manual",
|
||
"source": "keychain",
|
||
"auto_source": "keychain",
|
||
"label": "Вручную",
|
||
"detail": "Логин и пароль с этого Mac, код вводится вручную",
|
||
"ready": false,
|
||
"setup_hint": "Настройте логин и пароль: vpn --configure-login"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
Swift-приложение не вычисляет режим само. Оно читает `vpn --status --json` и сохраняет выбор через CLI. Унаследованные от запускающего shell переменные режима не подменяют настройку приложения.
|
||
|
||
## Что Ставится
|
||
|
||
| Путь | Назначение |
|
||
| --- | --- |
|
||
| `~/bin/vpn-lemanapro.sh` | основной CLI |
|
||
| `~/bin/uninstall-lemana-vpn.sh` | локальный uninstall helper |
|
||
| `~/bin/keychain-fingerprint` | optional Touch ID helper для Bitwarden |
|
||
| `~/Applications/LemanaVPN.app` | menu-bar приложение |
|
||
| `~/Library/LaunchAgents/ru.dokops.LemanaVPN.plist` | автозапуск приложения |
|
||
| `~/.config/lemana-vpn/env` | активный выбранный режим |
|
||
| `~/.config/openconnect-lite/config.toml` | профиль SSO и Keycloak autofill rules |
|
||
| `/usr/local/sbin/lemana-vpn-dns-cleanup` | root-owned DNS cleanup wrapper |
|
||
| `/var/db/lemana-vpn-dns` | Защищённый журнал исходных DNS и запись владельца VPN-сессии |
|
||
| `/etc/sudoers.d/lemana-vpn-openconnect` | `NOPASSWD` только для `openconnect` |
|
||
| `/etc/sudoers.d/lemana-vpn-dns` | `NOPASSWD` только для DNS cleanup wrapper |
|
||
|
||
Source-of-truth профили в репозитории:
|
||
|
||
- `templates/manual-keychain.env`
|
||
- `templates/keychain-auto.env`
|
||
- `templates/bitwarden-auto.env`
|
||
|
||
Installer рендерит один из них в `~/.config/lemana-vpn/env`.
|
||
|
||
## Настройки и журналы
|
||
|
||
Пользовательские параметры находятся в `~/.config/lemana-vpn/env`. Это shell-файл: используйте простые присваивания в кавычках, без команд. Пароли и код настройки 2FA остаются в Keychain.
|
||
|
||
| Параметр | Значение и назначение |
|
||
| --- | --- |
|
||
| `LEMANA_VPN_MODE` | `manual-keychain`, `keychain-auto` или `bitwarden-auto` |
|
||
| `LEMANA_VPN_BW_ITEM` | Имя записи Bitwarden, по умолчанию `LM LDAP` |
|
||
| `LEMANA_VPN_USE_TOUCHID` | `1` или `0`; используется в `bitwarden-auto` |
|
||
| `LEMANA_VPN_AUTO_SOURCE` | Последний источник автоматического входа: `keychain` или `bitwarden` |
|
||
| `LEMANA_VPN_LOG_MAX_BYTES` | Размер каждого файла журнала в байтах, по умолчанию `5242880` (5 МиБ) |
|
||
| `LEMANA_VPN_CONNECT_WAIT_SECONDS` | Интервал сообщения об ожидании SSO, по умолчанию `20` секунд |
|
||
|
||
Например, чтобы уменьшить журналы до 1 МиБ на файл, добавьте или измените строку:
|
||
|
||
```sh
|
||
LEMANA_VPN_LOG_MAX_BYTES="1048576"
|
||
```
|
||
|
||
Лимит — целое число от `1` до `999999999`. CLI применяет его при следующем запуске; приложение получает его из CLI при обновлении статуса. У каждого журнала в `~/Library/Logs` остаются текущий файл и один предыдущий `.1`: `LemanaVPN.log` и `LemanaVPN-openconnect-lite.log`. По умолчанию это до 20 МиБ суммарно. Ограничение действует при каждой записи, в том числе внутри долгого сеанса; от старого большого файла сохраняется только ограниченный хвост. Само изменение исходников существующие журналы не очищает — нужен обновлённый CLI и приложение.
|
||
|
||
Обновление сохраняет выбранный режим, логин, имя записи Bitwarden, Touch ID и дополнительные параметры в `env`. Флаги установщика имеют приоритет над переменными окружения, затем используются сохранённые значения и значения по умолчанию. Для CLI явно заданные переменные окружения режима, логина, Bitwarden, Touch ID и лимита журнала также имеют приоритет над `env`.
|
||
|
||
Существующий `~/.config/openconnect-lite/config.toml` сохраняет адрес, профиль и правила SSO; установщик обновляет только имя пользователя в `[credentials]`. Изменения шаблона SSO при обновлении автоматически не заменяют ваш профиль. Для логина и пароля используйте `vpn --configure-login`.
|
||
|
||
## Сохранение способа входа из CLI
|
||
|
||
```sh
|
||
vpn --set-auth=manual # ручной ввод кода
|
||
vpn --set-auth=keychain # автоматический вход с ключом на этом Mac
|
||
vpn --set-auth=bitwarden # автоматический вход через Bitwarden
|
||
vpn --set-auth=auto # последний выбранный автоматический источник
|
||
```
|
||
|
||
Команды только сохраняют настройку. Пароль и секреты не считываются и не изменяются, подключение не начинается. Готовность выбранного источника и нужный шаг настройки видны в `vpn --status --json`.
|
||
|
||
Импорт и синхронизация Bitwarden используют существующую запись Keychain `totp/<LDAP username>`. Поэтому название «Ключ на этом Mac» обозначает место хранения, а не гарантированное происхождение ключа.
|
||
|
||
## Совместимость
|
||
|
||
Ранее сохранённое сочетание `manual-keychain` + `LEMANA_VPN_LOCAL_OTP=1` теперь означает **полный автоматический вход** и при обновлении переводится в `keychain-auto`. Для ручной отправки и ввода кода выберите «Вручную» или `vpn --set-auth=manual`. Явные `vpn --manual` и `vpn --manual-full` принудительно отключают автоматический код независимо от сохранённого режима.
|
||
|
||
Старые команды оставлены как shims, чтобы существующие shell привычки не ломались:
|
||
|
||
```sh
|
||
vpn-auto # bitwarden-auto
|
||
vpn --auto # bitwarden-auto
|
||
vpn-manual # manual-keychain
|
||
vpn --manual # manual-keychain
|
||
vpn --saved-login # manual-keychain
|
||
```
|
||
|
||
Диагностический fallback без подстановки полей остаётся, но это не отдельный режим установки:
|
||
|
||
```sh
|
||
vpn --manual-full
|
||
vpn-manual-full
|
||
```
|
||
|
||
Он нужен только когда надо проверить, что проблема не в autofill.
|
||
|
||
## Обновление
|
||
|
||
Из checkout:
|
||
|
||
```sh
|
||
sh install.sh
|
||
vpn --status
|
||
open ~/Applications/LemanaVPN.app
|
||
```
|
||
|
||
Удалённо:
|
||
|
||
```sh
|
||
curl -fsSL https://git.dokops.ru/dokril/lemana-vpn/raw/branch/main/install.sh | sh
|
||
```
|
||
|
||
После изменения Swift app важно убедиться, что установленный бинарь обновился:
|
||
|
||
```sh
|
||
shasum app/.build/release/LemanaVPN ~/Applications/LemanaVPN.app/Contents/MacOS/LemanaVPN
|
||
strings ~/Applications/LemanaVPN.app/Contents/MacOS/LemanaVPN | rg 'manual-keychain|bitwarden-auto|import-yandex-key'
|
||
```
|
||
|
||
## Удаление
|
||
|
||
```sh
|
||
~/bin/uninstall-lemana-vpn.sh
|
||
```
|
||
|
||
Посмотреть действия без удаления:
|
||
|
||
```sh
|
||
~/bin/uninstall-lemana-vpn.sh --dry-run
|
||
```
|
||
|
||
## Разработка И Проверка
|
||
|
||
Для проверок нужен Python 3.11+ (`tomllib`), для изолированного исполнения JavaScript автозаполнения — Node.js. Браузер и настоящее подключение тесты не запускают; Node.js самому VPN не нужен.
|
||
|
||
```sh
|
||
sh -n install.sh
|
||
sh -n uninstall.sh
|
||
bash -n bin/vpn-lemanapro.sh
|
||
sh -n tests/smoke.sh
|
||
tests/smoke.sh
|
||
python3 tests/configuration.py
|
||
python3 tests/logging.py
|
||
python3 tests/menu.py
|
||
python3 tests/dns_cli.py
|
||
python3 tests/dns_recovery.py
|
||
python3 tests/session_stop.py
|
||
python3 tests/connection_runtime.py
|
||
python3 tests/auth_retry.py
|
||
python3 tests/auth_settings.py
|
||
"${LEMANA_VPN_OC_PYTHON:-$HOME/.local/pipx/venvs/openconnect-lite/bin/python}" tests/yandex_import.py
|
||
swift build -c release --package-path app
|
||
git diff --check
|
||
```
|
||
|
||
Live VPN проверяется только после статуса:
|
||
|
||
```sh
|
||
vpn --status
|
||
```
|
||
|
||
Если VPN уже connected, connecting или reconnecting, не запускай вторую сессию. Смотри логи:
|
||
|
||
```sh
|
||
tail -f ~/Library/Logs/LemanaVPN-openconnect-lite.log
|
||
tail -f ~/Library/Logs/LemanaVPN.log
|
||
```
|