feat(wp-c): méthode verify_token côté registre (patch 3, inerte)

Ajoute AgentRegistry.verify_token(token) -> machine_id|None : compare le
SHA-256 du token aux token_hash des agents 'active' via hmac.compare_digest
(temps constant). Agent désinstallé/révoqué refusé ; rotation à l'enroll
invalide l'ancien token.

Inerte au runtime : méthode non branchée sur l'auth HTTP (le branchement
derrière flag RPA_FLEET_PER_AGENT_TOKEN sera le Patch 4). api_stream.py
intouché. TDD : 6 tests + non-régression WP-C/WP-B (53 verts). Voir
PLAN-WPC-TDD-EXECUTABLE.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dom
2026-06-10 14:21:04 +02:00
parent 9fb2c7bfee
commit b20d17882e
2 changed files with 89 additions and 0 deletions

View File

@@ -29,6 +29,7 @@ Schema de la table `enrolled_agents` :
from __future__ import annotations
import hashlib
import hmac
import logging
import os
import secrets
@@ -172,6 +173,31 @@ class AgentRegistry:
).fetchone()
return int(row["n"]) if row else 0
def verify_token(self, token: str | None) -> Optional[str]:
"""WP-C : verifie un token poste, retourne le machine_id actif ou None.
Compare le SHA-256 du token presente aux `token_hash` des agents
`status='active'` via `hmac.compare_digest` (comparaison a temps
constant, evite les fuites par timing). Un agent desinstalle/revoque
n'est pas 'active' donc refuse ; la rotation a l'enrolement invalide
l'ancien token.
INERTE : non branchee sur l'auth runtime (le branchement derriere flag
sera le Patch 4). Aucun appelant runtime a ce stade.
"""
if not token:
return None
token_hash = hashlib.sha256(token.encode("utf-8")).hexdigest()
with _DB_LOCK, self._connect() as conn:
rows = conn.execute(
"SELECT machine_id, token_hash FROM enrolled_agents "
"WHERE status = 'active' AND token_hash IS NOT NULL"
).fetchall()
for row in rows:
if hmac.compare_digest(str(row["token_hash"]), token_hash):
return str(row["machine_id"])
return None
# ------------------------------------------------------------------
# Ecriture
# ------------------------------------------------------------------