Aller au contenu
Sécurité medium

Varnish Artifact Firewall : contrôler l'entrée des dépendances

18 min de lecture

logo orca

Vos développeurs lancent chaque jour des dizaines de npm install et de pip install. Chacune de ces commandes télécharge du code tiers — souvent des centaines de paquets transitifs — et l'exécute sur leurs postes et sur vos runners CI, sans qu'aucun contrôle ne décide si ce code a le droit d'entrer.

Ce vecteur est massivement exploité. En 2025, le malware Shai-Hulud a infecté plus de 500 paquets npm populaires : son script postinstall collectait identifiants, clés SSH et portefeuilles crypto dès l'installation. Côté PyPI, des milliers de paquets malveillants sont retirés chaque année, exploitant le typosquatting et la dependency confusion pour se faire passer pour des dépendances légitimes.

Pour un RSSI, le constat est simple : la porte d'entrée des dépendances n'est pas gardée. Un firewall d'artefacts la garde — il place une décision de politique (autoriser, masquer, refuser, mettre en quarantaine) devant chaque résolution, avant que le paquet n'entre dans votre usine logicielle.

Ce guide s'adresse aux équipes sécurité et plateforme qui gèrent déjà un gestionnaire d'artefacts (Nexus, Artifactory) et veulent passer du cache au contrôle. Vous y trouverez le rôle exact d'un firewall d'artefacts, comment écrire ses règles pour npm et PyPI, et un lab d'étude reproductible avec Varnish Artifact Firewall (Varnish Orca) devant Nexus.

  • Distinguer cache, firewall d'artefacts, analyse SCA et admission control
  • Écrire des règles allow / hide / deny / quarantine
  • Monter un lab Orca + Nexus pour étudier le contrôle d'entrée
  • Bloquer une version vulnérable et un nom interne usurpé
  • Situer le firewall dans une chaîne supply chain complète

Un gestionnaire d'artefacts comme Nexus ou un cache comme Varnish Orca résout deux problèmes réels : la vitesse (les dépendances sont servies localement) et la résilience (vous survivez à une panne de npmjs.org). Mais ils partagent une limite : ils servent tout ce qu'on leur demande.

Concrètement, si un développeur installe une version vulnérable de requests, un package au nom usurpé via dependency confusion, ou une dépendance publiée il y a deux heures par un compte compromis, le cache la télécharge, la stocke, et la rediffuse à toute l'équipe. Le cache accélère la propagation du problème au lieu de l'arrêter.

Le firewall d'artefacts ajoute la pièce manquante : une décision inline, prise avant que l'artefact n'entre dans le cache et dans vos builds.

Un firewall d'artefacts est un proxy qui s'intercale entre vos clients (npm, pip, runners CI) et vos dépôts, et qui évalue chaque résolution contre un jeu de règles. Selon la règle qui correspond, il rend l'une de ces décisions :

DécisionEffetCas d'usage
allowSert l'artefactException explicite, package de confiance
hideL'artefact existe mais n'est pas proposé à la résolutionÉvincer une version sans casser un lockfile existant
denyRefuse, renvoie une erreurPackage interdit, version vulnérable
quarantineRetient temporairement (artefacts récents)Fenêtre anti-typosquat sur publications fraîches

L'intérêt par rapport à un scanner classique : la décision est prise avant ingestion. Une dépendance refusée n'entre jamais dans le cache ni dans le build. Là où un outil de SCA vous dit après coup « vous avez installé un composant vulnérable », le firewall l'empêche d'arriver.

Dans ce guide, j'utilise Varnish Artifact Firewall (la brique Premium de Varnish Orca) devant Nexus. Le firewall est transparent : les clients visent le registre virtuel d'Orca, et enable_firewall: true route ce registre à travers le firewall en interne.

Le lab enchaîne trois composants. Chaque résolution traverse le firewall avant d'atteindre le cache, puis éventuellement le registre public.

