heldevia
Notre propre forge22 juillet 20268 min de lecture

Comment nous avons atteint 100 % sur internet.nl — et comment le vérifier vous-même

Notre site affiche 100 % au test public internet.nl : IPv6, TLS moderne, RPKI et une CSP stricte sans unsafe-inline sur un site statique. Voici le chemin réel — pannes comprises — et les commandes pour tout vérifier.

Aussi disponible en : English

Une société qui conseille des entreprises suisses sur l'IA et la sécurité, et dont le propre site web serait mal configuré : c'est la maison du forgeron aux poignées de porte rouillées. Nous avons donc pris notre propre médecine. Résultat : internet.nl, le test public soutenu par le gouvernement néerlandais et une coalition d'organisations de standardisation, donne 100 % à heldevia.ch.

Cet article raconte le chemin réel — y compris la panne que nous avons provoquée nous-mêmes — et surtout, fidèle à notre principe : chaque affirmation vient avec la commande pour la vérifier. Ne nous croyez pas sur parole ; c'est justement l'idée.

Le point de départ : 70 %

Notre stack est volontairement sobre : un site statique (export Next.js) servi par nginx, derrière un reverse proxy Traefik, sur une petite VM ARM chez Oracle Cloud à Zurich. Au premier scan, la substance passait déjà — DNSSEC signé, HTTPS partout, en-têtes de sécurité complets. Le score : 70 %.

Ce qui manquait tenait en quatre chantiers : pas d'IPv6, du TLS avec des restes des années 2010, une Content Security Policy avec unsafe-inline, et un préfixe IPv4 sans validation RPKI. Aucun n'était cosmétique.

IPv6 : le chantier « facile » qui ne l'était pas

Activer le dual-stack côté cloud fut la partie propre : un préfixe /56 sur le réseau virtuel, un /64 sur le sous-réseau, une adresse sur la VM — le tout en Terraform. Docker publiait déjà ses ports en IPv6, Traefik écoutait déjà sur toutes les familles. Et pourtant : timeout systématique en v6.

Le coupable était le pare-feu de l'hôte, et la raison est instructive. En IPv4, le trafic entre par une règle DNAT et traverse la chaîne FORWARD — il contourne le pare-feu local. En IPv6, sans ip6tables configuré, Docker passe par un proxy en espace utilisateur : le trafic arrive par la chaîne INPUT, où les ports 80 et 443 étaient fermés. Même serveur, deux chemins réseau complètement différents selon la famille d'adresses.

TLS : trois couches de vieux à décaper

Le scanner a relevé trois problèmes distincts, de difficulté croissante.

Les suites de chiffrement. Le proxy offrait encore des suites CBC avec SHA-1. Traefik permet de définir des profils TLS par nom d'hôte via son provider de fichiers : nous avons appliqué un profil moderne — TLS 1.2 minimum, suites AEAD uniquement — sans toucher aux autres services de la machine.

Les certificats. Let's Encrypt émettait du RSA-2048, que le test classe en « à retirer progressivement ». Un résolveur ACME dédié aux courbes elliptiques plus tard, la chaîne est en ECDSA P-256 signée SHA-256.

La signature du handshake — le piège. Le plus retors : même avec des suites modernes et un certificat ECDSA, internet.nl signalait encore du SHA-1. Il ne s'agissait ni des suites ni du certificat, mais de la signature de l'échange de clés en TLS 1.2 : Go (dont Traefik hérite) acceptait de signer en SHA-1 quand un client n'annonçait rien de mieux, et n'expose aucun réglage pour l'interdire. La solution n'était pas une configuration mais une mise à niveau : Go 1.25 a retiré SHA-1 du handshake TLS 1.2 conformément à la RFC 9155 ; Traefik v3.6 embarque ce runtime. Mise à niveau du proxy, problème dissous.

Une leçon de calibrage au passage : notre premier réflexe — passer en TLS 1.3 uniquement — faisait disparaître le SHA-1 mais coûtait le A+ chez SSL Labs, qui exige TLS 1.2 pour sa note maximale. Deux scanners sérieux, deux définitions du « bien ». Le correctif par mise à niveau satisfait les deux.

La CSP stricte sur un site statique : le mur, puis la porte

Le dernier avertissement d'internet.nl sur les en-têtes : notre CSP contenait unsafe-inline. C'est la directive qui annule l'essentiel de la protection d'une CSP — elle autorise précisément ce qu'une injection utilise.

Nous avons d'abord mesuré le mur. Un export statique Next.js contient des scripts d'hydratation inline qui changent à chaque build — 226 blocs distincts chez nous. Hachable, donc gérable. Le vrai mur : notre bibliothèque d'animation générait 2 430 attributs style="" dans le HTML exporté, et les attributs de style ne peuvent être autorisés ni par hash ni par nonce. Aucune configuration ne contourne cela ; il fallait changer le site, pas l'en-tête.

Nous avons donc retiré la bibliothèque d'animation (deux composants réécrits en CSS pur, mêmes mouvements), éliminé les derniers styles inline, et remplacé la page 404 générée par une page statique. Puis nous avons écrit un petit générateur qui, à chaque déploiement, hache chaque script inline de chaque page exportée et produit une politique par page pour nginx.

