Zum Hauptinhalt springen
Version: 1.0.0

Guards, und warum v1 nicht durchsetzt

Eine Härtungsrichtlinie kann Sie aus der Maschine aussperren, die Sie gerade härten. bitenforcer misst dieses Risiko an der Sitzung, in der Sie tatsächlich arbeiten, und meldet es, bevor irgendetwas angewendet wird. Dieselben Guards sind der Grund, warum v1 überhaupt nichts anwendet: Sieben unabhängige adversariale Prüfer kamen jeweils mit einem Aussperr-Muster an, das sie nicht erkannten.

Diese Seite behandelt die Guards, wie Sie eine Richtlinie in CI daran absichern, und die vollständige Darlegung, warum die Durchsetzung außerhalb des Funktionsumfangs dieses Releases liegt. Was das Werkzeug heute tut, steht im bitenforcer-Überblick.

Die Lockout-Guards​

Achtzehn Guards, jeder für eine Art, wie eine Härtungsrichtlinie Sie aus Ihrer eigenen Maschine aussperren kann. Auflisten mit 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

Vollständige Liste: 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.

Zwei Design-Regeln sind wichtig:

Es gibt kein globales --force

Ein Guard wird nur durch namentliche Nennung außer Kraft gesetzt — --acknowledge <guard-id> — und einen Guard zu quittieren lässt jeden anderen scharf. unreviewed-change lässt sich überhaupt nicht als Klasse quittieren: Das Token benennt genau ein Modul, eine Art und einen Schlüssel, und die Ablehnung selbst gibt das exakte zu verwendende Token aus.

Eine Sitzung, die nicht gemessen werden konnte, ist nicht quittierbar — für jeden Lauf, der den Fernzugriff berührt. Wer als Operator nicht lokalisiert werden kann, kann nicht geschützt werden, und etwas anderes vorzugeben ist genau der Weg, auf dem Menschen sich aussperren.

Eine Richtlinie in CI absichern​

--guards=blocking verwandelt jede nicht quittierte Feststellung in einen Exit-Code ungleich null:

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

So verhindern Sie, dass eine Härtungs-Baseline, die den Fernzugriff kappen würde, überhaupt jemals ein Änderungsfenster erreicht.

Warum die Durchsetzung nicht in v1 ist​

Der Durchsetzungspfad wurde gebaut. Er wurde anschließend siebenmal adversarial geprüft, von sieben unabhängigen Prüfern auf echten Hosts. Jeder Einzelne von ihnen hat sich entweder selbst aus dem Testhost ausgesperrt oder die Bestätigung gefälscht, die genau das hätte abfangen sollen — zehn verschiedene Aussperr-Muster, mehrere davon meldeten dabei Erfolg und Exit-Code 0.

Die Muster waren nie zweimal dieselben: sshd_config-Bytes; ein Firewall-Plan; ein systemd-Härtungs-Drop-in, das kein „stop“ ist und deshalb den Evaluator nie erreichte; eine Eigentümer-/Gruppenänderung an authorized_keys; ein sysctl außerhalb der beiden Muster, die ein Evaluator erkannte; eine „überlebende“ Authentifizierungsmethode, die das nur mit Schlüssel ausgestattete Konto des Operators gar nicht nutzen konnte; eine über Loopback akzeptierte Bestätigung; eine Prüfung, die nicht durchgeführt werden konnte und als Fehlschlag gelesen wurde.

Jede Welle schloss das Muster, das ihr gezeigt worden war, und der nächste Prüfer kam mit einem neuen an. Das ist die Signatur eines Open-World-Problems: „zähle jede Möglichkeit auf, wie eine Richtlinie diesen Host unerreichbar machen könnte“ hat kein letztes Element, und jeder Build der Guards ist eine Liste der Angriffe, die schon jemand bedacht hat.

Ein Totmann-Rollback wurde genau deshalb gebaut, weil es keine Aufzählung braucht — was auch immer den Host kaputt gemacht hat, die Bestätigung trifft nicht ein und die Änderung wird zurückgenommen. Es ist das richtige Modell, und der Code bleibt erhalten. Auch er hat die Überprüfung nicht überstanden, und seine letzte Feststellung beendete die Diskussion:

Die Wiederherstellungsmaschinerie wurde selbst zu einer lokalen Root-Rechteausweitung. Das Totmann-Rollback stellte eine Berechtigung über einen Symlink wieder her, den eine unprivilegierte Benutzerin in ihrem eigenen ~/.ssh platziert hatte, und brachte /etc/shadow von 0640 auf 0777. Das Manifest des Laufs vermerkte „4 restored, 0 failed“.

