Aller au contenu
VDRVOZit Detection & Response

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

CodeSignificationComportement attendu de l'agent
200 / 204acceptépasser à la suite
400corps ou élément invalide ; bad_seq désigne l'élément fautifsauter l'élément fautif
401jeton inconnu, révoqué, organisation suspendue ou IP source non autoriséeréessayer avec délai, journaliser une fois par heure
413plus de 500 éléments ou plus de 1 Mocouper le lot en deux
429débit dépassé (Retry-After: 30)délai exponentiel
5xxconsole indisponibledé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 (accepted ne le compte pas).
  • scenario : [a-z0-9_]{1,64} ; severity : info, moyenne, haute, critique ; ip : IPv4/IPv6 ou null.
  • message (4 000 caractères), user et action (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'