Deux leçons payées comptant, dans l'esprit de transparence de cette série :

  • nginx tronque les tokens de configuration à 4 096 octets. Notre première version — une politique globale unique de 12 Ko avec tous les hashes — ne parsait pas et a mis le site à terre pendant quatre minutes. D'où la politique par page (~1,1 Ko maximum), et un test de configuration avec l'image nginx de production avant tout déploiement.
  • Le garde-fou doit casser le déploiement. Le générateur refuse de publier si un seul attribut style="" réapparaît dans l'export. Un composant ajouté distraitement ne peut pas éroder la politique en silence.

Effet de bord apprécié : moins 32 Ko de JavaScript par page. Et ce que vous lisez en ce moment respecte la même règle — la coloration syntaxique de ces blocs de code est faite de classes CSS, pas de styles inline.

RPKI : la pêche à l'adresse IP

RPKI permet aux réseaux de vérifier cryptographiquement qu'une annonce de route est légitime — une protection contre les détournements BGP. Notre IPv6 était déjà couverte par un ROA d'Oracle. Notre IPv4, elle, tombait dans l'un des deux seuls préfixes sans ROA parmi les huit que le cloud attribue aux machines virtuelles dans la région de Zurich.

Un ROA est signé par le détenteur du préfixe : l'hébergeur, pas le client. Impossible de le faire nous-mêmes, et attendre qu'Oracle signe ce préfixe précis ne dépendait pas de nous. Notre solution assumée : la pêche. Réserver une adresse IP fixe, vérifier dans quel préfixe elle tombe, la rendre si le préfixe n'est pas signé, recommencer. Premier lancer : une adresse dans un préfixe avec ROA valide. Bascule de l'ancienne adresse vers la nouvelle en sept secondes de coupure IPv4 — les clients dual-stack n'ont rien vu, l'IPv6 n'a pas bougé.

Les finitions : security.txt signé et HSTS preload

Deux derniers standards complètent le tableau. Un security.txt (RFC 9116) signé PGP indique aux chercheurs en sécurité où nous écrire — avec une clé publiée sur le site. Gotcha vécu : le validateur de référence décode les fichiers signés en Latin-1 ; un simple tiret cadratin dans un commentaire le fait accuser, à tort, le bloc PGP. Notre fichier est en ASCII pur.

Enfin, HSTS avec preload : le domaine est soumis à la liste d'inclusion des navigateurs, qui forceront HTTPS avant même la première visite.

Ce que nous referions autrement

L'honnêteté d'un journal de forge, c'est aussi la liste des cicatrices :

  • Scanner d'abord, croire ensuite. Nous pensions notre TLS propre parce que notre configuration l'était. Le SHA-1 vivait deux couches plus bas, dans le runtime du proxy. Un scan externe avant de commencer aurait économisé une hypothèse fausse.
  • Tester la configuration avec l'artefact de production. La panne de quatre minutes venait d'un nginx -t exécuté avec une autre version de nginx que celle qui servait le site. Le test de validation utilise maintenant l'image exacte de production.
  • Mesurer trois fois. Les scores de performance locaux varient de ±8 points d'une exécution à l'autre. Toute décision se prend désormais sur la médiane de trois mesures — sinon on optimise du bruit.

Le score final tient en une ligne : 100 % à internet.nl, A+ chez SSL Labs en IPv4 et IPv6, zéro violation CSP — sur une infrastructure qui coûte zéro franc par mois. La rigueur n'est pas une question de budget ; c'est la même que nous appliquons aux systèmes d'IA que nous construisons pour nos clients. Les détails de notre posture, affirmation par affirmation, restent publics sur notre page Sécurité.

Questions fréquentes

Un score de 100 % sur internet.nl est-il indispensable ?
Non. internet.nl mesure l'adoption de standards modernes (IPv6, DNSSEC, TLS, en-têtes de sécurité, RPKI), pas la sécurité globale d'une organisation. Mais chaque point manquant mérite une conversation : certains relèvent de la défense en profondeur, d'autres de la disponibilité et de la qualité du routage. Pour nous, viser 100 % était une démonstration de méthode : chaque affirmation de notre page Sécurité doit être vérifiable.
Ce score est-il atteignable pour n'importe quel site ?
Le 100 % strict est nettement plus difficile avec un CMS ou du rendu dynamique : une CSP par hachage exige un HTML figé au moment du build. Avec du contenu généré à la requête, il faut des nonces côté serveur et une discipline stricte sur les styles inline. Un site statique réduit la surface d'attaque et rend ce niveau d'exigence réaliste — c'est d'ailleurs pour cela que le nôtre l'est.
Combien de temps cela prend-il ?
Dans notre cas, deux jours de travail concentré — parce que l'infrastructure était déjà décrite en Terraform, que le site est statique et que nous contrôlions le reverse proxy. Sans infrastructure as code, comptez plus : la moitié du travail consiste à comprendre précisément ce que votre hébergeur fait à votre place.
Pourquoi RPKI dépend-il de l'hébergeur et pas de vous ?
Les ROA (Route Origin Authorizations) sont signés par le détenteur du préfixe IP — votre opérateur ou votre cloud, pas vous. Si votre adresse tombe dans un préfixe sans ROA, vos options : demander à l'opérateur de signer, ou changer d'adresse vers un préfixe déjà signé, comme nous l'avons fait.

Tous les articles

Prêt à transformer votre entreprise avec l'IA ?

Discutons de la façon dont les solutions intelligentes peuvent créer votre prochain avantage concurrentiel.