Le guardie, e perché la v1 non applica nulla
Una policy di hardening può escluderti dalla macchina che stai mettendo in sicurezza.
bitenforcer misura quel rischio rispetto alla sessione in cui stai effettivamente lavorando
e lo riporta prima che venga applicato qualsiasi cosa. Quelle stesse guardie sono il motivo
per cui la v1 non applica assolutamente nulla: sette revisori avversariali indipendenti sono
arrivati ciascuno con una forma di lockout che non riconoscevano.
Questa pagina copre le guardie, come mettere una policy sotto gate in CI e il resoconto completo del perché l'enforcement è fuori dall'ambito di questa release. Per cosa fa lo strumento oggi, vedi la panoramica di bitenforcer.
Le guardie anti-lockout
Diciotto guardie, ciascuna dedicata a un modo in cui una policy di hardening può escluderti
dalla tua stessa macchina. Elencale con 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
Insieme completo: 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.
Contano due regole di progettazione:
--force globaleUna guardia si disarma solo nominandola — --acknowledge <guard-id> — e riconoscere una
guardia lascia armate tutte le altre. unreviewed-change non può essere riconosciuta come
classe: il token nomina un solo modulo, tipo e chiave, e il rifiuto stesso stampa l'esatto
token da usare.
Una sessione che non è stato possibile misurare non è riconoscibile per nessuna esecuzione che tocchi l'accesso remoto. Un operatore che non può essere localizzato non può essere protetto, e fingere il contrario è il modo in cui si finisce chiusi fuori.
Mettere una policy sotto gate in CI
--guards=blocking trasforma qualsiasi rilievo non riconosciuto in un'uscita non zero:
bitenforcer apply --config policy.yaml --dry-run --guards=blocking
# exit 1 when any guard fires — fails the pipeline
È così che impedisci a una baseline di hardening che reciderebbe l'accesso remoto di arrivare mai a una finestra di manutenzione.
Perché l'enforcement non è nella v1
Il percorso di enforcement è stato costruito. È poi stato revisionato sette volte, in modo
avversariale, da sette revisori indipendenti su host reali. Ognuno di loro, senza eccezioni,
si è chiuso fuori dall'host di test oppure ha falsificato la conferma che avrebbe dovuto
impedirlo — dieci distinte forme di lockout, molte delle quali riportavano al momento
successo e codice di uscita 0.
Le forme non sono mai state uguali due volte: i byte di sshd_config; un piano di firewall; un
drop-in di hardening systemd che non è uno "stop" e quindi non raggiungeva mai il valutatore;
una modifica di proprietario/gruppo su authorized_keys; una sysctl fuori dai due pattern che
un valutatore riconosceva; un metodo di autenticazione "sopravvissuto" che l'account
dell'operatore, basato solo su chiave, non poteva effettivamente usare; una conferma accettata
su loopback; una verifica che non poteva essere eseguita, interpretata come un fallimento.
Ogni ondata ha chiuso la forma che le era stata mostrata, e il revisore successivo è arrivato con una nuova. È la firma di un problema a mondo aperto: "enumera ogni modo in cui una policy potrebbe rendere questo host irraggiungibile" non ha un ultimo elemento, e qualsiasi build delle guardie è l'elenco degli attacchi a cui qualcuno ha già pensato.
Un rollback dead-man è stato costruito proprio perché non richiede alcuna enumerazione: qualunque cosa abbia rotto l'host, la conferma non arriva e la modifica viene annullata. È il modello giusto e il codice viene conservato. Nemmeno lui è sopravvissuto alla revisione, e il suo ultimo rilievo ha chiuso la discussione:
Il meccanismo di ripristino è diventato un'escalation di privilegi a root locale. Il rollback dead-man ha ripristinato un permesso attraverso un symlink che un utente senza privilegi aveva piazzato nella propria
~/.ssh, portando/etc/shadowda0640a0777. Il manifest dell'esecuzione registrava "4 restored, 0 failed".
Distribuire quella cosa avrebbe messo un'escalation di privilegi a root su ogni macchina dei clienti, consegnata dalla rete di sicurezza. Quindi l'enforcement è fuori dall'ambito della v1.
Cosa è stato dimostrato, e come
Un'impronta dell'intero filesystem — percorso, mode, proprietario, dimensione e SHA-256 di
ogni file sotto /etc, /var/lib e gli alberi systemd — presa prima e dopo un'esecuzione
con tutti i moduli come root, in un container con sshd, nft, iptables e getenforce
reali, è identica. Sia per apply (che rifiuta) sia per apply --dry-run (che riporta).
Sono 39 asserzioni nel test end-to-end di superficie del repository.
Sono poi state prodotte due build deliberatamente errate ed eseguite attraverso lo stesso script, perché un test che nessuno ha provato a rompere è decorazione:
- dichiarazione di ambito cancellata → 6 fallimenti, incluso
ROOT apply CHANGED the filesystem; - il comando
deadmantrattenuto reinserito → 7 fallimenti, ciascuno con il nome del comando ricomparso e lo stato che ha creato.
Cosa la v1 deliberatamente non include
| Comando | Nella v1 | Perché |
|---|---|---|
validate | ✅ | Rifiuta una policy non valida — e rifiuta chiavi che nulla implementa. |
apply --dry-run | ✅ | Il pre-flight. |
apply --dry-run --guards=blocking | ✅ | Gate di policy per la CI. |
guards, session, version | ✅ | In sola lettura. |
generate | ✅ | In sola lettura tranne con --output-file, che scrive — vedi l'avvertenza qui sotto. |
apply (con enforcement) | ❌ | Dichiara l'ambito della release in EN + FR, indica cosa puoi eseguire, esce con codice non zero. |
rollback, confirm | ❌ | Nulla nella v1 può creare uno snapshot o armare un rollback, quindi entrambi potrebbero solo rispondere "qui non c'è nulla" — il che pubblicizza comunque una capacità. |
deadman | ❌ | Trattenuto. Ripristinare significa modificare l'host, su una directory di stato fornita dall'operatore — è qui che viveva l'escalation di privilegi. |
--output-file scrive sull'host e sovrascriverà un file esistente--output-file è un flag globale, quindi si applica anche a generate. Quando è presente,
generate crea la directory padre (0750) e scrive il file (0640) — senza alcun controllo
di esistenza, senza O_EXCL e senza conferma. Puntarlo a un percorso che esiste già
sostituisce quel file.
Questa pagina elencava in precedenza generate tra i comandi "tutti in sola lettura", ed è lì
che stava il pericolo: sulla fiducia di quella frase un operatore ha puntato --output-file a
un percorso sotto /etc, e il comando ha sovrascritto la configurazione che già si trovava lì.
Tutto il resto della tabella è genuinamente in sola lettura; questo flag è l'eccezione, ed è
segnalato qui anziché corretto in silenzio.
Eseguire apply senza --dry-run non è un no-op silenzioso. Ti dice la verità ed esce con
codice non zero:
$ 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. […]
Nulla è stato cancellato dal codice. Il motore, le guardie, il codice di snapshot/rollback e il meccanismo dead-man rimangono tutti, con i loro test. Sono le fondamenta della prossima release. Semplicemente non sono collegati a nulla che possa modificare il tuo host.
Quando tornerà l'enforcement
L'asticella è messa per iscritto, così non può essere negoziata al ribasso in seguito:
Un revisore indipendente deve fallire nel falsificare una conferma E fallire nel sopravvivere a un rollback, su un host reale, due volte di seguito.
Due passaggi avversariali puliti consecutivi, da parte di un revisore che non ha scritto la correzione — non "le forme che ora riconosciamo sono coperte", un'affermazione già fatta e falsificata sette volte.
Passi successivi
- Panoramica di bitenforcer — cosa fanno
validateeapply --dry-run, con output reale. - bitcollector — la metà della coppia che riporta, e da dove arrivano le evidenze continue sulla postura.
- Framework di conformità — come si mappano i controlli.
Questa pagina ti è stata utile?