Aller au contenu
Sécurité medium

Sécurix : un NixOS durci selon l'ANSSI, open source par la DINUM

17 min de lecture

Sécurix est un modèle de poste de travail sécurisé construit sur NixOS par le département Opérateur de Produits Interministériels (OPI) de la DINUM. Il applique les recommandations de l’ANSSI pour les systèmes GNU/Linux et sert de base déclarative pour des postes multi-profils : administration de production, poste multi-agent, poste multi-niveaux, poste intranet-only. Le projet est en alpha, sous licence MIT, et publié pour que toute équipe puisse s’en inspirer ou le forker.

Cette page analyse le dépôt pour en faire ressortir le travail de sécurité concret : modules NixOS implémentés, choix d’architecture, patterns réutilisables. Je ne rejoue pas le tutoriel d’installation officiel — le but est de vous donner des points d’entrée dans le code pour apprendre et éventuellement piocher.

  • Comprendre le modèle de menace et la cible de Sécurix par rapport à un NixOS vanilla
  • Identifier les 7 briques de sécurité principales du dépôt
  • Lire le module ANSSI comme framework de compliance as code
  • Savoir où regarder pour chaque besoin : durcissement noyau, FIDO2, Secure Boot, auditd, VPN
  • Décider si Sécurix s’adresse à votre contexte ou si vous devez en extraire des morceaux

Construire un poste d’administration conforme aux Recommandations de sécurité relatives à un système GNU/Linux — le guide ANSSI-BP-028, publié par l’ANSSI — est un travail de plusieurs mois pour une équipe. Le guide compte plus d’une centaine de règles classées en quatre niveaux : minimal, intermédiaire, renforcé, élevé.

Sécurix propose une implémentation NixOS directement utilisable et surtout auditable règle par règle. Chaque règle ANSSI devient un module Nix versionné dans Git, avec :

  • une configuration déclarative qui applique la règle ;
  • un script de vérification exécutable à la demande ;
  • des métadonnées (sévérité, catégorie, tags) qui permettent l’inclusion ou l’exclusion en fonction du profil.

C’est l’un des rares exemples publics, dans la fonction publique française, de compliance as code appliqué à un OS complet. Pour une équipe qui découvre NixOS dans un contexte sécurisé, c’est un gisement d’idées.

Sécurix s’adresse à un poste physique d’administrateur qui doit accéder à des environnements de production ou à des systèmes sensibles. Le modèle de menace implicite comprend :

  • le vol ou la perte du poste (chiffrement complet du disque, déchiffrement par clé matérielle) ;
  • la compromission applicative (durcissement noyau contre Spectre / Meltdown / MDS, isolation des montages) ;
  • la compromission d’un utilisateur (FIDO2 comme auth principale, auditd R33, bastion mode) ;
  • la dérive de configuration (NixOS : tout est déclaratif et régénéré à chaque déploiement) ;
  • la chaîne de boot (Secure Boot avec PK/KEK custom, initrd signé).

Ce qu’il ne vise pas explicitement (à ce stade) : environnement multi-tenant type terminal partagé, ou postes nomades avec contraintes hors-ligne prolongées.

En parcourant le dossier modules/, sept familles ressortent.

Le dossier modules/anssi/ contient le cœur de la démarche. Chaque règle ANSSI est définie comme une entrée Nix avec ce schéma :

R8 = {
name = "R8_MemoryBootOptions";
anssiRef = "R8 – Paramétrer les options de configuration de la mémoire";
description = "Set memory options at boot";
severity = "intermediary";
category = "base";
config = _: { /* options NixOS qui appliquent la règle */ };
checkScript = pkgs: pkgs.writeShellScript "check-R8" ''
# vérification runtime
'';
};

Le fichier options.nix expose un framework complet :

  • Niveaux : minimal, intermediary, reinforced, high — mappés sur un ordre numérique (0 à 3).
  • Catégories : base, client, server.
  • Exclusions documentées via security.anssi.exceptions.<RuleID>.rationale. Les règles R37 (pas de MAC sous NixOS), R45-R49 (SELinux/AppArmor non supportés) sont explicitement écartées avec motif, pas silencieusement ignorées.
  • Traçabilité des règles non appliquées : le framework classe chaque règle non active en tag, category, level, architecture ou unknown.

