docs(runbook): piege cle virtuelle Nabil-Key vs cles dediees Hermes (ticket infra-2026-09-014)
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
# Piège Clé Virtuelle Nabil-Key vs Clé Dédiée Consommateur Bifrost (04/09/2026)
|
||||
|
||||
## 1. Contexte & Problématique
|
||||
|
||||
Chaque instance Hermes et consommateur de la passerelle **Bifrost** dispose d'une clé virtuelle dédiée (Virtual Key ou VK) associée à une politique budgétaire propre dans la table `governance_budgets` :
|
||||
- `vk-hermes-tt` $\rightarrow$ budget TT
|
||||
- `vk-hermes-nyora` $\rightarrow$ budget Nyora
|
||||
- `vk-hermes-perso` $\rightarrow$ budget Perso
|
||||
- `Nabil-Key` $\rightarrow$ budget personnel Nabil (plafond strict : 8 \$/mois)
|
||||
|
||||
### Le Piège
|
||||
Lors de configurations initiales ou de migrations manuelles, la valeur secrète de **`Nabil-Key`** (`bfk-0cd1fba7d440ca2eefd30b28a7349b7d28422dcbc49e982d`) a été copiée par erreur dans `/opt/data/config.yaml` d'instances Hermes (`hermes-nyora`, `hermes-perso`).
|
||||
|
||||
### Symptômes
|
||||
1. **Facturation croisée** : L'ensemble des coûts de tokens (appels LLM principaux et/ou appels `auxiliary.vision` / `auxiliary.approval`) est débité du budget personnel de 8 \$/mois au lieu de la sphère appropriée.
|
||||
2. **Risque de coupure inopinée** : Lorsque le budget de 8 \$ est atteint, Bifrost rejette toutes les requêtes portant cette clé avec une erreur HTTP 429 / 401 (`budget exhausted`), bloquant à la fois l'usage personnel et l'instance Hermes concernée.
|
||||
|
||||
---
|
||||
|
||||
## 2. Procédure de Diagnostic
|
||||
|
||||
### Étape 1 : Récupérer les clés virtuelles officielles depuis Bifrost
|
||||
Sur l'hôte hébergeant Bifrost (actuellement VPS Contabo `100.94.90.119` ou snapshot de base) :
|
||||
```bash
|
||||
python3 -c "
|
||||
import sqlite3
|
||||
conn = sqlite3.connect('/home/dsh-agent/bifrost/data/config.db')
|
||||
c = conn.cursor()
|
||||
c.execute('SELECT name, value, is_active FROM governance_virtual_keys;')
|
||||
for name, val, active in c.fetchall():
|
||||
print(f'{name}: {val[:12]}... (actif={active})')
|
||||
"
|
||||
```
|
||||
*Correspondances attendues :*
|
||||
- `vk-hermes-nyora` : commence par `sk-bf-90d6...`
|
||||
- `vk-hermes-perso` : commence par `sk-bf-bb06...`
|
||||
- `vk-hermes-tt` : commence par `sk-bf-2669...`
|
||||
- `Nabil-Key` : commence par `bfk-0cd1...` (à proscrire dans les conteneurs d'agents)
|
||||
|
||||
### Étape 2 : Auditer `config.yaml` dans chaque instance Hermes
|
||||
Sur le NAS Synology :
|
||||
```bash
|
||||
for inst in hermes-agent-tt hermes-agent-nyora hermes-agent-perso; do
|
||||
echo "=== $inst ==="
|
||||
docker exec $inst python3 -c '
|
||||
import yaml
|
||||
cfg = yaml.safe_load(open("/opt/data/config.yaml"))
|
||||
m_key = cfg.get("model", {}).get("api_key", "")
|
||||
print(" model.api_key:", m_key[:12] if m_key else "vide")
|
||||
for k, v in cfg.get("auxiliary", {}).items():
|
||||
if isinstance(v, dict) and v.get("api_key"):
|
||||
print(f" auxiliary.{k}.api_key:", v["api_key"][:12])
|
||||
'
|
||||
done
|
||||
```
|
||||
**Alerte immédiate** si une clé commence par `bfk-0cd1...`.
|
||||
|
||||
---
|
||||
|
||||
## 3. Procédure de Correction Chirurgicale
|
||||
|
||||
Ne jamais réécrire l'intégralité du fichier `config.yaml` (risque d'écraser des réglages runtime ou de corrompre l'indentation YAML). Utiliser un remplacement chirurgical par `sed`.
|
||||
|
||||
### 1. Sauvegarde préalable obligatoire
|
||||
```bash
|
||||
docker exec <nom_conteneur> cp /opt/data/config.yaml /opt/data/config.yaml.bak-$(date +%Y%m%d)-cle-perso
|
||||
```
|
||||
|
||||
### 2. Remplacement chirurgical de la valeur
|
||||
```bash
|
||||
# Exemple pour hermes-agent-perso avec sa clé vk-hermes-perso
|
||||
docker exec <nom_conteneur> sed -i 's/bfk-0cd1fba7d440ca2eefd30b28a7349b7d28422dcbc49e982d/<VRAIE_CLE_VK>/g' /opt/data/config.yaml
|
||||
```
|
||||
|
||||
### 3. Contrôle du diff
|
||||
```bash
|
||||
docker exec <nom_conteneur> diff -u /opt/data/config.yaml.bak-$(date +%Y%m%d)-cle-perso /opt/data/config.yaml
|
||||
```
|
||||
*Le diff ne doit comporter strictement que les lignes d'affectation de clé modifiées.*
|
||||
|
||||
### 4. Redémarrage propre du conteneur
|
||||
```bash
|
||||
docker restart <nom_conteneur>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Validation Post-Correction par Inférence Réelle
|
||||
|
||||
Exécuter un appel réel depuis l'intérieur du conteneur pour valider le bon fonctionnement de bout en bout :
|
||||
|
||||
```bash
|
||||
docker exec <nom_conteneur> python3 -c '
|
||||
import yaml, urllib.request, json, time
|
||||
|
||||
cfg = yaml.safe_load(open("/opt/data/config.yaml"))
|
||||
m = cfg.get("model", {})
|
||||
base_url = m.get("base_url", "http://bifrost-proxy/v1").rstrip("/")
|
||||
api_key = m.get("api_key", "")
|
||||
|
||||
url = base_url + "/chat/completions"
|
||||
payload = {
|
||||
"model": m.get("default", "mimo-v2.5"),
|
||||
"messages": [{"role": "user", "content": "ping"}],
|
||||
"max_tokens": 10
|
||||
}
|
||||
req = urllib.request.Request(
|
||||
url,
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": f"Bearer {api_key}"
|
||||
},
|
||||
data=json.dumps(payload).encode()
|
||||
)
|
||||
with urllib.request.urlopen(req, timeout=15) as resp:
|
||||
data = json.loads(resp.read().decode())
|
||||
print("Inférence OK :", data.get("model"), "en", data.get("extra_fields", {}).get("latency"), "ms")
|
||||
'
|
||||
```
|
||||
*Attendu : `Inférence OK : mimo-v2.5 en <latence> ms`.*
|
||||
Reference in New Issue
Block a user