Exploitation self-host¶
Les équipes qui exécutent CVE Radar sur des serveurs internes, Kubernetes ou Docker ont besoin de capacités production dans la même base MIT : journaux audit JSON, montage de secrets, RBAC, multi-tenant PostgreSQL, métriques Prometheus, miroirs airgap et découverte de stack Kubernetes.
Pas de niveau enterprise ni de verrou licence. Les variables d'environnement ne modifient que le comportement opérationnel. Dépannage : Exploitation ; env de base : Configuration.
flowchart TB
classDef ui fill:#e9edf5,stroke:#00baba,color:#253343
classDef api fill:#f3fcfc,stroke:#008c8c,color:#253343
classDef data fill:#fff7ed,stroke:#eda232,color:#253343
classDef ext fill:#f5f5f5,stroke:#666,color:#253343
subgraph deploy["Déploiement self-host"]
Browser[SPA navigateur]:::ui
API[Express API]:::api
PG[(PostgreSQL optionnel)]:::data
Metrics["GET /metrics"]:::api
Audit[stdout audit JSON]:::ext
end
Browser --> API
API --> PG
API --> Metrics
API --> Audit
API --> Feeds[NVD / OSV / miroirs]:::ext
API --> Notify[Slack / SMTP / webhooks]:::ext
Le schéma montre Postgres optionnel, scrape Prometheus sur /metrics, audit stdout et notifications après watch. En airgap, les feeds publics sont remplacés par des miroirs locaux (voir Déploiement airgap).
Journaux audit¶
CVE Radar peut émettre une ligne JSON par événement audit sur stdout pour ELK, Loki, Splunk ou le driver Docker json-file.
| action | Quand |
|---|---|
scan |
Après chaque POST /api/scan |
watch |
Après chaque POST /api/watch |
health |
Si AUDIT_HEALTH=true et health détaillé |
export |
Pas encore côté serveur (export navigateur) |
{
"audit": true,
"ts": "2026-06-06T14:30:00.000Z",
"action": "scan",
"ip": "10.0.0.42",
"stack": ["redis", "nginx"],
"duration_ms": 8420,
"sources_failed": ["NVD"],
"result_count": 12,
"mode": "full"
}
Réglez TRUST_PROXY_HOPS derrière un reverse proxy. Titres CVE et clés API jamais journalisés.
docker logs cve-radar 2>&1 | grep '"audit":true'
Secrets et production¶
| env direct | env fichier (*_FILE) |
|---|---|
NVD_API_KEY |
NVD_API_KEY_FILE |
GITHUB_TOKEN |
GITHUB_TOKEN_FILE |
DEEPL_API_KEY |
DEEPL_API_KEY_FILE |
ALERT_WEBHOOK_URL |
ALERT_WEBHOOK_URL_FILE |
NOTIFICATION_SLACK_WEBHOOK_URL |
— |
NOTIFICATION_DISCORD_WEBHOOK_URL |
— |
NOTIFICATION_TELEGRAM_BOT_TOKEN |
— |
NOTIFICATION_SMTP_PASS |
mount via env file Compose |
API_SECRET |
API_SECRET_FILE |
Exemple : docker-compose.secrets.example.yml. Sur Kubernetes, montez un Secret en fichiers.
RBAC et authentification API¶
Avec API_SECRET, toutes les routes /api/* sauf GET /api/health et GET /api/v1/health exigent X-Api-Key ou Bearer. API_ROLE :
| Rôle | Permissions |
|---|---|
| admin | Réglages, CRUD stacks tenant, scan/watch, translate, meta et history |
| scanner | scan/watch/validate, translate, meta et history |
| viewer | meta et history — pas de scan |
| auditor | history/trends et meta — pas de scan |
Par défaut admin. Un viewer sur POST /api/scan reçoit 403 { "code": "FORBIDDEN" }.
Notifications watch¶
Les opérateurs self-host ont souvent besoin d'alertes hors bande quand le watch planifié détecte de nouveaux CVE. CVE Radar envoie les notifications serveur de façon asynchrone après POST /api/watch avec newVulns non vide — la réponse HTTP est inchangée.
| Canal | env principales |
|---|---|
| Slack | NOTIFICATION_SLACK_WEBHOOK_URL ou legacy ALERT_WEBHOOK_URL |
| Discord | NOTIFICATION_DISCORD_WEBHOOK_URL |
| Telegram | NOTIFICATION_TELEGRAM_BOT_TOKEN, NOTIFICATION_TELEGRAM_CHAT_ID |
| E-mail SMTP | NOTIFICATION_SMTP_HOST, FROM, TO ; optionnel PORT, USER, PASS |
| Webhook générique | NOTIFICATION_WEBHOOK_URL |
| Contrôle | env | Défaut |
|---|---|---|
| Sévérité min. | NOTIFICATION_MIN_SEVERITY / ALERT_MIN_SEVERITY |
HIGH |
| Fenêtre dedup (ms) | NOTIFICATION_DEDUP_MS |
900000 |
| Format Slack | ALERT_WEBHOOK_FORMAT |
slack / generic |
Le webhook Slack legacy (ALERT_WEBHOOK_URL) fonctionne via NotificationService. NOTIFICATION_* accepte les mounts *_FILE. Vérifiez : GET /api/health?detailed=true → alerts.webhookConfigured. Voir Alertes et NOTIFICATIONS.md.
Multi-tenant (PostgreSQL)¶
DATABASE_URL=postgres://cve_radar:cve_radar@127.0.0.1:5432/cve_radar
Les migrations s'exécutent au premier pool. Schéma dans server/db/schema.ts (Drizzle ORM) pour migration ORM progressive ; les requêtes runtime tenant/stack et scan-history passent par Drizzle via getDb() (pool pg partagé).
X-Tenant-Id: arvancloud-sre
| Method | Path | Description |
|---|---|---|
POST |
/api/v1/tenants |
Créer tenant |
GET |
/api/v1/tenants/stacks |
Lister stacks |
POST |
/api/v1/tenants/stacks |
Créer stack |
GET |
/api/v1/tenants/stacks/:id |
Stack par UUID |
PUT |
/api/v1/tenants/stacks/:id |
Mettre à jour |
DELETE |
/api/v1/tenants/stacks/:id |
Supprimer |
GET |
/api/v1/scans/history |
History tenant |
GET |
/api/v1/scans/trends |
Trends tenant |
History filtrée par tenant_id. Routes legacy /api/* sous tenant default.
Prometheus et Grafana¶
GET /metrics. METRICS_ENABLED (true par défaut), METRICS_PROTECT pour auth.
| Métrique | Type | Description |
|---|---|---|
cve_radar_scans_total |
counter | scan/watch |
cve_radar_vulns_found |
gauge | dernier scan réussi |
cve_radar_scan_duration_seconds |
histogram | durée handler |
cve_radar_source_reachable |
gauge | upstream |
cve_radar_cache_entries_total |
gauge | taille cache |
Dashboard : docs/self-hosted/grafana/cve-radar-dashboard.json.
sum(rate(cve_radar_scans_total{status="success"}[5m]))
/ sum(rate(cve_radar_scans_total[5m]))
Déploiement airgap¶
AIRGAPPED=true
NVD_MIRROR_URL=http://internal-mirror/nvd
KEV_MIRROR_URL=http://internal-mirror/kev/catalog.json
# OSV — mirror ou bulk :
OSV_MIRROR_URL=http://internal-mirror/osv
# OSV_BULK_PATH=/data/osv/extracted
Avec OSV_BULK_PATH, OSV lit l’arborescence JSON locale (scripts/sync-osv-bulk.sh / make sync-osv-bulk) sans API query. GitHub, RSS et traduction externe ignorés. Mirror NVD/KEV manquant → fail-closed. jq .airgap sur health détaillé. scripts/sync-mirrors.sh, scripts/sync-osv-bulk.sh.
Découverte Kubernetes¶
K8S_DISCOVERY_ENABLED=true
# K8S_DISCOVERY_NAMESPACES=production,staging
GET /api/v1/discovery/kubernetes — auth requise en production. Désactivé → 503 K8S_DISCOVERY_DISABLED.
{
"enabled": true,
"images": ["haproxy", "nginx", "redis"],
"tools": ["HAProxy", "Nginx", "Redis"],
"unmapped": ["my-sidecar"]
}
ServiceAccount lecture seule sur deployments. Combiner avec isolation tenant.
Référence rapide¶
| Sujet | env |
|---|---|
| Audit | AUDIT_HEALTH, TRUST_PROXY_HOPS |
| Secrets | mount *_FILE |
| RBAC | API_SECRET, API_ROLE |
| Notifications | NOTIFICATION_*, ALERT_WEBHOOK_URL |
| Tenant | DATABASE_URL, X-Tenant-Id |
| Métriques | METRICS_ENABLED, METRICS_PROTECT |
| Airgap | AIRGAPPED, *_MIRROR_URL, OSV_BULK_PATH |
| K8s | K8S_DISCOVERY_ENABLED, K8S_DISCOVERY_NAMESPACES |
| Enrichment | EPSS_ENABLED, COMPLIANCE_ENABLED |
Copies maintainer : docs/self-hosted/.
Accueil · Précédent : Exploitation