C’est exactement le genre de cadre qu’un auditeur veut voir : non pas « tout est bon », mais « voici ce qui est appliqué, ce qui est écarté, et pourquoi ».

Le fichier modules/anssi/kernel-options.nix applique onze paramètres noyau au boot :

boot.kernelParams = [
"l1tf=full,force" # L1 Terminal Fault, désactive SMT
"page_poison=on" # empoisonne les pages libérées pour détecter les fuites
"pti=on" # Page Table Isolation (Meltdown)
"slab_nomerge=yes" # complique les heap overflows
"slub_debug=FZP" # debug allocateur slab
"spec_store_bypass_disable=seccomp" # Spectre v4
"spectre_v2=on"
"mds=full,nosmt" # MDS + désactivation SMT
"mce=0" # kernel panic sur Machine Check non corrigé
"page_alloc.shuffle=1" # randomisation allocateur pages
"rng_core.default_quality=500" # initialisation CSPRNG via TPM
];

Chaque paramètre est commenté avec sa motivation sécurité. Le checkScript associé relit /proc/cmdline et vérifie la présence effective — utile pour détecter une dérive après installation manuelle.

Le fichier modules/filesystems/securix_v2.nix pilote le partitionnement via disko. Structure clé :

  • ESP (/boot) : 1 Go FAT32.
  • Recovery (/recovery) : 2 Go FAT32 — partition de secours.
  • LUKS : reste du disque, avec :
    • enrollFido2 = true : déchiffrement au boot par clé FIDO2 (Yubikey) ;
    • enrollRecovery = true : clé de secours générée à l’installation ;
    • allowDiscards = true : support TRIM pour SSD.
  • Btrfs avec sous-volumes /home, /var, /nix et options de montage durcies : nosuid, nodev, noatime, compress=zstd.

Le choix nosuid,nodev sur /home bloque l’exécution de binaires setuid et l’accès aux device nodes depuis les données utilisateur — une défense classique mais trop souvent oubliée.

Le fichier modules/pam/u2f.nix configure PAM pour que la clé matérielle remplace le mot de passe comme auth principale :

security.pam.u2f = {
enable = true;
control = "sufficient"; # U2F suffit, pas de mot de passe requis
settings = {
inherit (cfg) origin;
appid = cfg.appId;
authfile = "/etc/u2f-mappings";
cue = true; # avertit l'utilisateur de toucher la clé
};
};

