API agent (protocole v1)
Protocole entre l'agent Vigie et la console. Il est la référence pour les deux côtés : toute évolution incompatible passera par /api/v2.
Généralités
- HTTPS uniquement, corps JSON UTF-8. Base :
https://vdr.vozit.dev/api/v1/agent/. - En-tête
Authorization: Bearer vdr_…sur chaque requête. L'identité du serveur est déduite du jeton ; un nom d'hôte envoyé n'est jamais utilisé pour identifier. - Horodatages : secondes Unix décimales.
- Limites : 500 éléments et 1 Mo par requête, 300 requêtes par minute et par serveur.
Codes de retour
| Code | Signification | Comportement attendu de l'agent |
|---|---|---|
| 200 / 204 | accepté | passer à la suite |
| 400 | corps ou élément invalide ; bad_seq désigne l'élément fautif | sauter l'élément fautif |
| 401 | jeton inconnu, révoqué, organisation suspendue ou IP source non autorisée | réessayer avec délai, journaliser une fois par heure |
| 413 | plus de 500 éléments ou plus de 1 Mo | couper le lot en deux |
| 429 | débit dépassé (Retry-After: 30) | délai exponentiel |
| 5xx | console indisponible | délai exponentiel (5 s → 300 s) |
Les erreurs ont la forme {"error": "code"}.
POST /heartbeat
{ "version": "0.2.0", "mode": "observation", "ts": 1792560000.0,
"boot_id": "e18600926f5c48dcb4d5ee404567e5f3", "uptime": 58211,
"stats": { "alerts_24h": 3, "bans_active": 1, "events_per_min": 12.4 },
"checks": { "last_run": 1792559900.0, "anomalies": 0 },
"whitelist": ["127.0.0.0/8", "176.118.172.99/32"],
"system": { "hostname": "pve-02", "os": "Debian GNU/Linux 12 (bookworm)", "kernel": "6.1.0-49-amd64", "arch": "x86_64", "python": "3.11.2" } }
Réponse 204. version et mode (observation ou protection) sont obligatoires ; whitelist et system sont facultatifs (ajouts compatibles de l'agent 0.2.0). La console enregistre le dernier contact, l'écart d'horloge (ts − heure de la console) et trace les redémarrages (changement de boot_id), changements de mode et de version.
POST /alerts
{ "items": [
{ "seq": 1287, "ts": 1788212381.0, "scenario": "connexion_apres_echecs", "severity": "haute",
"ip": "62.60.130.193", "user": "root@pam", "action": null, "message": "connexion reussie de root@pam …" } ] }
Réponse 200 {"accepted": 1, "last_seq": 1287}.
seq: entier > 0, unique par serveur (identifiant SQLite de l'agent). Renvoyer un élément déjà reçu ne crée pas de doublon (acceptedne le compte pas).scenario:[a-z0-9_]{1,64};severity:info,moyenne,haute,critique;ip: IPv4/IPv6 ounull.message(4 000 caractères),useretaction(255) sont tronqués ; aucune donnée n'est interprétée.- Élément invalide :
400 {"error": "invalid_item", "bad_seq": N, "accepted": n, "last_seq": s}. Les éléments valides du même lot sont enregistrés.
POST /bans
{ "items": [
{ "seq": 311, "ip": "62.60.130.193", "ts": 1787227847.0, "until": 1789819847.0,
"scenario": "bruteforce_lent", "simulated": true, "lifted": false } ] }
Même fonctionnement que /alerts. Une levée est une nouvelle ligne avec lifted: true et until == ts ; la console clôt alors les bans antérieurs de cette IP sur ce serveur.
Si l'organisation partage les bans, un ban bruteforce_lent ou scan_ports de moins de 24 h crée une décision de ban pour les autres serveurs (au plus une par IP et par 24 h).
GET /decisions?after=N
{ "items": [
{ "id": 42, "type": "unban", "ip": "203.0.113.5" },
{ "id": 43, "type": "ban", "ip": "198.51.100.7", "duration": 86400, "reason": "partage : bruteforce_lent sur pve-02" } ] }
Décisions d'identifiant supérieur à after, destinées à ce serveur ou à tous, publiées après l'enregistrement du serveur et depuis moins de 28 jours ; 500 au plus. La console note la remise. L'agent mémorise le plus grand id reçu et valide chaque décision : IP valide, hors liste blanche, durée de 1 à 2 419 200 s ; ban réel seulement en mode protection, sinon simulé.
POST /inventory
Ajout compatible du protocole v1 (agent 0.3 et plus). Dernier état du serveur ; remplace le précédent. Envoyé au démarrage, à chaque changement, et au moins toutes les 6 h. Limite : 8 Mo.
{ "ts": 1792560000.0,
"os": { "family": "debian", "id": "debian", "name": "Debian GNU/Linux 12 (bookworm)", "version": "12",
"kernel": "6.1.0-49-amd64", "arch": "x86_64", "reboot_required": false, "uptime": 58211 },
"packages": [ { "name": "openssh-server", "version": "1:9.2p1-2+deb12u3", "arch": "amd64" } ],
"updates": [ { "name": "openssl", "current": "3.0.13-1~deb12u1", "available": "3.0.14-1~deb12u2", "security": true } ],
"updates_checked": 1792559000.0,
"listening": [ { "proto": "tcp", "addr": "0.0.0.0", "port": 22, "process": "sshd", "exe": "/usr/sbin/sshd" } ],
"sca": [ { "id": "ssh.permit_root_login", "title": "…", "status": "pass|fail|na", "detail": "…", "ref": "…", "weight": 3 } ],
"sca_score": 82 }
Réponse 204. Bornes : 20 000 paquets, 5 000 mises à jour, 2 000 ports, 500 contrôles. Si 413, l'agent renvoie l'inventaire sans packages. Un inventaire sans sca conserve les derniers résultats de durcissement.
Exemple avec curl
curl -sS -X POST https://vdr.vozit.dev/api/v1/agent/heartbeat \
-H "Authorization: Bearer $(cat /etc/vigie/console.token)" -H 'Content-Type: application/json' \
-d '{"version":"0.2.0","mode":"observation","ts":'"$(date +%s)"'}' -w '%{http_code}\n'