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=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.
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=true → database.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:run — docs/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