Passa al contenuto principale
Versione: 1.0.0

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:

Non esiste un --force globale

Una 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/shadow da 0640 a 0777. 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 deadman trattenuto reinserito → 7 fallimenti, ciascuno con il nome del comando ricomparso e lo stato che ha creato.

Cosa la v1 deliberatamente non include​

ComandoNella v1Perché
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​

Questa pagina ti è stata utile?