Files
rpa_vision_v3/core/auth/manage_vault.py
Dom d5deac3029 feat: replay visuel VLM-first, worker séparé, package Léa, AZERTY, sécurité HTTPS
Pipeline replay visuel :
- VLM-first : l'agent appelle Ollama directement pour trouver les éléments
- Template matching en fallback (seuil strict 0.90)
- Stop immédiat si élément non trouvé (pas de clic blind)
- Replay depuis session brute (/replay-session) sans attendre le VLM
- Vérification post-action (screenshot hash avant/après)
- Gestion des popups (Enter/Escape/Tab+Enter)

Worker VLM séparé :
- run_worker.py : process distinct du serveur HTTP
- Communication par fichiers (_worker_queue.txt + _replay_active.lock)
- Le serveur HTTP ne fait plus jamais de VLM → toujours réactif
- Service systemd rpa-worker.service

Capture clavier :
- raw_keys (vk + press/release) pour replay exact indépendant du layout
- Fix AZERTY : ToUnicodeEx + AltGr detection
- Enter capturé comme \n, Tab comme \t
- Filtrage modificateurs seuls (Ctrl/Alt/Shift parasites)
- Fusion text_input consécutifs, dédup key_combo

Sécurité & Internet :
- HTTPS Let's Encrypt (lea.labs + vwb.labs.laurinebazin.design)
- Token API fixe dans .env.local
- HTTP Basic Auth sur VWB
- Security headers (HSTS, CSP, nosniff)
- CORS domaines publics, plus de wildcard

Infrastructure :
- DPI awareness (SetProcessDpiAwareness) Python + Rust
- Métadonnées système (dpi_scale, window_bounds, monitors, os_theme)
- Template matching multi-scale [0.5, 2.0]
- Résolution dynamique (plus de hardcode 1920x1080)
- VLM prefill fix (47x speedup, 3.5s au lieu de 180s)

Modules :
- core/auth/ : credential vault (Fernet AES), TOTP (RFC 6238), auth handler
- core/federation/ : LearningPack export/import anonymisé, FAISS global
- deploy/ : package Léa (config.txt, Lea.bat, install.bat, LISEZMOI.txt)

