Skip to content

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
API_SECRET_PREVIOUS API_SECRET_PREVIOUS_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" }.

Pack d'audit sécurité

Pour les revues entreprise / GRC (#259) :

Artefact Rôle
docs/security/THREAT_MODEL.md Menaces STRIDE-lite
docs/security/DEPENDENCY_AUDIT.md Procédure d'audit des dépendances
docs/self-hosted/SECRETS.md Rotation API_SECRET + API_SECRET_PREVIOUS sans coupure
SECURITY.md OIDC et SAML via fédération vers OIDC (pas de SP natif ; ADR 007)

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=truealerts.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.

Si X-Tenant-Id est envoyé mais mal formé, l’API renvoie 400 TENANT_SLUG_INVALID — pas de repli silencieux sur default.

Checklist multi-tenant

Sujet Comportement
Isolation des données SQL avec WHERE tenant_id = … (stacks, history, prefs notifications).
Validation header Slug valide si header présent ; slug inconnu → 404 TENANT_NOT_FOUND.
Slugs réservés default, system, admin, root, postgres, public interdits à la création (409 TENANT_SLUG_RESERVED).
Auth / RBAC API_SECRET et OIDC optionnel ; routes admin tenant exigent le rôle admin.
Rate limits scan/watch par IP + tenant sur v1.
Audit Lignes audit avec tenant ; GET /api/v1/audit limité au tenant courant.
Notifications Prefs et webhooks tenant avec X-Tenant-Id ; dedup inclut le slug.
Routes legacy /api/* sans version écrit l’history sous default.
Health GET /api/health?detailed=truedatabase.multiTenant ; capabilities → features.multiTenant.

Docker Compose : docker compose --profile postgres up -d et DATABASE_URL — voir TENANTS.md.

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 source
cve_radar_mirror_age_days gauge mirror
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
# MIRROR_MANIFEST_PATH=/data/mirrors/.mirror-manifest.json
# MIRROR_STALE_DAYS=7

Avec OSV_BULK_PATH, OSV lit l’arborescence JSON locale (npm run mirrors:sync / make sync-osv-bulk) sans API query. GitHub, RSS et traduction externe ignorés. Mirror NVD/KEV manquant → fail-closed.

curl -s 'http://localhost:3001/api/health?detailed=true' | jq '.airgap.mirrorHealth'

La barre latérale affiche un avertissement miroir obsolète si l’âge dépasse MIRROR_STALE_DAYS (7 par défaut). Prometheus : cve_radar_mirror_age_days.

Sync miroir (zone Internet)

npm run mirrors:sync (ou make mirrors-sync) télécharge KEV, pages NVD 2.0 (MIRROR_NVD_DAYS) et bulk OSV avec manifest et SHA256. Définissez MIRROR_MANIFEST_PATH sur l’hôte air-gap. RPO/RTO et dimensionnement : docs/self-hosted/AIRGAP.md.

Drapeau Effet
MIRROR_SKIP_NVD=true KEV + OSV seulement
MIRROR_SKIP_OSV=true NVD + KEV seulement
MIRROR_SKIP_KEV=true NVD + OSV seulement

CI valide les échantillons JSON via schemas/airgap/*.json (npm run check:airgap-schemas).

Helm chart

Chart officiel : charts/cve-radar/. Deployment, Service, Ingress optionnel, Redis, Postgres optionnel, PVC /app/data, montages Secret via env *_FILE.

helm install cve-radar ./charts/cve-radar -n cve-radar --create-namespace

Voir docs/self-hosted/HELM.md.

Kubernetes Operator (CVEScanStack)

L’operator v0 réconcilie les CR CVEScanStack en CronJob agent et watch. CRD : cve-radar.io/v1alpha1 ; manifests dans deploy/k8s/operator/. In-cluster : npm run operator:rundocs/self-hosted/OPERATOR.md.

kubectl apply -f deploy/k8s/operator/crd-cvescanstack.yaml
kubectl apply -f deploy/k8s/operator/rbac.yaml
kubectl apply -f deploy/k8s/operator/deployment.yaml

Chaque CVEScanStack définit stack, apiKeySecretRef, tenantId optionnel et les crons import/watch. #254.

Worker de jobs de scan (BullMQ)

Quand REDIS_URL est défini, exécutez un worker séparé à côté de l’API : npm run worker:run (image prod : node dist-server/server/worker/run.js). L’API accepte POST /api/v1/scan/queue ; les clients pollent GET /api/v1/jobs/:id et récupèrent les résultats paginés via GET /api/v1/jobs/:id/results. L’UI Web bascule en file + poll quand /api/capabilities signale features.jobQueue: true. Sans Redis, les scans restent synchrones (POST /api/v1/scan/stream). Voir #257.

REDIS_URL=redis://127.0.0.1:6379 npm run worker:run

Agent de scan (Trivy / Grype)

POST /api/v1/scans/import pour JSON Trivy/Grype. CLI : npm run agent:scan. Exemple CronJob : deploy/k8s/agent-scan-cronjob.example.yaml. docs/self-hosted/AGENT.md.

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, MIRROR_MANIFEST_PATH, MIRROR_STALE_DAYS
K8s K8S_DISCOVERY_ENABLED, K8S_DISCOVERY_NAMESPACES
Enrichment EPSS_ENABLED, COMPLIANCE_ENABLED

Copies maintainer : docs/self-hosted/.

Accueil · Précédent : Exploitation