Le contrôle sufficient est le détail intéressant : la clé FIDO2 seule suffit pour ouvrir la session. Le mot de passe reste disponible uniquement comme mécanisme de secours, pas comme auth normale. L’appId et l’origin sont paramétrables pour que les clés restent liées à un parc identifié (pam://acme-corp-workstations).

Complément côté Yubikey : modules/security-keys.nix embarque yubikey-manager, yubikey-personalization, pcscd et yubikey-agent pour SSH.

Le README mentionne l’enrôlement centralisé pour Secure Boot avec gestion PK/KEK. Dans la pratique, cela signifie que les clés de signature UEFI ne sont pas celles de Microsoft : l’organisation génère sa propre Platform Key et Key Exchange Key, signe son initrd et son kernel, et configure l’UEFI du poste pour ne reconnaître que cette chaîne.

C’est la seule façon crédible de garantir que l’initrd chargé au boot — celui qui déchiffre le LUKS avec FIDO2 — n’a pas été remplacé entre deux démarrages.

Le fichier modules/auditd.nix active auditd avec une politique de rotation et d’alerte :

space_left = 10%
space_left_action = ignore
admin_space_left = 5%
admin_space_left_action = email
action_mail_acct = <adminEmail>
num_logs = 10
max_log_file = 100
max_log_file_action = rotate

La règle audit par défaut (-a exit,always -F arch=b64 -S execve) trace tous les execve — utile comme baseline. Le commentaire TODO: dans le code liste les prochains traçages prévus : accès à l’audit lui-même, mounts, clés USB, modules noyau, kexec, interfaces réseau, Thunderbolt.

Au-delà du durcissement, Sécurix isole le poste dans des modes d’usage :

Le module modules/self.nix gère la notion de selfDescriptionType : un profil peut être "user", "machine" ou "both", ce qui découple l’identité de la machine de l’identité de l’agent qui l’utilise. C’est ce qui permet le poste multi-agent.

Le dossier tests/ expose deux suites bâties sur pkgs.testers.nixosTest, qui boote une VM QEMU et y exécute un script Python. Leur contenu est minimal :

securix.wait_for_unit("default.target")
securix.succeed("cat /etc/os-release | grep securix")

Ce sont des smoke tests : le système boote, l’identité Sécurix est présente. Les checkScript déclarés par chaque règle ANSSI ne sont pas appelés automatiquement — ils sont faits pour être lancés à la main sur une machine installée. Retenez-le avant de lire « test ANSSI passed ».

J’ai validé la procédure dans une VM Debian 12 + Nix 2.34, cohérente avec le setup du lab NixOS Lab 1.

  • CPU : 4 cœurs. 2 fonctionnent, mais allongent le build à 25-30 min.
  • RAM : 8 GB conseillés. 4 GB swappent dès la phase de fish-completions.
  • Disque : 25 GB libres — le /nix/store atteint ~19 GB au premier run.
  • KVM : /dev/kvm accessible, nested virt activée côté hyperviseur (/sys/module/kvm_intel/parameters/nested = Y).
Fenêtre de terminal
git clone https://github.com/cloud-gouv/securix.git
cd securix
# Toute la suite
nix-build -A tests
# Ou un test précis, plus rapide à itérer
nix-build -A tests.minimal -j 4 --cores 4
nix-build -A tests.anssi-minimal -j 4 --cores 4

Au premier run, Nix télécharge le channel nixos-25.11 et construit la closure complète (kernel durci, Sway, auditd, pam, luks…). Les runs suivants sont à chaud.

Fenêtre de terminal
ls -la result
# → result -> /nix/store/<hash>-vm-test-run-minimal
cat result/test.log | tail -30

Le test.log contient dmesg, journald, et la trace de chaque succeed/fail du script Python. Sur un échec, l’assertion ratée apparaît en clair avec les logs kernel qui la précèdent.

Trois configurations exécutées dans la même VM (4 vCPU / 8 GB / /nix/store initialement vide) :

ConfigTest scriptBoot systemdKernel params R8 appliqués
minimal (sans ANSSI)70.49 s66 s (2.8 + 22.3 + 40.8)❌ aucun
anssi-minimal (level = "minimal")67.21 s63 s (2.7 + 20.9 + 39.6)❌ aucun (R8 filtré)
anssi-intermediary (level = "intermediary", patch maison)124.44 s119 s (3.5 + 41.2 + 74.1)✅ les 11 params

Trois constats tirés de ces mesures.

Le nom de test anssi-minimal est piégeur. R8 a severity = "intermediary" ; la logique de options.nix (levelExclusion = severity > level, mappés 0..3) exclut tout ce qui dépasse minimal. Résultat : le cmdline kernel de ce test contient juste les défauts NixOS (console=…, lsm=landlock,yama,bpf), pas le moindre l1tf=, page_poison=, spectre_v2=, etc. Un système level = "minimal" n’applique quasiment rien des règles ANSSI.

Le framework de niveaux fonctionne. En patchant tests/anssi-minimal.nix en level = "intermediary", le kernel-params du système contient bien les 11 paramètres R8 attendus. Passer du minimal au durcissement réel demande donc une seule ligne de profil : security.anssi.level = "intermediary" ou "reinforced".

Le durcissement a un vrai coût de boot. intermediary quasi-double le temps de démarrage (66 s → 119 s). Le surcoût est concentré sur l’initrd (+19 s) et le userspace (+33 s) — le kernel lui-même reste rapide. Cause principale : le SMT désactivé par mds=full,nosmt réduit de moitié le parallélisme systemd. Sur un SSD NVMe réel, le ratio sera moins brutal, mais comptez 1 à 2 min de boot sur une machine ANSSI-durcie, c’est assumé par le modèle de menace.

Sécurix est un excellent corpus d’étude pour qui vient de découvrir NixOS via les labs de cette section. Points d’entrée selon le sujet :

SujetFichier à lire en premier
Framework de compliancemodules/anssi/options.nix
Règles ANSSI déclarativesmodules/anssi/preboot.nix, kernel-options.nix
LUKS + FIDO2 + recoverymodules/filesystems/securix_v2.nix
PAM U2Fmodules/pam/u2f.nix
auditd minimalmodules/auditd.nix
Profil par défautmodules/self.nix
Variantes matérielhardware/ (ThinkPad X13/X280/T14, EliteBook, Latitude…)
Tests NixOStests/anssi-minimal.nix

Non pour un remplacement direct de poste de production, oui comme base à forker et comme gisement de patterns. Le projet est en alpha et le README est explicite : aucun support n’est proposé.

BriqueVerdictCommentaire
Module ANSSI (modules/anssi/)✅ RéutilisableFramework autoportant, copy-pastable selon le README.
Kernel hardening (R8)✅ FonctionneLes 11 params s’appliquent dès level = "intermediary".
PAM U2F / FIDO2✅ Mature~50 lignes, control = "sufficient" bien pensé.
LUKS + FIDO2 + recovery✅ MatureBasé sur disko, montages btrfs durcis (nosuid,nodev).
auditd (R33)⚠️ PartielRotation + mail OK, mais règles additionnelles en TODO.
Ruleset ANSSI complet⚠️ Quasi videSur ~100 règles BP-028, une dizaine codée. Le reste est commenté dans ruleset.nix.
Test suite⚠️ Smoke testsBoot + branding, pas la compliance runtime.
MAC (SELinux / AppArmor)❌ Non applicableNixOS ne supporte pas SELinux ; R37, R45-R49 écartées avec motif.
Profils matériel⚠️ Limités7-8 modèles de laptop, seul x280 testé par CI.
Onboarding / inventory❌ Annoncéphone home en développement.

Il n’est pas technique. C’est la combinaison taux de complétion du ruleset + absence de validation runtime en CI. Tant que les checkScript individuels ne tournent pas dans le test, impossible de prouver la conformité à un auditeur — or c’est précisément sur cette preuve que repose la promesse « NixOS conforme ANSSI ».

  1. Extraire le module ANSSI pour l’intégrer dans votre propre profil NixOS. C’est la partie la plus mature et la seule vraiment plug-and-play.
  2. Forker pour construire un poste d’admin interne, en acceptant de compléter ruleset.nix règle par règle selon votre cible de conformité.
  3. Ne pas déployer main tel quel en production — attendre une release stable ou avoir une équipe pour suivre le projet.

Trois extensions transformeraient la test suite en outil réellement exploitable en CI : appeler les checkScript dans testScript, tester tous les variants matériels, tester au moins un VPN. La structure de tests/default.nix (un simple attrset) rend l’ajout trivial — une entrée = un scénario.

  • Sécurix est un poste NixOS open source de la DINUM, aligné BP-028, en alpha sous MIT.
  • Le vrai apport est le module ANSSI : compliance as code avec sévérité, catégories, exceptions motivées et checkScript — réutilisable hors Sécurix.
  • Côté durcissement effectif : R8 (11 params kernel Meltdown/Spectre/L1TF/MDS), FIDO2 comme auth principale (pam.u2f.control = "sufficient"), LUKS + FIDO2 + recovery via disko, auditd R33 avec mail et trace des execve.
  • Piège du nom anssi-minimal : level = "minimal" filtre R8 et tout ce qui est intermediary. Pour un durcissement effectif, passer à "intermediary" — et doubler le temps de boot (66 s → 119 s mesurés en VM, SMT désactivé oblige).
  • Limites structurelles : ~10 règles BP-028 réellement codées sur ~100, test suite en smoke tests, pas de MAC (NixOS).
  • Verdict : base à forker et compléter, pas produit prêt pour la prod.

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