Client npm / pip -> Varnish Artifact Firewall (Orca) -> Nexus (groupes) -> npmjs.org / pypi.org
décision allow/hide/deny/quarantine

Le rôle de Nexus ici est de centraliser : un dépôt proxy vers le registre public, un dépôt hosted pour vos packages internes, et un groupe qui expose une URL unique. L'ordre des membres du groupe (hosted avant proxy) constitue déjà une première barrière contre la dependency confusion. Le firewall ajoute la décision par règle au-dessus de cette organisation.

Le lab tient dans un docker-compose.yml (Nexus + Orca) et quelques fichiers de configuration. Voici les pièces essentielles à reproduire.

  1. Déclarer Nexus et Orca

    docker-compose.yml
    services:
    nexus:
    image: sonatype/nexus3:3.77.1
    ports: ["8081:8081"]
    volumes: ["nexus-data:/nexus-data"]
    orca:
    image: varnish/orca
    platform: linux/amd64 # Orca est distribué pour linux/amd64
    depends_on: [nexus]
    ports:
    - "8080:80" # registres virtuels (endpoint client)
    - "9090:9090" # métriques firewall (Premium)
    volumes:
    - ./orca/orca.yaml:/etc/varnish-supervisor/default.yaml:ro
    - ./orca/rulesets:/etc/orca/rulesets:ro
    volumes:
    nexus-data:
  2. Créer les dépôts Nexus (proxy, hosted, groupe) pour npm et PyPI via l'API REST. Le groupe npm-all agrège npm-private-hosted puis npm-public-proxy, et c'est lui que verra le firewall.

  3. Configurer le firewall Orca

    orca/orca.yaml
    virtual_registry:
    registries:
    - name: npm-nexus
    default: true
    enable_firewall: true
    remotes:
    - url: http://nexus:8081/repository/npm-all/
    - name: pypi-nexus
    enable_firewall: true
    remotes:
    - url: http://nexus:8081/repository/pypi-all/
    firewall:
    default_action: allow # observe en POC ; deny en production
    default_quarantine_days: 2
    severity_deny_threshold: 9
    severity_allow_threshold: 4
    audit_log_output: stdout
    metrics_address: ":9090"
    rulesets:
    - path: /etc/orca/rulesets/npm-rules.yaml
    - path: /etc/orca/rulesets/pypi-rules.yaml
  4. Pointer les clients sur le firewall

    Orca transmet le chemin client tel quel : incluez le chemin du groupe Nexus.

    .npmrc
    registry=http://npm-nexus.localhost:8080/repository/npm-all/
    pip.conf
    [global]
    index-url = http://pypi-nexus.localhost:8080/repository/pypi-all/simple
    trusted-host = pypi-nexus.localhost

Une règle décrit quoi reconnaître (match) et quoi décider (action). Les règles sont regroupées dans un fichier identifié par un id, et triées par priority. Voici un jeu de règles npm de démonstration.

orca/rulesets/npm-rules.yaml
id: npm-demo-rules
rules:
# Blocage dur d'un package précis.
- id: block-left-pad
priority: 100
action: deny
severity: 9.5
reason: "Package bloqué pour la démonstration."
match:
- type: npm
name: left-pad
# Anti dependency confusion : un scope interne ne vient jamais du public.
- id: block-internal-scope
priority: 90
action: deny
reason: "Le scope @company ne doit pas être résolu depuis le registre public."
match:
- type: npm
name: "@company/*"
# Masquer les vieilles versions sans casser un lockfile existant.
- id: hide-lodash-old
priority: 50
action: hide
reason: "Les versions de lodash < 4.17.21 ne sont pas proposées."
match:
- type: npm
name: lodash
version: "vers:npm/<4.17.21"

Côté Python, la même grammaire bloque une version vulnérable ou un préfixe interne. La plage de versions utilise la notation VERS (vers:pypi/<2.32.0).