UX :
- Filtrage OS (VWB + Chat montrent que les workflows de l'OS courant)
- Bibliothèque persistante (cache local + SQLite)
- Clustering hybride (titre fenêtre + DBSCAN)
- EdgeConstraints + PostConditions peuplés
- GraphBuilder compound actions (toutes les frappes)

Agent Rust :
- Token Bearer auth (network.rs)
- sysinfo.rs (DPI, résolution, window bounds via Win32 API)
- config.txt lu automatiquement
- Support Chrome/Brave/Firefox (pas que Edge)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 10:19:18 +01:00

214 lines
7.2 KiB
Python

#!/usr/bin/env python3
"""
CLI de gestion du coffre-fort de credentials (vault).
Usage :
# Ajouter un login
python -m core.auth.manage_vault --vault /path/to/vault.enc --action add \
--app "DPI_Crossway" --type login \
--username "robot_lea" --password "xxx"
# Ajouter un seed TOTP
python -m core.auth.manage_vault --vault /path/to/vault.enc --action add \
--app "DPI_Crossway" --type totp_seed \
--secret "JBSWY3DPEHPK3PXP"
# Lister les applications configurées
python -m core.auth.manage_vault --vault /path/to/vault.enc --action list
# Générer un code TOTP
python -m core.auth.manage_vault --vault /path/to/vault.enc --action generate-totp \
--app "DPI_Crossway"
# Supprimer un credential
python -m core.auth.manage_vault --vault /path/to/vault.enc --action remove \
--app "DPI_Crossway" --type login
Le mot de passe maître est demandé interactivement via getpass.
"""
import argparse
import getpass
import sys
from .credential_vault import CredentialVault
from .totp_generator import TOTPGenerator
def main():
parser = argparse.ArgumentParser(
description="Gestionnaire de coffre-fort de credentials pour Léa.",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=__doc__,
)
parser.add_argument(
"--vault",
required=True,
help="Chemin du fichier vault chiffré",
)
parser.add_argument(
"--action",
required=True,
choices=["add", "list", "remove", "generate-totp", "show"],
help="Action à effectuer",
)
parser.add_argument("--app", help="Nom de l'application")
parser.add_argument(
"--type",
dest="cred_type",
choices=["login", "totp_seed", "session_token", "certificate"],
help="Type de credential",
)
# Champs pour le type "login"
parser.add_argument("--username", help="Nom d'utilisateur (type login)")
parser.add_argument("--password", help="Mot de passe (type login)")
parser.add_argument("--domain", help="Domaine Windows (type login, optionnel)")
# Champs pour le type "totp_seed"
parser.add_argument("--secret", help="Secret base32 (type totp_seed)")
parser.add_argument(
"--digits", type=int, default=6, help="Nombre de chiffres TOTP (défaut: 6)"
)
parser.add_argument(
"--interval", type=int, default=30, help="Intervalle TOTP en secondes (défaut: 30)"
)
parser.add_argument(
"--algorithm", default="SHA1", help="Algorithme HMAC (défaut: SHA1)"
)
args = parser.parse_args()
# Demander le mot de passe maître
master_password = getpass.getpass("Mot de passe maître : ")
if not master_password:
print("Erreur : mot de passe maître requis.", file=sys.stderr)
sys.exit(1)
try:
vault = CredentialVault(args.vault, master_password)
except ValueError as e:
print(f"Erreur d'ouverture du vault : {e}", file=sys.stderr)
sys.exit(1)
# ---- Actions ----
if args.action == "list":
apps = vault.list_apps()
if not apps:
print("Vault vide — aucune application configurée.")
else:
print(f"Applications configurées ({len(apps)}) :")
for app in apps:
types = vault.list_credential_types(app)
print(f" {app} : {', '.join(types)}")
elif args.action == "add":
if not args.app:
print("Erreur : --app requis pour l'action 'add'.", file=sys.stderr)
sys.exit(1)
if not args.cred_type:
print("Erreur : --type requis pour l'action 'add'.", file=sys.stderr)
sys.exit(1)
if args.cred_type == "login":
if not args.username:
args.username = input("Username : ")
if not args.password:
args.password = getpass.getpass("Password : ")
data = {"username": args.username, "password": args.password}
if args.domain:
data["domain"] = args.domain
elif args.cred_type == "totp_seed":
if not args.secret:
args.secret = input("Secret base32 : ")
data = {
"secret": args.secret,
"digits": args.digits,
"interval": args.interval,
"algorithm": args.algorithm,
}
elif args.cred_type == "session_token":
token = input("Token de session : ")
data = {"token": token}
elif args.cred_type == "certificate":
cert_path = input("Chemin du certificat : ")
key_path = input("Chemin de la clé privée : ")
data = {"cert_path": cert_path, "key_path": key_path}
else:
print(f"Type non géré : {args.cred_type}", file=sys.stderr)
sys.exit(1)
vault.add_credential(args.app, args.cred_type, data)
vault.save()
print(f"Credential ajouté : {args.app} / {args.cred_type}")
elif args.action == "remove":
if not args.app or not args.cred_type:
print(
"Erreur : --app et --type requis pour l'action 'remove'.",
file=sys.stderr,
)
sys.exit(1)
removed = vault.remove_credential(args.app, args.cred_type)
if removed:
vault.save()
print(f"Credential supprimé : {args.app} / {args.cred_type}")
else:
print(f"Credential non trouvé : {args.app} / {args.cred_type}")
elif args.action == "generate-totp":
if not args.app:
print(
"Erreur : --app requis pour l'action 'generate-totp'.",
file=sys.stderr,
)
sys.exit(1)
totp_creds = vault.get_credential(args.app, "totp_seed")
if not totp_creds:
print(
f"Pas de seed TOTP configuré pour '{args.app}'.",
file=sys.stderr,
)
sys.exit(1)
totp = TOTPGenerator(
secret=totp_creds["secret"],
digits=totp_creds.get("digits", 6),
interval=totp_creds.get("interval", 30),
algorithm=totp_creds.get("algorithm", "SHA1"),
)
code = totp.generate()
remaining = totp.time_remaining()
print(f"Code TOTP : {code}")
print(f"Expire dans : {remaining}s")
elif args.action == "show":
if not args.app:
print(
"Erreur : --app requis pour l'action 'show'.",
file=sys.stderr,
)
sys.exit(1)
types = vault.list_credential_types(args.app)
if not types:
print(f"Aucun credential pour '{args.app}'.")
else:
print(f"Credentials pour '{args.app}' :")
for cred_type in types:
cred = vault.get_credential(args.app, cred_type)
# Masquer les mots de passe et secrets
display = {}
for k, v in (cred or {}).items():
if k in ("password", "secret", "token"):
display[k] = v[:3] + "***" if len(str(v)) > 3 else "***"
else:
display[k] = v
print(f" {cred_type} : {display}")
if __name__ == "__main__":
main()