Aller au contenu principal
Version: 1.0.0

Les garde-fous, et pourquoi la v1 n'applique rien

Une politique de durcissement peut vous exclure de la machine que vous êtes en train de durcir. bitenforcer mesure ce risque par rapport à la session dans laquelle vous êtes réellement en train de travailler, et le rapporte avant que quoi que ce soit ne soit appliqué. Ces mêmes garde-fous sont la raison pour laquelle la v1 n'applique absolument rien : sept relecteurs contradictoires indépendants sont chacun arrivés avec une forme de verrouillage qu'ils ne reconnaissaient pas.

Cette page couvre les garde-fous, la façon de verrouiller une politique sur eux en CI, et l'exposé complet des raisons pour lesquelles l'application est hors périmètre pour cette version. Pour ce que l'outil fait aujourd'hui, voir la présentation de bitenforcer.

Les garde-fous anti-verrouillage​

Dix-huit garde-fous, couvrant chacun une manière dont une politique de durcissement peut vous exclure de votre propre machine. Listez-les avec bitenforcer guards :

ssh-operator-lockout the SSH change would lock out the account you are running as
(FR) la modification SSH verrouillerait le compte avec lequel
vous êtes connecté
--acknowledge ssh-operator-lockout

firewall-ipv6-unhandled IPv4 would be locked down while IPv6 stays open (silent bypass)
(FR) IPv4 serait verrouillé alors qu'IPv6 resterait ouvert
--acknowledge firewall-ipv6-unhandled

Ensemble complet : session-undetermined, ssh-no-auth-method, ssh-operator-lockout, ssh-listen-address-unusable, ssh-listen-address-excludes-session, ssh-crypto-narrowing, firewall-session-drop, firewall-deny-before-allow, firewall-ipv6-unhandled, selinux-relabel-required, selinux-port-context-loss, selinux-access-loss, services-remote-access-loss, users-operator-lockout, kernel-remote-access-loss, kernel-boot-risk, security-regression, unreviewed-change.

Deux règles de conception comptent :

Il n'existe pas de --force global

On ne désarme un garde-fou qu'en le nommant — --acknowledge <guard-id> — et acquitter un garde-fou laisse tous les autres armés. unreviewed-change ne peut pas être acquitté en tant que classe : le jeton nomme un module, un type et une clé précis, et le refus lui-même affiche le jeton exact à utiliser.

Une session qui n'a pas pu être mesurée n'est pas acquittable pour toute exécution touchant à l'accès distant. Un opérateur qu'on ne peut pas localiser ne peut pas être protégé, et prétendre le contraire est précisément la façon dont on finit exclu de sa machine.

Verrouiller une politique en CI​

--guards=blocking transforme tout constat non acquitté en code de sortie non nul :

bitenforcer apply --config policy.yaml --dry-run --guards=blocking
# exit 1 when any guard fires — fails the pipeline

C'est ainsi que vous empêchez un socle de durcissement qui couperait l'accès distant d'atteindre une fenêtre de changement.

Pourquoi l'application n'est pas dans la v1​

Le chemin d'application a été construit. Il a ensuite été revu sept fois, de manière contradictoire, par sept relecteurs indépendants sur de vrais hôtes. Chacun d'entre eux, sans exception, s'est soit exclu de l'hôte de test, soit a forgé la confirmation censée précisément empêcher cela — dix formes distinctes de verrouillage, plusieurs d'entre elles rapportant un succès et un code de sortie 0 sur le moment.

Les formes n'ont jamais été les mêmes deux fois : des octets de sshd_config ; un plan de pare-feu ; un drop-in de durcissement systemd qui n'est pas un « stop » et n'a donc jamais atteint l'évaluateur ; un changement de propriétaire/groupe sur authorized_keys ; un sysctl hors des deux motifs qu'un évaluateur reconnaissait ; une méthode d'authentification « survivante » que le compte à clé seule de l'opérateur ne pouvait en réalité pas utiliser ; une confirmation acceptée via la boucle locale ; une sonde impossible à réaliser interprétée comme un échec.

Chaque vague a fermé la forme qu'on lui avait montrée, et le relecteur suivant est arrivé avec une nouvelle. C'est la signature d'un problème en monde ouvert : « énumérer toutes les façons dont une politique peut rendre cet hôte injoignable » n'a pas de dernier élément, et toute version des garde-fous n'est que la liste des attaques auxquelles quelqu'un a déjà pensé.

Un retour arrière à homme mort (dead-man) a été construit précisément parce qu'il n'exige aucune énumération : quoi qu'il ait cassé sur l'hôte, la confirmation n'arrive pas et le changement est annulé. C'est le bon modèle et le code est conservé. Il n'a pas non plus survécu à la revue, et son dernier constat a clos le débat :