orca/rulesets/pypi-rules.yaml
id: pypi-demo-rules
rules:
- id: block-old-requests
priority: 100
action: deny
severity: 9.0
reason: "Les versions de requests < 2.32.0 sont interdites."
match:
- type: pypi
name: requests
version: "vers:pypi/<2.32.0"

Une règle peut porter un score severity. Le firewall le compare aux seuils définis dans la configuration : au-dessus de severity_deny_threshold (9), la décision devient deny ; en dessous de severity_allow_threshold (4), elle devient allow ; entre les deux, c'est l'action par défaut qui s'applique. Ce mécanisme permet de brancher des règles communautaires (par exemple des jeux de règles dérivés des données OSV) et de piloter leur effet via un seul réglage, sans réécrire chaque règle.

Charger les règles depuis des fichiers locaux (path:) impose de redémarrer Orca pour les prendre en compte : pratique en lab, pas en production. Le mode dynamique passe par une source git, que le firewall rafraîchit automatiquement à intervalle régulier — sans redémarrage.

orca/orca.yaml (extrait firewall)
firewall:
rulesets:
- git:
name: npm-osv-rules
url: https://github.com/varnish/osv-rules.git
sub_path: rulesets/npm/all.yaml
ref: main # branche, tag ou SHA
interval: 30m # fréquence de pull (défaut : 1h)
webhook:
url: http://collector.interne/reloaded # notifié après chaque rechargement

Ce fonctionnement débloque une vraie démarche GitOps : vos règles vivent dans un dépôt versionné et signé, toute modification passe par une revue, et le firewall applique la nouvelle politique au prochain cycle de interval. Le webhook prévient vos systèmes (invalidation du cache de manifestes, notification d'audit) à chaque rechargement réussi. Vous pouvez ainsi brancher des jeux de règles communautaires maintenus à jour (par exemple les règles dérivées des données OSV publiées par Varnish) sans intervention manuelle.

Une fois la licence d'essai montée, le firewall démarre et applique les décisions. Le scénario d'étude le plus parlant compare deux installations.

  1. Installer une dépendance autorisée

    Fenêtre de terminal
    npm install is-number # autorisé : l'installation réussit
  2. Tenter une dépendance bloquée

    Fenêtre de terminal
    npm install left-pad # deny : l'installation échoue
  3. Lire le journal d'audit

    Fenêtre de terminal
    docker compose logs orca | grep -i deny

    Vous y retrouvez la décision, le package et la raison (reason) de la règle. C'est ce journal que vous routerez vers un SIEM en production.

  4. Modifier une règle et rejouer

    Retirez block-left-pad, redémarrez Orca, relancez l'installation : elle passe. Vous venez de démontrer que la politique pilote la décision.

Un firewall d'artefacts ferme une porte : l'entrée non gouvernée des dépendances. Il ne traite pas le reste de la surface d'attaque, et le présenter comme une protection globale serait une erreur.

  • Il ne lit pas le code d'un package autorisé : une dépendance saine aujourd'hui peut être compromise demain par une mise à jour malveillante.
  • Il ne protège ni le runner CI compromis, ni l'exfiltration de secrets, ni l'image déjà construite et non signée.
  • Il ne produit ni SBOM ni provenance : ce sont d'autres briques.

Surtout, la configuration registry incite à passer par le firewall mais ne l'impose pas : un développeur ou un job peut toujours écrire npm config set registry https://registry.npmjs.org. Refermer ce contournement — sur les runners CI comme sur les postes de dev — est un sujet à part entière, traité juste après.

Un firewall ne protège que ce qui passe par lui. Tant que les clients peuvent joindre registry.npmjs.org ou pypi.org en direct, n'importe qui peut le court-circuiter d'une seule commande. La règle d'or tient en une phrase : la configuration incite, le réseau impose.

Côté CI, le contournement se ferme proprement parce que vous maîtrisez l'environnement. Placez les runners dans un réseau sans route sortante vers Internet : seul le firewall d'artefacts est joignable en egress. Une tentative npm config set registry https://registry.npmjs.org échoue alors faute de route. Sous Kubernetes, une NetworkPolicy egress qui n'autorise que le service du firewall produit le même effet. Des runners éphémères et standardisés (reconstruits à chaque job) empêchent en plus toute config locale persistante.

Le poste de développeur est plus dur à contraindre : le dev a souvent les droits admin sur sa machine, donc aucune configuration applicative n'est infalsifiable. Pousser un .npmrc, un pip.conf ou un GOPROXY global par MDM (Intune, Jamf) ou gestion de configuration réduit la friction, mais reste contournable d'un --registry. Le contrôle robuste est, là encore, réseau :

SolutionEffetRobustesse
Couper l'egress direct (pare-feu/proxy d'entreprise) vers npmjs/pypi/Docker HubLe set registry public échoue : plus de routeÉlevée
DNS interne (split-horizon) : registry.npmjs.org → IP du firewallMême une URL publique en dur atterrit sur le firewallÉlevée (avec egress fermé)
Poste sans route Internet directe, seul le proxy interne est routableTout transite par le firewall, par constructionMaximale
Environnement de dev distant (dev container, VDI, espace de code)On contrôle le réseau de l'env, pas la machine physiqueÉlevée
Config managée (MDM/Ansible : .npmrc, pip.conf, GOPROXY)Baisse la friction, oriente le défautFaible seule

