new connection

This commit is contained in:
boyko_vn 2026-07-15 10:36:55 +03:00
parent 9f37fd8b78
commit 70004e0645
5 changed files with 41 additions and 7 deletions

6
1.py Normal file
View File

@ -0,0 +1,6 @@
import keyring
# keyring.get_password(service_name, username)
value = keyring.get_password("openvpn-totp-offad", "static")
print(value) # None, если ничего не сохранено

View File

@ -21,3 +21,7 @@
Правятся вручную или через пункт меню «Открыть настройки»; перечитываются на лету.
Секреты (`secret`, `static`) в конфиге не хранятся — они в keyring под именем `keyring_service`.
## Сервис для декодирования
https://2fafast.com/tools/qr-decoder/

View File

@ -25,14 +25,15 @@ def main():
static = os.environ.get("OVPN_STATIC_PASS")
missing = [name for name, val in
(("OVPN_TOTP_SECRET", secret), ("OVPN_STATIC_PASS", static))
(("OVPN_TOTP_SECRET", secret),)
if not val]
if missing:
sys.exit(f"Не заданы переменные окружения: {', '.join(missing)}")
service = keyring_service()
keyring.set_password(service, "secret", secret)
keyring.set_password(service, "static", static)
if static:
keyring.set_password(service, "static", static)
print(f"Сохранено в keyring (сервис: {service}).")
if __name__ == "__main__":

View File

@ -2,7 +2,7 @@
"""launch_vpn.py — auth = static+TOTP, конкатенация без разделителя.
Параметры берутся из общего vpn_config.json (см. launch_vpn_gui.py);
секреты из keyring."""
import subprocess, pyotp, keyring, tempfile, json, os
import subprocess, pyotp, keyring, tempfile, json, os, base64
CONFIG_PATH = os.path.join(
os.path.dirname(os.path.abspath(__file__)), "vpn_config.json")
@ -28,12 +28,18 @@ def main():
cfg = load_config()
service = cfg.get("keyring_service", "openvpn-totp")
secret = keyring.get_password(service, "secret")
static = keyring.get_password(service, "static")
# static — необязателен: если его нет в keyring, пароль = только TOTP-код.
static = keyring.get_password(service, "static") or ""
code = pyotp.TOTP(secret).now()
if cfg.get("static_challenge"):
password = (f"SCRV1:{base64.b64encode(static.encode()).decode()}"
f":{base64.b64encode(code.encode()).decode()}")
else:
password = f"{static}{code}"
fd, path = tempfile.mkstemp(suffix=".txt")
try:
with os.fdopen(fd, "w", newline="\n") as f:
f.write(f"{cfg['username']}\n{static}{code}\n")
f.write(f"{cfg['username']}\n{password}\n")
subprocess.run([cfg["openvpn_bin"], "--config", cfg["ovpn_config"],
"--auth-user-pass", path])
finally:

View File

@ -7,6 +7,7 @@ import threading
import tempfile
import json
import os
import base64
import pyotp
import keyring
import pystray
@ -32,6 +33,12 @@ DEFAULT_CONFIG = {
"dev_node": "OpenVPN Data Channel Offload",
# имя сервиса в keyring, откуда берутся secret (TOTP) и static (пароль).
"keyring_service": "openvpn-totp",
# формат передачи OTP серверу.
# False -> пароль = static+code одной строкой (сервер сам отрезает OTP).
# True -> формат SCRV1 (static и OTP кодируются раздельно в base64); нужен
# для серверов со static-challenge. Саму строку static-challenge при
# этом надо УБРАТЬ из .ovpn, иначе openvpn спросит код интерактивно.
"static_challenge": False,
# путь к лог-файлу openvpn.
"log_path": "ovpn.log",
@ -153,14 +160,24 @@ def is_running() -> bool:
return proc is not None and proc.poll() is None
def auth_password(static: str, code: str) -> str:
"""Собрать поле пароля для auth-user-pass с учётом режима static_challenge."""
if config.get("static_challenge"):
s = base64.b64encode(static.encode()).decode()
r = base64.b64encode(code.encode()).decode()
return f"SCRV1:{s}:{r}"
return f"{static}{code}"
def build_auth_file() -> str:
service = config.get("keyring_service", "openvpn-totp")
secret = keyring.get_password(service, "secret")
static = keyring.get_password(service, "static")
# static — необязателен: если его нет в keyring, пароль = только TOTP-код.
static = keyring.get_password(service, "static") or ""
code = pyotp.TOTP(secret).now()
fd, path = tempfile.mkstemp(suffix=".txt")
with os.fdopen(fd, "w", newline="\n") as f:
f.write(f"{config['username']}\n{static}{code}\n")
f.write(f"{config['username']}\n{auth_password(static, code)}\n")
return path