Require LDAP username on first VPN launch

This commit is contained in:
2026-05-26 14:30:21 +03:00
parent f2d4f8e04b
commit b8ddf8f22d
6 changed files with 210 additions and 26 deletions

View File

@@ -3,6 +3,7 @@ set -euo pipefail
CONFIG_DIR="${LEMANA_VPN_CONFIG_DIR:-$HOME/.config/lemana-vpn}"
CONFIG_FILE="$CONFIG_DIR/env"
OC_CONFIG_DIR="${OPENCONNECT_LITE_CONFIG_DIR:-$HOME/.config/openconnect-lite}"
_ENV_LEMANA_VPN_USERNAME="${LEMANA_VPN_USERNAME+x}${LEMANA_VPN_USERNAME-}"
_ENV_LEMANA_VPN_BW_ITEM="${LEMANA_VPN_BW_ITEM+x}${LEMANA_VPN_BW_ITEM-}"
@@ -27,7 +28,7 @@ OC_VENV="${LEMANA_VPN_OC_VENV:-$HOME/.local/pipx/venvs/openconnect-lite}"
OC_PYTHON="${LEMANA_VPN_OC_PYTHON:-$OC_VENV/bin/python}"
OC_BIN="${LEMANA_VPN_OC_BIN:-$HOME/.local/bin/openconnect-lite}"
BW_ITEM_NAME="${LEMANA_VPN_BW_ITEM:-LM LDAP}"
KC_USERNAME="${LEMANA_VPN_USERNAME:-60103293}"
KC_USERNAME="${LEMANA_VPN_USERNAME:-}"
KC_FP="${LEMANA_VPN_KEYCHAIN_FINGERPRINT:-$HOME/bin/keychain-fingerprint}"
CREDENTIAL_SOURCE="${LEMANA_VPN_CREDENTIAL_SOURCE:-}"
if [[ -z "$CREDENTIAL_SOURCE" ]]; then
@@ -648,7 +649,99 @@ PY
}
_can_prompt() {
[[ -t 0 ]]
[[ -r /dev/tty && -w /dev/tty ]] || [[ -t 0 ]]
}
_prompt_read() {
local var_name="$1" prompt="$2" secret="${3:-0}" value
if [[ -r /dev/tty && -w /dev/tty ]]; then
if [[ "$secret" == "1" ]]; then
IFS= read -rsp "$prompt" value < /dev/tty || value=""
printf '\n' > /dev/tty
else
IFS= read -rp "$prompt" value < /dev/tty || value=""
fi
else
if [[ "$secret" == "1" ]]; then
IFS= read -rsp "$prompt" value || value=""
printf '\n'
else
IFS= read -rp "$prompt" value || value=""
fi
fi
printf -v "$var_name" '%s' "$value"
}
_persist_username() {
KC_USERNAME="$KC_USERNAME" CONFIG_FILE="$CONFIG_FILE" OC_CONFIG_FILE="$OC_CONFIG_DIR/config.toml" python3 - <<'PY'
import json
import os
from pathlib import Path
username = os.environ["KC_USERNAME"]
config_file = Path(os.environ["CONFIG_FILE"])
oc_config_file = Path(os.environ["OC_CONFIG_FILE"])
def shell_quote(value: str) -> str:
return '"' + value.replace("\\", "\\\\").replace('"', '\\"').replace("$", "\\$").replace("`", "\\`") + '"'
config_file.parent.mkdir(parents=True, exist_ok=True)
lines = []
if config_file.exists():
lines = config_file.read_text().splitlines()
username_line = "LEMANA_VPN_USERNAME=" + shell_quote(username)
for index, line in enumerate(lines):
if line.startswith("LEMANA_VPN_USERNAME="):
lines[index] = username_line
break
else:
lines.insert(0, username_line)
config_file.write_text("\n".join(lines).rstrip() + "\n")
config_file.chmod(0o600)
if oc_config_file.exists():
oc_lines = oc_config_file.read_text().splitlines()
rendered = "username = " + json.dumps(username)
for index, line in enumerate(oc_lines):
if line.strip().startswith("username"):
oc_lines[index] = rendered
break
else:
oc_lines.insert(0, rendered)
oc_config_file.write_text("\n".join(oc_lines).rstrip() + "\n")
oc_config_file.chmod(0o600)
PY
}
_ensure_username() {
if [[ -n "${KC_USERNAME:-}" ]]; then
return 0
fi
_emit '{"event":"username_required"}' "LDAP username is missing. Enter it once to save it for future VPN runs."
if ! _can_prompt; then
_emit '{"event":"error","message":"LDAP username is missing. Run vpn in Terminal once, or reinstall with --username <LDAP_USERNAME>."}' \
"LDAP username is missing. Run in Terminal: vpn"
return 1
fi
while [[ -z "${KC_USERNAME:-}" ]]; do
_prompt_read KC_USERNAME "Corporate LDAP username: "
KC_USERNAME="${KC_USERNAME#"${KC_USERNAME%%[![:space:]]*}"}"
KC_USERNAME="${KC_USERNAME%"${KC_USERNAME##*[![:space:]]}"}"
if [[ -z "$KC_USERNAME" ]]; then
printf 'LDAP username is required.\n' >&2
fi
done
export LEMANA_VPN_USERNAME="$KC_USERNAME"
_persist_username
_emit '{"event":"username_saved"}' "LDAP username saved for future VPN runs."
}
_configure_keychain() {
@@ -664,17 +757,15 @@ _configure_keychain() {
printf 'Saved values go to macOS Keychain service openconnect-lite.\n\n'
if $password_present; then
read -rsp "Corporate LDAP password for $KC_USERNAME [leave empty to keep saved password]: " password
_prompt_read password "Corporate LDAP password for $KC_USERNAME [leave empty to keep saved password]: " 1
else
read -rsp "Corporate LDAP password for $KC_USERNAME: " password
_prompt_read password "Corporate LDAP password for $KC_USERNAME: " 1
fi
printf '\n'
if $totp_present; then
read -rsp "TOTP seed BASE32 [leave empty to keep saved seed]: " totp_secret
_prompt_read totp_secret "TOTP seed BASE32 [leave empty to keep saved seed]: " 1
else
read -rsp "TOTP seed BASE32 from 2FA setup: " totp_secret
_prompt_read totp_secret "TOTP seed BASE32 from 2FA setup: " 1
fi
printf '\n'
if [[ -z "$password" && "$password_present" != "true" ]]; then
printf 'LDAP password is required because no saved password was found.\n' >&2
@@ -769,8 +860,7 @@ _bw_unlock() {
fi
_emit '{"event":"bw_manual"}' "Bitwarden vault is locked. Enter Bitwarden master password to sync LDAP credentials."
read -rsp "Bitwarden master password (not LDAP password): " manual_pw
printf '\n'
_prompt_read manual_pw "Bitwarden master password (not LDAP password): " 1
if [[ -z "$manual_pw" ]]; then
printf 'Empty Bitwarden password. Using existing Keychain credentials.\n' >&2
return 1
@@ -786,7 +876,7 @@ _bw_unlock() {
if [[ "$USE_TOUCHID" == "1" && -x "$KC_FP" ]]; then
local save_choice
read -rp "Save Bitwarden master password behind Touch ID for next VPN unlock? [Y/n] " save_choice
_prompt_read save_choice "Save Bitwarden master password behind Touch ID for next VPN unlock? [Y/n] "
if [[ "${save_choice:-y}" =~ ^[Yy]?$ ]]; then
printf '%s' "$manual_pw" | "$KC_FP" set "$BW_KC_SERVICE" "$BW_KC_ACCOUNT_MASTER" >/dev/null 2>&1 \
&& printf 'Saved. Next unlock can use Touch ID.\n' \
@@ -921,6 +1011,7 @@ if $STATUS_MODE; then
fi
if $CONFIGURE_KEYCHAIN_MODE; then
_ensure_username
_configure_keychain
exit 0
fi
@@ -936,6 +1027,7 @@ else
printf '{"event":"modules","modules":%s}\n' "$(_module_status_json)"
fi
_ensure_username
_sync_credentials
_ensure_keychain_credentials
_patch_oc