La machinerie de récupération est devenue une élévation de privilèges locale vers root. Le retour arrière à homme mort a restauré une permission au travers d'un lien symbolique qu'un utilisateur non privilégié avait planté dans son propre ~/.ssh, faisant passer /etc/shadow de 0640 à 0777. Le manifeste de l'exécution a consigné « 4 restored, 0 failed ».

Livrer cela aurait installé une élévation de privilèges vers root sur chaque machine cliente, apportée par le filet de sécurité lui-même. L'application est donc hors périmètre pour la v1.

Ce qui a été prouvé, et comment​

Une empreinte de l'ensemble du système de fichiers — chemin, mode, propriétaire, taille et SHA-256 de chaque fichier sous /etc, /var/lib et les arborescences systemd — prise avant et après une exécution de tous les modules en root, dans un conteneur disposant de vrais sshd, nft, iptables et getenforce, est identique. Aussi bien pour apply (qui refuse) que pour apply --dry-run (qui rapporte). Cela représente 39 assertions dans le test de surface de bout en bout du dépôt.

Deux versions ont ensuite été délibérément faussées et passées dans le même script, parce qu'un test que personne n'a essayé de casser n'est qu'une décoration :

  • énoncé de périmètre supprimé → 6 échecs, dont ROOT apply CHANGED the filesystem ;
  • la commande deadman retenue réenregistrée → 7 échecs, nommant chacun la commande revenue et l'état qu'elle a créé.

Ce que la v1 ne livre délibérément pas​

CommandeDans la v1Pourquoi
validate✅Refuse une politique invalide — et refuse les clés que rien n'implémente.
apply --dry-run✅Le contrôle préalable.
apply --dry-run --guards=blocking✅Verrou de politique pour la CI.
guards, session, version✅En lecture seule.
generate✅En lecture seule sauf avec --output-file, qui écrit — voir l'avertissement ci-dessous.
apply (appliquant réellement)❌Énonce le périmètre de la version en EN + FR, nomme ce que vous pouvez exécuter, sort avec un code non nul.
rollback, confirm❌Rien dans la v1 ne peut créer un instantané ni armer un retour arrière : ces commandes ne pourraient jamais répondre autre chose que « il n'y a rien ici » — ce qui annoncerait tout de même une capacité.
deadman❌Retirée. Restaurer, c'est modifier l'hôte, à partir d'un répertoire d'état fourni par l'opérateur — c'est là que résidait l'élévation de privilèges.
--output-file écrit sur l'hôte et écrasera un fichier existant

--output-file est une option globale : elle s'applique donc aussi à generate. Lorsqu'elle est présente, generate crée le répertoire parent (0750) et écrit le fichier (0640) — sans vérification d'existence, sans O_EXCL et sans confirmation. La pointer vers un chemin qui existe déjà remplace ce fichier.

Cette page listait auparavant generate parmi les commandes « toutes en lecture seule », et c'est là que résidait le danger : sur la foi de cette phrase, un opérateur a pointé --output-file vers un chemin sous /etc, et la commande a écrasé la configuration qui s'y trouvait déjà. Tout le reste du tableau est réellement en lecture seule ; cette option est l'exception, et elle est signalée ici plutôt que corrigée en silence.

Exécuter apply sans --dry-run n'est pas une opération vide et silencieuse. L'outil vous dit la vérité et sort avec un code non nul :

$ bitenforcer apply --config policy.yaml
Error: apply: this release does not change hosts, and nothing on this one was changed.

bitenforcer v1 ships policy validation and pre-flight. Enforcement — writing
sshd_config, firewall rules, sysctls, accounts and unit state — is deliberately not
part of it, and no flag turns it on. It is held back because seven independent
adversarial reviews of that path each locked themselves out of a test host or forged
the confirmation meant to catch it.

What this build does, and does well:
bitenforcer apply --config policy.yaml --dry-run
bitenforcer apply --config policy.yaml --dry-run --guards=blocking
bitenforcer validate --config policy.yaml

(FR) Cette version ne modifie aucun hôte et rien n'a été modifié ici. […]

Rien n'est supprimé de la base de code. Le moteur, les garde-fous, le code d'instantané et de retour arrière ainsi que la machinerie homme mort demeurent, avec leurs tests. Ils sont le socle de la prochaine version. Ils ne sont simplement reliés à rien qui puisse modifier votre hôte.

Quand l'application reviendra​

La barre est écrite noir sur blanc pour qu'elle ne puisse pas être négociée à la baisse plus tard :

Un relecteur indépendant doit échouer à forger une confirmation ET échouer à survivre à un retour arrière, sur un hôte réel, deux fois de suite.

Deux passes contradictoires propres et consécutives, menées par un relecteur qui n'a pas écrit le correctif — et non « les formes que nous reconnaissons désormais sont couvertes », une affirmation qui a été faite et démentie sept fois.

Étapes suivantes​

Cette page vous a-t-elle été utile ?