7.4 KiB
Local Yandex Key OTP Import Implementation Plan
Intent: Import the user's selected VPN OTP from an official local Yandex Key export and generate it on this Mac without Bitwarden.
Current Behavior: Keychain/PyOTP and OTP autofill already exist, but manual-keychain disables OTP and the auto mode uses Bitwarden; there is no backup importer.
Expected Outcome: Installer and CLI expose a local wizard: choose file and entry, verify the current phone code, save only the selected secret in Keychain and enable local OTP. Existing manual setup remains the default.
Target-Perspective Output: A synthetic export imported through the real wizard yields one verified Keychain write and the persisted local OTP flag; a fake client receives shown browser, OTP on, submit off. Cancellation/invalid input yields no writes or launch.
Truth Owner: openconnect-lite Keychain account totp/<LDAP username> owns the secret; env owns LEMANA_VPN_LOCAL_OTP; CLI owns status and launch flags. Python helper owns backup parsing and verified import only.
Contract Boundary: New CLI --import-yandex-key, installer option/question, optional status metadata. No secret in shell arguments, env, output, errors or logs. Only the helper accesses the selected secret in memory.
Cutover: Opt-in LEMANA_VPN_LOCAL_OTP=1 changes manual-keychain OTP from off to on after verification and persistence. Browser stays visible, submit stays off. Existing bitwarden-auto and full-manual behavior remain.
Displaced Path: The unconditional manual-mode OTP-off branch becomes a validated optional setting; no new generator, cloud extractor, third auth mode or alternate Keychain store.
Value Density: Reuse existing keyring/PyOTP dependencies and existing runtime patches. One helper next to CLI, installed/uninstalled with it.
Acceptance Evidence: Deterministic tests of parsing, real PyOTP RFC fixture verification, fake Keychain writes/rollback, CLI dispatch and fake-client capture; status readiness and installer preservation; full automated regression ladder.
Evidence Lane: Temporary homes, synthetic public test secrets and stub Keychain only. No actual user export, Keychain, GUI, live VPN, install or uninstall.
Kill Criteria: No unsupported OTP silently accepted; no writes before verification; failed activation restores prior Keychain entry; no OTP enabled by mere presence of a saved seed; full-manual never fills OTP.
Architecture Slice: install.sh -> installed CLI -> sibling lemana-vpn-import-yandex.py via OC Python -> existing Keychain + atomic env update -> CLI status/launch -> Swift status. uninstall.sh removes the helper. DNS/reconnect/runtime SSO patches and unrelated dirty work are excluded.
Plan Review Gate: Requires PRE review before execution.
Input and lifecycle contract
- Verified plaintext format: JSON array with
name,secret,techInfo(OTP URI); primary implementations: GNOME Authenticator, Yandex export help. - Accept only
otpauth://totp, SHA1, 6 digits, 30 seconds, valid Base32. Reject duplicate critical parameters, secret mismatch, HOTP/YAOTP and unsupported parameters. Never fetch metadata URLs. Mixed backups may contain unsupported records; validate the selected entry. - Bound file reading to 4 MiB and entries to 1000; sanitize/cap display names. Require a real terminal at production entry, use hidden input for current phone code, allow EOF/interrupt to cancel without repeated prompts.
- Require saved LDAP username/password first (
vpn --configure-login). Show destination account and the visible/no-submit behavior. Verify current/adjacent 30-second code locally, then write selected secret and enable manual-keychain/local OTP. Prepare the mode-0600 env replacement before changing Keychain; roll back the old secret if activation fails. Preserve unrelated env settings, no backup copy retained or removed automatically. LEMANA_VPN_LOCAL_OTPis 0 by default, only 0/1 accepted; invocation environment overrides env file. Reset to 0 in env to return to manual codes while retaining the saved seed. Bitwarden ignores this flag; full-manual ignores it for launch/preflight.- Status must distinguish local OTP readiness and missing-seed guidance without reading/displaying secrets. Existing older CLI/Swift decoding remains compatible.
Ordered tasks
- Root implements Python helper and focused tests (
bin/lemana-vpn-import-yandex.py,tests/yandex_import.py). Read-only explorer/review agents validate format/security. Test synthetic success, wrong code/selection, malformed/unsupported input, EOF, secret-safe output, storage/config failures and rollback. - Root integrates CLI flag/config, local preflight, status and launch (
bin/vpn-lemanapro.sh) plus manual readiness decoding (VPNManager.swift). Preserve old command/event shapes where unchanged. Add fake-launch/status/no-TTY tests totests/smoke.shand focused wizard tests. - Root integrates installer question/flag and helper packaging/removal (
install.sh,uninstall.sh), documents workflow and limits (README.md). Existing unowned-env preservation retains local OTP across updates; add evidence totests/configuration.py. No extra GUI controls. - Read-only correctness/maintainability/POST plan review, fix blockers, then final syntax, wizard/config/logging tests, smoke and Swift release build. Main agent owns edits and integration; independent read-only work can run in parallel.
Risks
Wrong OTP type/parameters produces silent login failures; unsupported settings must fail before persistence. Wrong selected account is addressed by explicit selection and phone-code verification. File/Keychain errors must not silently switch modes or replace usable credentials. Real macOS Keychain prompts and live SSO remain unverified without a separate current request.
Completed evidence
- PRE and POST plan/maintainability reviews passed. Security review findings resolved: saved/effective/runtime LDAP binding, strict parameter allowlist, case/whitespace-safe secret masking before label truncation, Keychain readback, and a consistent commit point with interruption handling.
tests/yandex_import.pypasses with the existing OC Python. Synthetic RFC TOTP data exercise valid import, mixed exports, unsupported types/parameters, malformed/oversized files, EOF/wrong code, Keychain/config failures, rollback/commit and silent-backend failure.- Automated PTY drives the actual CLI and sibling helper against a temporary fake Keychain: verified one-account write, unchanged LDAP password, no echoed phone code/secret, persisted setting and ready CLI JSON. Runtime account mismatches and explicit local-OTP override are checked.
- Shell syntax,
tests/smoke.sh,tests/configuration.py(10 checks),tests/logging.py,git diff --check: PASS. Smoke captures local OTP on, full-manual off, missing seed blocking launch, unchanged Bitwarden flow, installer action/packaging and uninstall removal. swift build -c release --package-path app: PASS (3.29 s). Restricted run hit compiler cache and nested sandbox restrictions; approved build escalation completed without launching the app.- No real export, real Keychain writes, real installation, GUI or live VPN acceptance was performed. Abrupt process death/power loss across separate Keychain/filesystem stores remains a documented limitation.