Уточни TOTP seed и статус модулей VPN
This commit is contained in:
71
README.md
71
README.md
@@ -2,11 +2,13 @@
|
||||
|
||||
CLI-установка корпоративного VPN `vpn.lemanapro.ru` для macOS.
|
||||
|
||||
**Модули по умолчанию:** Core: включён; Bitwarden: включён; Touch ID: включён; DNS cleanup: включён; runtime-патчи: применяются автоматически перед подключением.
|
||||
|
||||
Репозиторий собирает в один воспроизводимый пакет то, что раньше было ручной локальной настройкой:
|
||||
|
||||
- `openconnect` как VPN-клиент;
|
||||
- `openconnect-lite` для SAML SSO через Keycloak;
|
||||
- опциональный Bitwarden CLI для LDAP-пароля и TOTP;
|
||||
- опциональный Bitwarden CLI для LDAP-пароля и TOTP seed;
|
||||
- опциональный Touch ID helper для мастер-пароля Bitwarden;
|
||||
- безопасный DNS cleanup через root-owned wrapper;
|
||||
- алиасы `vpn`, `vpn-debug`, `vpn-fix-dns`.
|
||||
@@ -38,7 +40,7 @@ curl -fsSL https://git.dokops.ru/dokril/lemana-vpn/raw/branch/main/install.sh |
|
||||
curl -fsSL https://git.dokops.ru/dokril/lemana-vpn/raw/branch/main/install.sh | sh -s -- --without-touchid
|
||||
```
|
||||
|
||||
Минимальная установка без Bitwarden и Touch ID. Пароль LDAP и TOTP будут один раз записаны в macOS Keychain вручную:
|
||||
Минимальная установка без Bitwarden и Touch ID. В macOS Keychain вручную будут записаны LDAP-пароль и TOTP secret. Не текущий 30-секундный TOTP-код, а постоянный seed из настройки 2FA.
|
||||
|
||||
```sh
|
||||
curl -fsSL https://git.dokops.ru/dokril/lemana-vpn/raw/branch/main/install.sh | sh -s -- --minimal --configure-keychain
|
||||
@@ -72,6 +74,35 @@ curl -fsSL https://example.org/dokril/lemana-vpn/raw/branch/main/install.sh \
|
||||
| `/etc/sudoers.d/lemana-vpn-dns` | `NOPASSWD` только для DNS cleanup wrapper |
|
||||
| `~/.zshrc` | Идемпотентный блок алиасов `vpn`, `vpn-debug`, `vpn-fix-dns` |
|
||||
|
||||
## Статус модулей
|
||||
|
||||
`vpn` и `vpn --status` первой строкой показывают, какие модули включены в конфиге и реально установлены на машине:
|
||||
|
||||
```sh
|
||||
vpn --status
|
||||
Modules: core=ok, bitwarden=on, touchid=on, dns=on, patches=active, keychain=password:yes/totp_seed:yes
|
||||
VPN disconnected
|
||||
```
|
||||
|
||||
Значения:
|
||||
|
||||
| Поле | Значение |
|
||||
| --- | --- |
|
||||
| `core=ok` | Есть `openconnect`, `openconnect-lite` и config |
|
||||
| `bitwarden=on` | Модуль включён и `bw` установлен |
|
||||
| `bitwarden=off` | Модуль отключён через `--without-bitwarden` или `LEMANA_VPN_USE_BITWARDEN=0` |
|
||||
| `bitwarden=missing` | Модуль включён, но `bw` не найден |
|
||||
| `touchid=on/off/missing` | Состояние Touch ID helper |
|
||||
| `dns=on/missing` | Наличие DNS cleanup wrapper |
|
||||
| `patches=active/pending` | Применены ли runtime-патчи `openconnect-lite` |
|
||||
| `keychain=password:yes/totp_seed:yes` | Есть ли LDAP-пароль и TOTP seed в Keychain |
|
||||
|
||||
JSON-режим тоже отдаёт модульный статус:
|
||||
|
||||
```sh
|
||||
vpn --status --json
|
||||
```
|
||||
|
||||
## Модули
|
||||
|
||||
### Core
|
||||
@@ -87,7 +118,9 @@ curl -fsSL https://example.org/dokril/lemana-vpn/raw/branch/main/install.sh \
|
||||
|
||||
### Bitwarden
|
||||
|
||||
Включён по умолчанию. CLI при каждом запуске `vpn` пытается получить LDAP-пароль и TOTP из записи Bitwarden `LM LDAP`, затем записывает их в macOS Keychain для `openconnect-lite`.
|
||||
Включён по умолчанию. CLI при каждом запуске `vpn` пытается получить LDAP-пароль и TOTP seed из записи Bitwarden `LM LDAP`, затем записывает их в macOS Keychain для `openconnect-lite`.
|
||||
|
||||
TOTP seed — это постоянный секрет 2FA. Сам одноразовый TOTP-код меняется каждые 30 секунд и генерируется `openconnect-lite` в момент входа.
|
||||
|
||||
Отключить:
|
||||
|
||||
@@ -101,6 +134,34 @@ sh install.sh --without-bitwarden
|
||||
vpn-lemanapro.sh --configure-keychain
|
||||
```
|
||||
|
||||
### Если Bitwarden нет
|
||||
|
||||
Bitwarden не обязателен. Без него установка работает как обычный `openconnect-lite` profile с секретами в macOS Keychain.
|
||||
|
||||
Установка:
|
||||
|
||||
```sh
|
||||
curl -fsSL https://git.dokops.ru/dokril/lemana-vpn/raw/branch/main/install.sh \
|
||||
| sh -s -- --without-bitwarden --without-touchid --configure-keychain
|
||||
```
|
||||
|
||||
Что понадобится:
|
||||
|
||||
- LDAP username;
|
||||
- LDAP password;
|
||||
- TOTP secret из корпоративной 2FA настройки.
|
||||
|
||||
Важно: вводить нужно не текущие 6 цифр из authenticator-приложения, а постоянный secret. Обычно он есть в QR-коде как `secret=BASE32...` или может быть показан при ручной настройке TOTP.
|
||||
|
||||
Если secret есть только в QR-коде:
|
||||
|
||||
1. Открой QR-код в приложении/на портале, где настраивалась 2FA.
|
||||
2. Найди режим ручной настройки, где показывается secret.
|
||||
3. Если доступен только QR, его нужно расшифровать любым локальным QR-сканером и взять параметр `secret`.
|
||||
4. Вставь secret в prompt `TOTP secret (BASE32...)`.
|
||||
|
||||
Если TOTP secret получить нельзя, автоматический headless-вход невозможен: `openconnect-lite` не сможет сам генерировать свежий TOTP-код на каждом входе.
|
||||
|
||||
### Touch ID
|
||||
|
||||
Включён по умолчанию. Установщик собирает `keychain-fingerprint` из `https://github.com/dss99911/keychain-fingerprint.git` и кладёт бинарник в `~/bin/keychain-fingerprint`.
|
||||
@@ -128,7 +189,7 @@ vpn-fix-dns # сбросить корпоративные DNS после
|
||||
1. CLI проверит `bw`.
|
||||
2. Если vault locked, попросит мастер-пароль.
|
||||
3. Если установлен Touch ID helper, предложит сохранить мастер-пароль за Touch ID prompt.
|
||||
4. Достанет `LM LDAP`, запишет LDAP-пароль и TOTP в Keychain.
|
||||
4. Достанет `LM LDAP`, запишет LDAP-пароль и TOTP seed в Keychain.
|
||||
5. Запустит `openconnect-lite` и пройдёт Keycloak SSO.
|
||||
|
||||
## Настройка
|
||||
@@ -159,6 +220,8 @@ curl -fsSL https://git.dokops.ru/dokril/lemana-vpn/raw/branch/main/install.sh \
|
||||
- password: LDAP пароль;
|
||||
- TOTP: `otpauth://...secret=BASE32...` или raw BASE32 secret.
|
||||
|
||||
Это не 6-значный одноразовый код. В Bitwarden должен лежать постоянный TOTP secret, из которого коды генерируются автоматически.
|
||||
|
||||
## Почему DNS wrapper, а не wildcard sudoers
|
||||
|
||||
Старый вариант давал `NOPASSWD` на `networksetup -setdnsservers *`. Это слишком широкое право: любой локальный процесс пользователя мог поменять DNS на произвольный сервер.
|
||||
|
||||
@@ -45,7 +45,7 @@ Usage: vpn-lemanapro.sh [--debug] [--json] [--status] [--configure-keychain]
|
||||
--status --json Show current VPN status as JSON
|
||||
--debug Run visible browser and passthrough debug logs
|
||||
--json Emit JSON Lines events for UI wrappers
|
||||
--configure-keychain Prompt for LDAP password/TOTP and save them to Keychain
|
||||
--configure-keychain Prompt for LDAP password and TOTP secret, then save them to Keychain
|
||||
HELP
|
||||
exit 0
|
||||
;;
|
||||
@@ -75,11 +75,113 @@ _json_get() {
|
||||
python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('$key',''))" 2>/dev/null || true
|
||||
}
|
||||
|
||||
_find_webengine_process() {
|
||||
if [[ -n "${LEMANA_VPN_WEBENGINE_PROCESS:-}" ]]; then
|
||||
printf '%s\n' "$LEMANA_VPN_WEBENGINE_PROCESS"
|
||||
return 0
|
||||
fi
|
||||
find "$OC_VENV/lib" -path '*/site-packages/openconnect_lite/browser/webengine_process.py' -print -quit 2>/dev/null
|
||||
}
|
||||
|
||||
_module_bool() {
|
||||
if "$@" >/dev/null 2>&1; then
|
||||
printf true
|
||||
else
|
||||
printf false
|
||||
fi
|
||||
}
|
||||
|
||||
_patches_active() {
|
||||
local wep
|
||||
wep="$(_find_webengine_process)"
|
||||
[[ -n "$wep" && -f "$wep" ]] || return 1
|
||||
grep -q '"offscreen"' "$wep" \
|
||||
&& grep -q 'new Event("input", {{bubbles: true}})' "$wep" \
|
||||
&& grep -q 'new RegExp' "$wep"
|
||||
}
|
||||
|
||||
_keychain_has() {
|
||||
security find-generic-password -s "$1" -a "$2" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
_module_status_json() {
|
||||
local openconnect_installed openconnect_lite_installed bitwarden_installed touchid_installed dns_cleanup_installed
|
||||
local config_present oc_config_present patch_backup_present patches_active keychain_password keychain_totp_seed
|
||||
openconnect_installed="$(_module_bool command -v openconnect)"
|
||||
openconnect_lite_installed="$(_module_bool test -x "$OC_BIN")"
|
||||
bitwarden_installed="$(_module_bool command -v bw)"
|
||||
touchid_installed="$(_module_bool test -x "$KC_FP")"
|
||||
dns_cleanup_installed="$(_module_bool test -x "$DNS_CLEANUP")"
|
||||
config_present="$(_module_bool test -f "$CONFIG_FILE")"
|
||||
oc_config_present="$(_module_bool test -f "$HOME/.config/openconnect-lite/config.toml")"
|
||||
patch_backup_present="$(_module_bool test -f "$PATCH_BACKUP_DIR/webengine_process.py.before-lemana-vpn")"
|
||||
patches_active="$(_module_bool _patches_active)"
|
||||
keychain_password="$(_module_bool _keychain_has openconnect-lite "$KC_USERNAME")"
|
||||
keychain_totp_seed="$(_module_bool _keychain_has openconnect-lite "totp/$KC_USERNAME")"
|
||||
|
||||
printf '{"core":{"openconnect":%s,"openconnect_lite":%s,"config":%s,"openconnect_lite_config":%s},"bitwarden":{"enabled":%s,"installed":%s,"item":"%s"},"touchid":{"enabled":%s,"installed":%s},"keychain":{"password":%s,"totp_seed":%s},"dns_cleanup":{"installed":%s},"patches":{"active":%s,"backup":%s}}' \
|
||||
"$openconnect_installed" \
|
||||
"$openconnect_lite_installed" \
|
||||
"$config_present" \
|
||||
"$oc_config_present" \
|
||||
"$([[ "$USE_BITWARDEN" == "1" ]] && printf true || printf false)" \
|
||||
"$bitwarden_installed" \
|
||||
"$BW_ITEM_NAME" \
|
||||
"$([[ "$USE_TOUCHID" == "1" ]] && printf true || printf false)" \
|
||||
"$touchid_installed" \
|
||||
"$keychain_password" \
|
||||
"$keychain_totp_seed" \
|
||||
"$dns_cleanup_installed" \
|
||||
"$patches_active" \
|
||||
"$patch_backup_present"
|
||||
}
|
||||
|
||||
_module_human_part() {
|
||||
local name="$1" enabled="$2" installed="$3"
|
||||
if [[ "$enabled" == "0" ]]; then
|
||||
printf '%s=off' "$name"
|
||||
elif [[ "$installed" == "true" ]]; then
|
||||
printf '%s=on' "$name"
|
||||
else
|
||||
printf '%s=missing' "$name"
|
||||
fi
|
||||
}
|
||||
|
||||
_module_status_human() {
|
||||
local core bitwarden_installed touchid_installed dns_cleanup_installed patches_active keychain_password keychain_totp_seed
|
||||
if command -v openconnect >/dev/null 2>&1 && [[ -x "$OC_BIN" && -f "$HOME/.config/openconnect-lite/config.toml" ]]; then
|
||||
core="core=ok"
|
||||
else
|
||||
core="core=missing"
|
||||
fi
|
||||
|
||||
bitwarden_installed="$(_module_bool command -v bw)"
|
||||
touchid_installed="$(_module_bool test -x "$KC_FP")"
|
||||
dns_cleanup_installed="$(_module_bool test -x "$DNS_CLEANUP")"
|
||||
patches_active="$(_module_bool _patches_active)"
|
||||
keychain_password="$(_module_bool _keychain_has openconnect-lite "$KC_USERNAME")"
|
||||
keychain_totp_seed="$(_module_bool _keychain_has openconnect-lite "totp/$KC_USERNAME")"
|
||||
|
||||
printf 'Modules: %s, ' "$core"
|
||||
_module_human_part "bitwarden" "$USE_BITWARDEN" "$bitwarden_installed"
|
||||
printf ', '
|
||||
_module_human_part "touchid" "$USE_TOUCHID" "$touchid_installed"
|
||||
printf ', dns=%s, patches=%s, keychain=password:%s/totp_seed:%s\n' \
|
||||
"$([[ "$dns_cleanup_installed" == "true" ]] && printf on || printf missing)" \
|
||||
"$([[ "$patches_active" == "true" ]] && printf active || printf pending)" \
|
||||
"$([[ "$keychain_password" == "true" ]] && printf yes || printf no)" \
|
||||
"$([[ "$keychain_totp_seed" == "true" ]] && printf yes || printf no)"
|
||||
}
|
||||
|
||||
_check_status() {
|
||||
local modules_json
|
||||
modules_json="$(_module_status_json)"
|
||||
|
||||
if [[ ! -f "$STATUS_FILE" ]]; then
|
||||
if $JSON_MODE; then
|
||||
printf '%s\n' '{"state":"disconnected","reason":"no status file"}'
|
||||
printf '{"state":"disconnected","reason":"no status file","modules":%s}\n' "$modules_json"
|
||||
else
|
||||
_module_status_human
|
||||
printf '%s\n' "VPN disconnected (нет status-файла)"
|
||||
fi
|
||||
return 0
|
||||
@@ -100,8 +202,9 @@ _check_status() {
|
||||
|
||||
if [[ "$state" != "connected" ]] || ! $process_alive; then
|
||||
if $JSON_MODE; then
|
||||
printf '{"state":"disconnected","reason":"%s"}\n' "$(! $process_alive && printf 'process dead (pid=%s)' "$pid" || printf '%s' "$state")"
|
||||
printf '{"state":"disconnected","reason":"%s","modules":%s}\n' "$(! $process_alive && printf 'process dead (pid=%s)' "$pid" || printf '%s' "$state")" "$modules_json"
|
||||
else
|
||||
_module_status_human
|
||||
printf '%s\n' "VPN disconnected"
|
||||
fi
|
||||
return 0
|
||||
@@ -125,22 +228,15 @@ _check_status() {
|
||||
fi
|
||||
|
||||
if $JSON_MODE; then
|
||||
printf '{"state":"connected","ip":"%s","healthy":%s,"remaining_sec":%s,"expires":"%s","pid":%s,"dns_target":"%s"}\n' \
|
||||
"$ip" "$healthy" "$remaining_sec" "$expires" "$pid" "$dns_target"
|
||||
printf '{"state":"connected","ip":"%s","healthy":%s,"remaining_sec":%s,"expires":"%s","pid":%s,"dns_target":"%s","modules":%s}\n' \
|
||||
"$ip" "$healthy" "$remaining_sec" "$expires" "$pid" "$dns_target" "$modules_json"
|
||||
else
|
||||
_module_status_human
|
||||
printf 'VPN connected - %s (session: %sh %sm, tunnel: %s)\n' \
|
||||
"$ip" "$hours" "$mins" "$($healthy && printf healthy || printf unhealthy)"
|
||||
fi
|
||||
}
|
||||
|
||||
_find_webengine_process() {
|
||||
if [[ -n "${LEMANA_VPN_WEBENGINE_PROCESS:-}" ]]; then
|
||||
printf '%s\n' "$LEMANA_VPN_WEBENGINE_PROCESS"
|
||||
return 0
|
||||
fi
|
||||
find "$OC_VENV/lib" -path '*/site-packages/openconnect_lite/browser/webengine_process.py' -print -quit 2>/dev/null
|
||||
}
|
||||
|
||||
_patch_oc() {
|
||||
local wep
|
||||
wep="$(_find_webengine_process)"
|
||||
@@ -433,6 +529,12 @@ fi
|
||||
|
||||
trap '_dns_cleanup; _clear_status' EXIT
|
||||
|
||||
if ! $JSON_MODE; then
|
||||
_module_status_human
|
||||
else
|
||||
printf '{"event":"modules","modules":%s}\n' "$(_module_status_json)"
|
||||
fi
|
||||
|
||||
_patch_oc
|
||||
_sync_bitwarden
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ Options:
|
||||
--without-bitwarden Do not install/use Bitwarden CLI; use Keychain credentials
|
||||
--with-touchid Install/use keychain-fingerprint Touch ID helper (default)
|
||||
--without-touchid Do not install/use Touch ID helper
|
||||
--configure-keychain Prompt for LDAP password/TOTP after install
|
||||
--configure-keychain Prompt for LDAP password and TOTP secret after install
|
||||
--username VALUE Corporate LDAP username (default: 60103293)
|
||||
--bw-item VALUE Bitwarden item name (default: LM LDAP)
|
||||
--raw-base-url URL Raw file base URL for curl installs
|
||||
|
||||
Reference in New Issue
Block a user