Das auszuliefern hätte auf jeder Kundenmaschine eine Root-Rechteausweitung installiert, geliefert vom Sicherheitsnetz. Deshalb liegt die Durchsetzung außerhalb des Funktionsumfangs von v1.

Was bewiesen wurde, und wie​

Ein Fingerabdruck des gesamten Dateisystems — Pfad, Modus, Eigentümer, Größe und SHA-256 jeder Datei unter /etc, /var/lib und den systemd-Bäumen —, erhoben vor und nach einem Lauf aller Module als Root, in einem Container mit echtem sshd, nft, iptables und getenforce, ist identisch. Sowohl für apply (das sich verweigert) als auch für apply --dry-run (das berichtet). Das sind 39 Zusicherungen im End-to-End-Oberflächentest des Repositories.

Zwei Builds wurden anschließend absichtlich falsch gemacht und durch dasselbe Skript geschickt, denn ein Test, den niemand zu brechen versucht hat, ist Dekoration:

  • Scope-Statement gelöscht → 6 Fehlschläge, darunter ROOT apply CHANGED the filesystem;
  • der zurückgehaltene deadman-Befehl wieder registriert → 7 Fehlschläge, jeder benennt den Befehl, der zurückkam, und den Zustand, den er erzeugt hat.

Was v1 bewusst nicht ausliefert​

BefehlIn v1Warum
validate✅Weist eine ungültige Richtlinie zurück — und weist Schlüssel zurück, die nichts implementiert.
apply --dry-run✅Der Pre-Flight.
apply --dry-run --guards=blocking✅Richtlinien-Gate für CI.
guards, session, version✅Rein lesend.
generate✅Rein lesend außer mit --output-file, das schreibt — siehe die Warnung unten.
apply (durchsetzend)❌Nennt den Funktionsumfang des Releases in EN + FR, benennt, was Sie stattdessen ausführen können, und endet mit Exit-Code ungleich null.
rollback, confirm❌Nichts in v1 kann einen Snapshot erzeugen oder ein Rollback scharfstellen, sodass beide immer nur antworten könnten „hier ist nichts“ — was trotzdem eine Fähigkeit bewirbt.
deadman❌Zurückgehalten. Wiederherstellen ist ein Ändern des Hosts, über ein vom Operator angegebenes Zustandsverzeichnis — genau hier saß die Rechteausweitung.
--output-file schreibt auf den Host und überschreibt eine vorhandene Datei

--output-file ist ein globales Flag, gilt also auch für generate. Ist es gesetzt, legt generate das übergeordnete Verzeichnis an (0750) und schreibt die Datei (0640) — ohne Existenzprüfung, ohne O_EXCL und ohne Rückfrage. Zeigt es auf einen Pfad, der bereits existiert, ersetzt es diese Datei.

Diese Seite führte generate zuvor unter den Befehlen auf, die „alle rein lesend“ seien, und genau darin lag die Gefahr: Im Vertrauen auf diesen Satz richtete ein Operator --output-file auf einen Pfad unter /etc, und der Befehl überschrieb die dort bereits vorhandene Konfiguration. Alles Übrige in der Tabelle ist tatsächlich rein lesend; dieses eine Flag ist die Ausnahme, und sie wird hier benannt statt stillschweigend korrigiert.

apply ohne --dry-run auszuführen ist kein stiller No-Op. Es sagt Ihnen die Wahrheit und endet mit einem Exit-Code ungleich null:

$ 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. […]

Nichts wird aus der Codebasis gelöscht. Die Engine, die Guards, der Snapshot-/Rollback-Code und die Totmann-Maschinerie bleiben allesamt erhalten, mit ihren Tests. Sie sind das Fundament des nächsten Releases. Sie sind schlicht an nichts angeschlossen, das Ihren Host ändern kann.

Wann die Durchsetzung zurückkommt​

Die Messlatte ist aufgeschrieben, damit sie später nicht heruntergehandelt werden kann:

Ein unabhängiger Prüfer muss daran scheitern, eine Bestätigung zu fälschen, UND daran scheitern, ein Rollback zu überleben — auf einem echten Host, zweimal hintereinander.

Zwei aufeinanderfolgende saubere adversariale Durchgänge durch einen Prüfer, der die Korrektur nicht selbst geschrieben hat — nicht „die Muster, die wir jetzt erkennen, sind abgedeckt“, eine Behauptung, die bereits siebenmal aufgestellt und widerlegt wurde.

Nächste Schritte​

War diese Seite hilfreich?