Aucune ligne ne suffit isolément sur une machine où le dev est administrateur. La combinaison qui tient : pas de route directe vers les registres publics + DNS qui rabat les noms publics sur le firewall. La config managée s'ajoute pour le confort, pas pour la sécurité.

Le réseau ferme la porte ; la détection vérifie qu'elle reste fermée. Ajoutez en CI un contrôle qui inspecte les lockfiles : si une entrée resolved (npm) ou une URL d'index pointe ailleurs que vers le firewall, le build échoue — vous rattrapez ainsi un poste mal configuré avant le merge. En parallèle, surveillez les logs du proxy réseau : toute tentative d'accès direct à npmjs/pypi/Docker Hub devient une alerte. Cette détection des contournements prolonge les techniques décrites dans Attaques via les gestionnaires de paquets.

Le firewall n'est pas un substitut aux autres contrôles : il occupe la position la plus en amont. Une chaîne supply chain mûre les combine.

ÉtapeBriqueRôle
Entrée des dépendancesVarnish Artifact Firewallbloque/masque avant ingestion
CentralisationNexuscache et expose les artefacts
InventaireSBOM / Syftliste les composants
VulnérabilitésTrivy / Grypescanne après résolution
SignatureCosignsigne et vérifie
ProvenanceSLSAatteste l'origine du build

Le firewall réduit le bruit en aval : moins de composants douteux entrent, moins de vulnérabilités à trier ensuite. Il ne remplace pas la défense en profondeur, il la commence.

  • Un cache accélère et résiste aux pannes ; un firewall d'artefacts décide ce qui a le droit d'entrer.
  • Quatre décisions : allow, hide, deny, quarantine.
  • Les règles reconnaissent un package par type/name/version (notation VERS) ou purl, et la sévérité pilote la décision via des seuils.
  • Le firewall traite la dependency confusion, les versions vulnérables et les publications trop récentes — pas le code malveillant d'un package autorisé.
  • Sans règle d'egress réseau, la configuration registry se contourne.
  • Chez Varnish, le firewall est Premium ; une licence d'essai permet de l'étudier en conditions réelles.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracing. Aujourd'hui, ce site ne couvre même pas mes frais d'hébergement, d'électricité, de matériel, de logiciels, mais surtout de cafés.

Un soutien régulier, même symbolique, m'aide à garder ces ressources gratuites et à continuer de produire des guides de qualité. Merci pour votre appui.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn