Tutanum Classico
Introduzione
Per le basi (didattica): Il percorso della didattica.
Per i motivi (discussione): Indice della discussione, Che cosa è Tutanum?, Modello di minaccia.
Questa guida costruisce un nodo Tutanum su hardware scelto apposta per lo scopo. È la parte pratica della Discussione: lì si trova il perché di ogni scelta, qui la sequenza delle operazioni. Ogni passo rimanda alla sezione che lo motiva, e va letta almeno una volta prima di eseguirlo.
Chi vuole invece riutilizzare un computer che possiede già segue Tutanum Lieve, che parte da questa stessa guida e vi aggiunge le mitigazioni necessarie.
La costruzione procede per livelli, dal ferro ai servizi. Ogni livello dà per fatto il precedente.
| Livello | Che cosa si ottiene | Discussione di riferimento |
|---|---|---|
| 1. Hardware e firmware | Una macchina adatta, collocata, alimentata e collegata nel modo giusto | Hardware, Firmware |
| 2. Sistema operativo | FreeBSD installato offline, cifrato e messo in sicurezza (hardening) | Sistema operativo |
| 3. Firewall e DNS | Filtro a due livelli e risoluzione dei nomi cifrata | Firewall, Proxy |
| 4. Jail | Un compartimento per ogni servizio | Namespace, container e jail |
| 5. Reti anonime | Tor, I2P e iop | Tor, I2P |
| 6. Servizi | Sito come onion service, SSH, trasferimento file | Servizi esposti |
| 7. Monitoraggio e manutenzione | Audit, difesa, backup e aggiornamenti | Monitoraggio e difesa, Backup |
Prima di iniziare
Per le basi (didattica): La sicurezza di rete.
Per i motivi (discussione): OPSEC: il livello umano, Password, Crittografia.
Materiale
- Il computer che diventerà il nodo, scelto come indicato al livello 1.
- Una SSD SATA nuova. Niente chiavette USB o microSD come disco di sistema: con ZFS si corrompono in fretta (vedi Estensioni).
- Un DVD-R e un lettore CD/DVD USB. Un supporto che non si può riscrivere non può essere infettato dopo la masterizzazione.
- Un secondo computer, fidato, per scaricare e verificare l'immagine e per consultare la documentazione mentre il nodo resta scollegato.
- Un cavo di rete e un router proprio, non quello fornito dal provider.
- Le passphrase si tengono a mente oppure in un password manager sul computer fidato. Non si scrivono mai su carta.
Regole che valgono per tutta la guida
- Scollegato dalla rete finché il firewall non è attivo: il cavo di rete resta scollegato dall'installazione fino a quando il firewall del livello 3 è in funzione (vedi Rete).
- Niente wireless: Wi-Fi e Bluetooth vanno disattivati e, dove possibile, rimossi fisicamente.
- Tre passphrase distinte: cifratura del disco, root e utente di amministrazione. Si costruiscono come indicato in Password.
- Nomi che dicono a cosa serve ogni cosa: nome host, utenti e jail devono far capire a colpo d'occhio
la loro funzione. Si difende bene solo ciò che si riconosce: un sistema che il suo amministratore non
padroneggia è debole e resta in balia degli automatismi. Fuori dai nomi resta soltanto l'identità di chi
amministra (nome, cognome, soprannomi). In questa guida il nodo si chiama
nodo, l'amministratoreoperatoree ogni jail prende il nome del servizio che ospita (tor,i2p,web). - Un servizio, una jail: sull'host non gira nulla che ascolti la rete.
- Mai root direttamente: si lavora dall'utente di amministrazione, un comando per volta con
doas(vedi Privilegi amministrativi).
Negli esempi la rete di casa è 192.168.178.0/24, l'interfaccia di rete è em0 e le jail
stanno in 10.0.0.0/24. Vanno sostituiti con i propri valori.
Livello 1: hardware e firmware
Per le basi (didattica): Architettura di Neumann, Memoria, bus e ingresso/uscita, Circuiti e componenti, Campi e onde.
Per i motivi (discussione): CPU, Posizionamento, Alimentazione, BIOS e UEFI, Intel Management Engine e AMD PSP, Attacchi hardware delle intelligence.
Scegliere la macchina
I requisiti sono quelli elencati in Quali sono le caratteristiche di Tutanum?: CPU supportata da FreeBSD o OpenBSD, almeno 4 core (meglio 8), almeno 4 GB di RAM, bassi consumi, hardware stabile e facile da reperire. A questi il Classico aggiunge il requisito che lo distingue dal Lieve: una catena di avvio che si possa verificare.
| Strada | Esempio | Punti di forza | Da verificare prima dell'acquisto |
|---|---|---|---|
| Single board computer ARM | Banana Pi BPI-M7 (RK3588, da 8 a 32 GB di RAM), serie BPI-M* | Niente Intel ME né AMD PSP, consumi minimi, clusterizzabile | Lo stato del supporto della scheda in FreeBSD o OpenBSD, e la presenza di binari nel boot loader |
| x86 con firmware libero | Una scheda tra quelle supportate da Libreboot o Coreboot | Supporto pieno del sistema operativo, firmware sostituito e Intel ME neutralizzato | Che il modello esatto sia nell'elenco di compatibilità |
| RISC-V | StarFive VisionFive | Architettura aperta | È ancora presto per la produzione: solo per sperimentare |
Sono da escludere gli smartphone e, per il Classico, il Raspberry Pi, che si avvia con firmware proprietario (vedi Single Board Computer). Un comune PC con UEFI proprietario e Intel ME attivo non è un Classico: è un Lieve.
La macchina si acquista in contanti, in negozio, senza farsela spedire: una spedizione può essere intercettata (vedi Catena di fornitura).
Preparare la macchina
- Fotografare: scheda, saldature, etichette e sigilli, appena aperta la confezione. Le foto sono il riferimento per accorgersi di una manomissione futura.
- Togliere ciò che non serve: moduli Wi-Fi e Bluetooth, microfoni, webcam, altoparlanti. Ciò che non c'è non può essere acceso da remoto.
- Chiudere le interfacce pericolose: le porte con accesso diretto alla memoria (Thunderbolt, FireWire, ExpressCard) e quelle di debug (JTAG, UART) vanno disattivate o sigillate (vedi Attacchi con accesso fisico).
- Firmware: installare Libreboot o Coreboot dove previsto, annotarne l'impronta, impostare una password e disattivare l'avvio da rete e da USB. Sulle macchine x86 l'installazione avverrà in modalità BIOS con tabella GPT, non UEFI (vedi BIOS e UEFI).
- Orologio: rimuovere la batteria tampone e usare un modulo RTC esterno (DS3231), come indicato in Tempo. Prima si controlla che il firmware conservi password e impostazioni anche senza batteria: su molte schede si perdono, e in quel caso la batteria resta e il modulo esterno si aggiunge.
- Sigillare il case per accorgersi se qualcuno lo apre: applicare sulle viti e sulle giunture dei sigilli antimanomissione numerati, oppure una goccia di smalto con brillantini. I brillantini si dispongono a caso e nessuno riesce a rifare lo stesso disegno, quindi chi svita e richiude lascia una traccia. Fotografare ogni sigillo da vicino e confrontare la foto a ogni controllo (vedi Attacchi con accesso fisico).
Collocare, alimentare, collegare
- Posizione: ancorata se fissa, in valigetta ventilata se deve muoversi, comunque fuori dalla vista e lontana dalle finestre (vedi Posizionamento e Compromissione elettromagnetica).
- Ambiente: temperatura e umidità stabili. Oltre che per l'hardware, una temperatura costante toglie un canale laterale (vedi Calore e umidità).
- Alimentazione: un gruppo di continuità, così che un'interruzione di corrente nel quartiere non faccia sparire il servizio nello stesso istante (vedi Alimentazione).
- Rete: solo cavo, dietro un router proprio riportato alle impostazioni di fabbrica, con la password di amministrazione cambiata, il Wi-Fi spento e il nodo in una zona separata dagli altri dispositivi di casa.
Livello 2: sistema operativo
Per le basi (didattica): Sistemi operativi, File system, Processi e thread, Kernel e MINIX.
Per i motivi (discussione): Distribuzioni, Filesystem, Crittografia, Boot loader, Gestione degli utenti e dei gruppi, Verifica e integrità del software.
La guida usa FreeBSD, per le ragioni discusse in Distribuzioni. Lo stesso percorso vale, con comandi diversi, per OpenBSD.
Scaricare e verificare l'immagine
Sul computer fidato si scarica l'immagine dvd1 dell'ultima RELEASE dal
sito di FreeBSD, insieme al file delle somme di controllo
firmato. Un'immagine non verificata non si masterizza (vedi
Verifica e integrità del software).
# verificare la firma del file delle somme, poi la somma dell'immagine
gpg --verify CHECKSUM.SHA256-FreeBSD-*-RELEASE-amd64.asc
sha256sum -c CHECKSUM.SHA256-FreeBSD-*-RELEASE-amd64 --ignore-missing
# masterizzare su DVD-R e chiudere il disco, così che non sia più scrivibile
Sulle schede ARM l'installazione parte da un'immagine scritta sul supporto di avvio anziché da un DVD: la verifica è la stessa e le schermate del programma di installazione, dove è disponibile, non cambiano.
Installazione
Importante: procedere offline, con il cavo di rete scollegato. Conviene tenere aperto sul secondo computer il manuale di FreeBSD.
- Dal computer fidato masterizzare sul DVD-R l'immagine verificata al passo precedente, di FreeBSD oppure di OpenBSD, e collegare il lettore USB al computer che ospiterà Tutanum.
- Immettere il disco nel lettore e procedere ad installare il sistema operativo come segue
(per questa guida abbiamo scelto FreeBSD):
-
Selezionare "Boot Multi User" premendo il tasto invio.
-
Scegliere "Install" premendo il tasto invio.
-
Muoversi con le frecce su e giù della tastiera e posizionarsi su "Italian" e premere spazio per selezionare la mappatura italiana della tastiera.
Al fine di assicurarsi che sia la mappatura corretta selezionare con il tasto invio la voce "Test it.kbd keymap".
Per accettare l'impostazione inserita, muoversi su "Continue with it.kbd keymap" e premere invio.
-
Impostare come hostname un nome generico, che non dica nulla del nodo né di chi lo gestisce (per esempio "nodo"), e proseguire premendo il tasto invio.
-
Muovendosi con le frecce su e giù della tastiera selezionare con la barra spaziatrice le voci "ports" per il supporto al software a cui hanno fatto il porting (solitamente da GNU Linux) e "src" per la compilazione del software. La voce "lib32" (librerie a 32 bit) resta deselezionata: nessun servizio del nodo ne ha bisogno, e sarebbe solo codice in più da attaccare.
-
Selezionare la voce "Auto (ZFS)" per procedere con il partizionamento guidato del disco con filesystem di tipo OpenZFS.
FreeBSD usa come filesystem predefinito OpenZFS, erede dello ZFS di Sun Microsystems. La cifratura del disco proposta dall'installatore è GELI, che cifra l'intera partizione sotto ZFS; la cifratura nativa di OpenZFS è un'altra cosa, che si applica ai singoli dataset. Il sistema si carica con il boot loader di FreeBSD (gptzfsboot e loader), più piccolo di GRUB e senza i suoi problemi. Il codice di avvio resta però fuori dalla parte cifrata: per questo l'host lavora con il securelevel a 2 (vedi Hardening).
-
Spostarsi con le frecce su e giù della tastiera per selezionare le voci da cambiare. Per potere scegliere un altro valore è possibile premere il tasto invio della tastiera una volta posizionati sulla voce interessata.
È necessario impostare "Encrypt Disks" su "YES", "Partition Scheme" su "GPT (BIOS)" e "Encrypt Swap" su "YES". Una volta terminato posizionarsi sulla voce "Install" e premere invio.
-
Selezionare la voce "stripe" e premere invio, così da non impostare nessun RAID: ogni nodo ha un disco fisico proprio, e la ridondanza si ottiene con più nodi e con i backup, non con più dischi nella stessa macchina.
-
Selezionare con la barra spaziatrice il block device interessato e premere il tasto invio. Le voci potrebbero variare: le schermate di questa guida vengono da un'installazione di prova in macchina virtuale.
-
Spostarsi con le frecce sinistra e destra su "YES" e premere invio per procedere con la formattazione dei dischi (assicurarsi di non averci salvato sopra nulla di importante in quanto la memoria andrà ad essere formattata).
-
Impostare una passphrase per la cifratura del disco.
Digitare la password scelta e premere invio.
-
Reinserire la password e premere invio.
-
Attendere la fine della formattazione del disco e l'installazione del sistema operativo.

-
Seguendo i consigli precedentemente riportati, impostare una password per root. Verrà richiesto di inserirla due volte. Per i non conoscenti dell'inserimento delle password sui sistemi Unix like, non verrà mostrata al momento della digitazione, questo per motivi di sicurezza.
-
Selezionare l'interfaccia di rete appropriata e premere invio.
-
Selezionare la voce "Yes" e premere invio.
-
Alla domanda sul DHCP selezionare "No" e, nella schermata che segue, inserire l'indirizzo statico del nodo (in questa guida 192.168.178.3, maschera 255.255.255.0, router 192.168.178.1). sshd, sshguard e pf lo danno per fisso, e con il MAC casuale a ogni avvio il DHCP lo cambierebbe a ogni riavvio. La schermata mostra il "Yes" dell'installazione di prova.
-
Selezionare la voce "No" e premere invio, in quanto dall'IPv6 è possibile ricostruire il Mac Address.
-
Nella schermata dei DNS lasciare il router, selezionare "OK" spostandosi con il tasto Tab e premere invio. Verrà sostituito da dnscrypt-proxy al livello 3.
-
Digitare il tasto 7 per selezionare "Europe" come zona temporale e poi premere invio.
-
Selezionare "Italy" (è il 22) usando le frecce della tastiera e premere invio.
-
Premere invio per confermare le scelte configurate.
-
Selezionare "Set Date" usando le frecce sinistra e destra della tastiera e poi premere invio.
-
Procedere nello stesso modo con l'ora attuale.
-
Deselezionare tutte le voci con la barra spaziatrice e premere "OK". Se non si usa un servizio è necessario non installarlo o non renderlo attivo.
-
Selezionare con la barra spaziatrice tutte le voci e premere "OK". In questo modo il sistema operativo verrà messo in hardening. Tra le voci c'è "Secure console": da qui in poi la modalità utente singolo chiede la password di root, che quindi non va mai bloccata (vedi Primo aggiornamento e doas).
-
Selezionare "Yes" e premere invio per creare un nuovo utente non root.
-
Inserire a preferenza il nome dell'utente non root da utilizzare e premere invio. Evitare ovviamente di utilizzare nomi personali o che possano ricondurre a sé, ma che siano facilmente ricordabili. Per Uid, Login group, Login group, Login class, Shell, Home directory, Home directory permissions e per le ulteriori voci che seguono, premere invio, usando pertanto il valore di default. Quando verrà richiesto l'inserimento di una password, crearne una apposita per l'utente creato, che sia diversa da quella di root e dalla cifratura del disco.
-
Confermare la scelta scrivendo "yes" e poi premendo invio.
-
Scrivere "no" e premere invio.
-
Selezionare la voce "Exit" e premere invio.
-
Spostarsi sulla voce "Yes" con le frecce sinistra e destra della tastiera e premere invio.
-
Alla richiesta di aprire una shell per le ultime modifiche manuali, rispondere "Yes": le modifiche sono quelle del passo seguente di questa guida.
-
Prima configurazione
Nella shell aperta dal programma di installazione, o al primo accesso come root, si sistema
/etc/rc.conf. Il file completo e commentato è config/rc.conf,
descritto in Configurazioni di esempio. Le righe
essenziali a questo punto sono queste.
# /etc/rc.conf
hostname="nodo" # generico: il nome compare in registri e messaggi
clear_tmp_enable="YES"
syslogd_flags="-ss" # nessun socket di rete per syslog
sendmail_enable="NONE"
dumpdev="NO" # niente crash dump su disco
zfs_enable="YES"
ntpd_enable="NO" # niente NTP: vedi "Il tempo come vettore d'attacco"
ipv6_network_interfaces="none"
ipv6_activate_all_interfaces="NO"
ifconfig_em0="ether random" # indirizzo MAC casuale a ogni avvio
ifconfig_em0_alias0="inet 192.168.178.3/24" # indirizzo statico: con il MAC casuale il DHCP lo cambierebbe
defaultrouter="192.168.178.1"
cloned_interfaces="lo1" # l'interfaccia delle jail (livello 4)
ifconfig_lo1="inet 10.0.0.1/24" # l'host visto dalle jail: vi ascolterà dnscrypt-proxy
Le ragioni sono in IPv6 e MAC Address e in
Il tempo come vettore d'attacco. Si riavvia con reboot
e si accede come root dalla console: fino al primo aggiornamento, quando verrà installato doas,
i comandi di questa guida preceduti da doas si eseguono direttamente da root.
Messaggio di accesso
Il messaggio predefinito annuncia sistema e versione del kernel a chiunque apra una sessione. Lo si sostituisce con un testo che non descrive nulla (vedi Il messaggio di accesso e le jail).
doas sysrc update_motd=NO
printf 'host. Qui non gira alcun servizio: ogni servizio ha la propria jail.\nOgni comando privilegiato passa da doas e viene registrato.\n' | doas tee /etc/motd.nodo
# /etc/login.conf, classe "default": aggiungere :welcome=/etc/motd.nodo:\ e togliere la riga :copyright=...
doas cap_mkdb /etc/login.conf
Hardening
Le impostazioni seguono la pagina security(7) del manuale, spiegata voce per voce in
FreeBSD: nucleo del kernel, dispositivi grezzi e filesystem,
le indicazioni di Kicksecure tradotte per FreeBSD e le contromisure
contro la fuga dell'ora.
# /etc/sysctl.conf
security.bsd.see_other_uids=0 # ognuno vede solo i propri processi
security.bsd.see_other_gids=0
security.bsd.see_jail_proc=0
security.bsd.unprivileged_read_msgbuf=0 # il log del kernel solo a root
security.bsd.unprivileged_proc_debug=0 # niente ptrace tra processi non privilegiati
security.bsd.conservative_signals=1
security.bsd.hardlink_check_uid=1
security.bsd.hardlink_check_gid=1
kern.randompid=1
kern.coredump=0
kern.elf64.aslr.enable=1 # randomizzazione degli indirizzi
kern.elf64.aslr.pie_enable=1
kern.elf64.aslr.stack=1
kern.elf64.aslr.honor_sbrk=0
kern.elf64.nxstack=1 # stack non eseguibile
kern.elf64.allow_wx=0 # W^X: mai pagine scrivibili ed eseguibili insieme
machdep.syscall_ret_flush_l1d=1
machdep.mitigations.rngds.enable=1
net.inet.ip.random_id=1
net.inet.tcp.rfc1323=0 # niente timestamp TCP (né window scaling: meno banda, nessun orologio esposto)
net.inet.icmp.tstamprepl=0 # niente timestamp ICMP
net.inet.tcp.blackhole=2 # nessuna risposta sulle porte chiuse
net.inet.udp.blackhole=1
net.inet.ip.redirect=0
net.inet.icmp.drop_redirect=1
net.link.ether.ipfw=1 # ipfw esamina anche i frame ethernet (livello 2)
net.inet.tcp.drop_synfin=1 # scarta i SYN+FIN, tipici delle scansioni
security.bsd.unprivileged_mlock=0 # solo root blocca pagine in memoria
security.bsd.unprivileged_idprio=0 # solo root usa le priorità di inattività
Sulle macchine x86 si aggiungono le mitigazioni dei difetti del processore, le stesse di
Tutanum Lieve: anche una scheda con Coreboot monta una CPU con Meltdown,
Spectre e varianti (vedi CPU). Il microcodice
(cpu-microcode) si installa con il primo aggiornamento.
# /boot/loader.conf (solo x86)
vm.pmap.pti=1 # Meltdown
machdep.hyperthreading_allowed=0 # niente hyper-threading: toglie una famiglia di canali laterali
# /etc/sysctl.conf (solo x86)
hw.ibrs_disable=0 # Spectre v2
hw.mds_disable=3 # MDS
hw.spec_store_bypass_disable=2 # Spectre v4
# /etc/rc.conf
auditd_enable="YES"
# il securelevel si alza solo all'ultimo passo, "Sigillare i file di avvio"
# /etc/fstab: le cartelle scrivibili da tutti non eseguono
tmpfs /tmp tmpfs rw,noexec,nosuid,mode=1777 0 0
doas zfs set exec=off zroot/var/tmp
doas zfs set setuid=off zroot/var/tmp
# i file nuovi non leggibili dagli altri utenti: in /etc/login.conf, classe default, :umask=027:
# root però resta a 022: doas e pkg ereditano la umask, e con 027 le cartelle create installando
# i pacchetti (provato: /usr/local/sbin) diventano illeggibili per gli utenti dei servizi, e
# dnscrypt-proxy non parte ("No such file or directory"). In /etc/login.conf, classe root, :umask=022:
doas cap_mkdb /etc/login.conf
doas pw usermod root -L root # root nella propria classe (anche in ogni jail)
doas sh -c umask; umask # -> 0022 con doas, 0027 per operatore
# toor è un secondo account con UID 0, senza password ma inutile: si toglie
doas pw userdel toor
# cron e at soltanto per root: un utente compromesso non si lascia dietro compiti ripetuti
echo root | doas tee /var/cron/allow /var/at/at.allow > /dev/null
doas chmod 600 /var/cron/allow /var/at/at.allow
allow_wx=0 blocca i programmi che generano codice mentre girano: va provato con ogni servizio
prima di renderlo definitivo.
A nodo finito l'host lavora al securelevel 2. Il livello 1 non basta: lascia scrivere sui dischi non montati, e quindi sulla partizione con il codice di avvio, che sta fuori dalla parte cifrata. Un intruso diventato root potrebbe sostituirlo e catturare la passphrase del disco al riavvio successivo. Il livello 2 ha però un effetto che va conosciuto prima: su FreeBSD 15 congela pf. Non si caricano regole, non si aggiungono indirizzi alle tabelle, non si chiudono connessioni e non si spegne il firewall; si possono soltanto togliere indirizzi. Per questo il livello si alza all'ultimo passo, quando pacchetti e jail sono a posto, e tutto ciò che deve cambiare a sistema avviato (sshguard, contenimento, esche) vive nelle tabelle di ipfw, che al livello 2 restano modificabili. Il livello 3, che congela anche ipfw, si assegna alle jail come indicato al livello 4 (vedi Il livello di sicurezza del kernel). Dal livello 2 il kernel sposta l'orologio al massimo di un secondo per volta e non lo riporta indietro: va regolato prima, come indicato in Orologio.
Con il securelevel attivo i moduli del kernel non si caricano più a sistema avviato: pf, ipfw e ogni altro modulo
necessario vanno dichiarati in /boot/loader.conf o in rc.conf prima del riavvio.
L'audit si configura con le classi indicate in
In pratica su FreeBSD: registrare tutto (all) produce
un volume di eventi in cui non si trova più nulla.
Livello 3: firewall e DNS
Per le basi (didattica): La rete internet, Il trasporto, Applicazioni e web, La sicurezza di rete.
Per i motivi (discussione): Firewall, Difesa dalla forza bruta, Proxy, Privilegi amministrativi.
L'impianto è quello descritto in Configurazioni di esempio: ipfw filtra al livello 2 (ethernet e MAC), pf ai livelli 3 e 4 (IP, porte, NAT per le jail). Il principio è il default-deny, in ingresso e in uscita.
ipfw e pf
- Copiare config/ipfw.rules in
/etc/ipfw.rulese config/pf.conf in/etc/pf.conf, sostituendo interfaccia e indirizzi con i propri: le macro in cima al file li raccolgono tutti. - Abilitare i due firewall e i relativi log.
- Provare le regole prima di caricarle, e caricarle dalla console, mai da remoto.
doas sysrc firewall_enable=YES firewall_script=/etc/ipfw.rules firewall_logging=YES
doas sysrc pf_enable=YES pf_rules=/etc/pf.conf pflog_enable=YES pflog_logfile=/var/log/pflog
echo 'pf_load="YES"' | doas tee -a /boot/loader.conf
doas pfctl -nf /etc/pf.conf # solo controllo della sintassi: nessun output = nessun errore
doas service ipfw start
doas service pf start
doas service pflog start
doas pfctl -sr # le regole in vigore
doas pfctl -si # i contatori
Il file di pf fa quattro cose che vale la pena sapere prima di caricarlo. L'host esce in rete soltanto con
dnscrypt-proxy: tutto il resto passerà da Tor, e se Tor è fermo fallisce invece di uscire in chiaro. Verso
Internet escono soltanto le jail tor e i2p, riconosciute da un'etichetta che mette
il NAT. Né lo0 né lo1 si saltano, perché il traffico tra le jail e verso l'host passa da
lì: saltarle vorrebbe dire non
filtrare più nulla tra un servizio e l'altro. E le regole quick sono in un ordine preciso, con
i blocchi delle tabelle prima di ogni pass: ciò che si aggiunge va messo nella sezione giusta.
I compiti sono divisi così: pf è la politica fissa, caricata all'avvio; ipfw, oltre al livello 2, tiene le
tabelle che cambiano a sistema avviato (quarantena, 22 di sshguard,
esche, mai_bloccare). Le tabelle di ipfw hanno anche un altro vantaggio: ipfw
esamina ogni pacchetto, quindi un indirizzo inserito perde subito anche le connessioni già aperte, che pf
invece lascerebbe proseguire. Dopo la sigillatura ogni modifica a pf.conf vale dal riavvio
successivo.
Contro la forza bruta il primo strato è già nel file: la tabella di overload di pf, riempita dal kernel, da
cui il computer di amministrazione è escluso (bastano quattro connessioni in quindici secondi per finirci
dentro). Le sue voci non scadono da sole: le toglie cron.
# /etc/crontab: le voci di overload scadono dopo un giorno (togliere indirizzi resta permesso)
7 * * * * root /sbin/pfctl -q -t bruteforce -T expire 86400
Sopra si mette un solo demone, sshguard, che si installerà con il primo aggiornamento. blacklistd e fail2ban fanno la stessa cosa e non vanno sommati: il confronto e le configurazioni complete sono in Difesa dalla forza bruta. Il blocco di un intero paese è in Bloccare gli IP di una nazione.
Solo ora, con i due firewall in funzione, si collega il cavo di rete.
Primo aggiornamento e doas
Il sistema appena installato è già vecchio. Il primo aggiornamento avviene dietro il firewall, prima di
esporre qualsiasi servizio. Tor però non c'è ancora, e pf lascia uscire soltanto dnscrypt-proxy: per questa
volta si riempie la tabella manutenzione, prevista in pf.conf, che apre all'host DNS, HTTP e
HTTPS. Resta aperta finché la jail tor non funziona (livello 5), e poi si svuota. Funziona perché
il securelevel non è ancora salito.
# apertura temporanea, dalla console
pfctl -t manutenzione -T add 0.0.0.0/0
pfctl -t manutenzione -T show # -> 0.0.0.0/0: l'host esce in chiaro fino al livello 5
zfs snapshot -r zroot@prima-del-primo-aggiornamento
freebsd-update fetch install # solo con i set di distribuzione: con pkgbase lo fa il pkg upgrade qui sotto
pkg update && pkg upgrade
pkg audit -F # vulnerabilità note nei pacchetti installati
pkg install cpu-microcode # solo x86: poi cpu_microcode_load="YES" in /boot/loader.conf
# doas al posto di sudo
pkg install doas
echo "permit operatore as root" > /usr/local/etc/doas.conf
chown root:wheel /usr/local/etc/doas.conf && chmod 600 /usr/local/etc/doas.conf
doas -C /usr/local/etc/doas.conf # nessun output = sintassi corretta
È la regola minima. I ruoli più stretti (sola osservazione, backup, gestione dei casi dell'EDR) e il significato di ogni parola chiave sono in Configurare doas.conf.
Si esce, si accede come operatore e si verifica che doas id risponda
uid=0(root). Da qui in poi root non si usa più, ma non si blocca con
pw lock: con la console marcata insecure la modalità utente singolo chiede la sua
password, ed è proprio da lì che passano gli aggiornamenti del sistema di base dopo la
sigillatura. Bloccarlo vorrebbe dire chiudersi fuori. L'accesso diretto si chiude
altrove: SSH non accetta root, e l'utente operatore non va aggiunto al gruppo
wheel, così che nemmeno su funzioni. I suoi privilegi passano solo da
doas (vedi
Gestione degli utenti e dei gruppi).
sshguard
doas pkg install sshguard
# /usr/local/etc/sshguard.conf
BACKEND="/usr/local/libexec/sshg-fw-ipfw" # tabella 22 di ipfw: pf al livello 2 non accetta indirizzi
FILES="/var/log/auth.log"
THRESHOLD=30 # bando al terzo tentativo
BLOCK_TIME=600
DETECTION_TIME=1800
BLACKLIST_FILE=90:/var/db/sshguard/blacklist.db
WHITELIST_FILE=/usr/local/etc/sshguard.whitelist
# /usr/local/etc/sshguard.whitelist: il computer di amministrazione e la jail tor, nient'altro
192.168.178.10
10.0.0.2
doas install -d -m 700 /var/db/sshguard # per la lista dei bandi permanenti
doas sysrc sshguard_enable=YES
doas service sshguard start
doas ipfw table 22 list # vuota, finché nessuno insiste
Nell'elenco di chi non va bandito stanno soltanto il computer da cui si amministra, per non chiudersi fuori,
e la jail tor: ciò che arriva dall'onion service di amministrazione ha sempre il suo indirizzo, e
bandirlo significherebbe chiudere quell'accesso a tutti (vedi
Dietro Tor e I2P il bando per indirizzo non funziona). Il resto
della rete locale non è nell'elenco: SSH ascolta solo lì, ed è proprio da lì che arriverebbe un dispositivo di
casa compromesso.
DNS cifrato
Il nodo non deve chiedere i nomi in chiaro al router. Si installa dnscrypt-proxy con la
configurazione config/dnscrypt-proxy.toml, che richiede DNSSEC e
server senza registri, usa i relay anonimi e blocca IPv6. Ogni opzione è spiegata in
Proxy, insieme alla variante che instrada le richieste dentro Tor.
doas pkg install dnscrypt-proxy2
doas cp dnscrypt-proxy.toml /usr/local/etc/dnscrypt-proxy/dnscrypt-proxy.toml
# il file ascolta su 127.0.0.1:53 e su 10.0.0.1:53 (l'host visto dalle jail) e non tiene registri delle richieste
doas sysrc dnscrypt_proxy_enable=YES
doas sysrc dnscrypt_proxy_mac_portacl_enable=YES # permette al demone non privilegiato di usare la porta 53
doas service dnscrypt-proxy start
echo "nameserver 127.0.0.1" | doas tee /etc/resolv.conf
doas chflags schg /etc/resolv.conf # nessun programma può più riscriverlo
drill freebsd.org @127.0.0.1 # deve rispondere
Allo stesso proxy si affida il filtro dei domini di tracciamento e degli indicatori malevoli: una lista di blocco è un file di testo, senza alcun servizio in più. È l'alternativa minima a Pi-hole, discussa in Le alternative minime.
# /usr/local/etc/dnscrypt-proxy/dnscrypt-proxy.toml
[blocked_names]
blocked_names_file = '/usr/local/etc/dnscrypt-proxy/blacklist.txt'
#log_file = '/var/log/dnscrypt-proxy/blocked-names.log' # solo per una diagnosi: registra i nomi bloccati
# blacklist.txt: un nome o un modello per riga
*.doubleclick.net
ads.*
=dominio-esatto.example
# i domini malevoli dei feed MISP si aggiungono in coda
sort -u /usr/local/etc/dnscrypt-proxy/blacklist.txt /var/db/ioc/domini.txt \
| doas tee /usr/local/etc/dnscrypt-proxy/blacklist.new > /dev/null
doas mv /usr/local/etc/dnscrypt-proxy/blacklist.new /usr/local/etc/dnscrypt-proxy/blacklist.txt
doas service dnscrypt-proxy restart
drill doubleclick.net @127.0.0.1 # -> rifiutato
Livello 4: jail
Per le basi (didattica): Processi e thread, Memoria virtuale.
Per i motivi (discussione): Namespace, container e jail, IPC (comunicazione tra i processi).
Ogni servizio vive in una jail, per le ragioni spiegate in Namespace, container e jail. La jail è anche l'unità di contenimento dell'EDR: se un servizio cade, si ferma quella jail e il resto continua.
| Jail | Indirizzo | Che cosa contiene | Con chi può parlare |
|---|---|---|---|
tor | 10.0.0.2 | Tor e nient'altro | Internet (solo bridge o guard), jail web |
i2p | 10.0.0.3 | i2pd con il bridge SAM | Internet |
web | 10.0.0.10 | nginx | Nessuno: risponde soltanto alla jail tor |
iop | 10.0.0.11 | Il demone di iop | Solo la jail i2p, porta SAM |
esca | 10.0.0.66 | Honeypot | Nessuno |
Installare CBSD
doas pkg install cbsd
doas zfs create -o mountpoint=/jails zroot/jails
doas env workdir="/jails" cbsd initenv
initenv pone una serie di domande. Le risposte per un nodo Tutanum (provate con CBSD 15.1 su
FreeBSD 15.1; le stesse si possono scrivere in un file e passare a
/usr/local/cbsd/sudoexec/initenv, partendo da /usr/local/cbsd/share/initenv.conf):
| Domanda | Risposta | Perché |
|---|---|---|
| Preparare l'ambiente (prepare or upgrade hier environment) | 1 | |
| Aggiungere l'utente cbsd a sudoers | 0 | Ogni modifica alle jail deve passare da doas |
Scrivere cbsd_workdir in rc.conf | 1 | |
nodename | nodo | Lo stesso nome generico dell'host |
nodeip | L'indirizzo del nodo nella rete locale | |
jnameserver | L'indirizzo dell'host visto dalle jail | Le jail risolvono attraverso dnscrypt-proxy, non attraverso il router |
| Rete delle jail | 10.0.0.0/24 | Gli indirizzi stanno su lo1 |
nat_enable | 0 | Il NAT è già in pf.conf, scritto a mano: escono soltanto tor e i2p. Quello di CBSD farebbe uscire tutte le jail |
ipfw_enable | 0 | Le regole di ipfw sono quelle di ipfw.rules: CBSD non le deve toccare |
Impostare pf_load=YES | 1 | |
fbsdrepo | 1 | Base e kernel dai repository ufficiali di FreeBSD, non da quelli di CBSD |
zfsfeat | 1 | Un dataset per jail: istantanee e ripristino |
parallel | 5 | |
stable | 0 | Solo RELEASE |
sqlreplica, i tre statsd_*, RSYNC | 0 | Funzioni non necessarie: meno codice in esecuzione |
| RACCT | 1 | Serve per limitare le risorse delle jail con rctl |
Scrivere cbsdd_enable=YES | 1 | |
rcshutdown_timeout, preseedinit | 0 |
Il valore predefinito di jnameserver sono i DNS di Google: va cambiato, come in tabella. Poi si
scarica la base delle jail, che arriva senza le correzioni di sicurezza (provato: 15.1-RELEASE contro la
15.1-RELEASE-p5 dell'host) e va subito aggiornata. baseupdate usa freebsd-update e
non passa da Tor: si esegue ora, con la tabella manutenzione aperta, e in seguito insieme agli
aggiornamenti del sistema di base.
Con nat_enable a 0 CBSD esegue natoff, che per quel valore svuota il NAT
di pf (pfctl -F nat): succede alla fine di initenv e a ogni arresto o riavvio di
cbsdd. Le regole di filtro restano, le due righe nat di pf.conf no, e
Tor e I2P restano senza uscita (provato: pfctl -sn vuoto dopo initenv; all'avvio del
sistema invece il NAT resta). Dopo initenv, e dopo ogni service cbsdd restart, si
ricarica il file; dopo la sigillatura pf è congelato e il problema non si pone.
doas pfctl -f /etc/pf.conf && doas pfctl -sn # le due regole nat e i rdr
doas cbsd repo action=get sources=base ver=15.1
doas cbsd baseupdate ver=15.1
doas jexec <jail> freebsd-version -u # -> 15.1-RELEASE-pN, come l'host
Creare e preparare una jail
doas cbsd jconstruct-tui # nome, indirizzo e host_hostname secondo la tabella sopra
# interface: lo1, sempre. Con "auto" CBSD mette l'indirizzo su em0
# (provato): le regole di pf per le jail non varrebbero più
doas cbsd jstart tor
doas cbsd jls # stato delle jail
doas cbsd jlogin tor # entrare nella jail
Ogni jail riceve un securelevel proprio, più alto di quello dell'host, e non vede i dispositivi con cui si intercetta il traffico o si legge la memoria.
# CBSD non ha un parametro per il securelevel: lo alza la jail stessa, dal proprio rc.conf
doas jexec web sysrc kern_securelevel_enable=YES kern_securelevel=3
doas cbsd jstop web && doas cbsd jstart web
doas jexec web sysctl kern.securelevel # -> 3
doas cbsd jget jname=web devfs_ruleset # -> 4, il predefinito di CBSD
doas jexec web ls /dev # -> nessun bpf, mem, kmem, io
# due valori predefiniti di CBSD da cambiare (provato con CBSD 15.1): il debug dei processi tra utenti
# non privilegiati è permesso dentro la jail, e l'albero dei port dell'host vi è montato. I mount sono
# già negati (enforce_statfs=2), ma si scrive tutto esplicitamente
doas cbsd jstop web
doas cbsd jset jname=web allow_unprivileged_proc_debug=0 mount_ports=0 enforce_statfs=2 \
allow_mount=0 allow_nullfs=0 allow_tmpfs=0 allow_fdescfs=0 allow_procfs=0 allow_devfs=0
doas cbsd jstart web
doas jexec web sysctl -n security.bsd.unprivileged_proc_debug # -> 0
mount | grep -c "jails/web/usr/ports" # -> 0
jls -j web enforce_statfs allow.mount children.max # -> 2 false 0
Una jail compromessa non deve poter affamare le altre. I limiti di risorse richiedono
kern.racct.enable=1 nel loader e un riavvio; si scrivono in /etc/rctl.conf, che li
applica a ogni avvio. Provato: con maxproc a 40 una jail che tenta di avviare cento processi si
ferma a 39, e un ciclo infinito resta sotto il tetto di CPU.
# /boot/loader.conf
kern.racct.enable=1
# /etc/rctl.conf: per ogni jail, valori da adattare al carico
jail:web:maxproc:deny=100
jail:web:memoryuse:deny=512m
jail:web:pcpu:deny=50
jail:tor:memoryuse:deny=1g
jail:i2p:memoryuse:deny=1g
jail:iop:maxproc:deny=50
jail:esca:memoryuse:deny=512m
jail:esca:maxproc:deny=100
jail:esca:pcpu:deny=20
doas sysrc rctl_enable=YES # senza, /etc/rctl.conf non si carica all'avvio
doas rctl -u jail:web # uso corrente rispetto ai limiti
Le jail non escono su Internet, quindi i pacchetti non si installano da dentro: li scarica l'host e li
installa nella radice della jail avviata con pkg -r. Con -r pkg leggerebbe la
configurazione dentro la jail: con -C gli si passa quella dell'host, che contiene il proxy. Finché
Tor non funziona passano dalla tabella manutenzione, poi da Tor. pkg però non aggiorna la cache
delle librerie della jail: dopo ogni installazione si esegue service ldconfig start nella jail,
altrimenti i programmi non trovano le librerie appena installate (provato con nginx).
Dentro ogni jail si ripete, in piccolo, ciò che si è fatto sull'host: un utente non privilegiato per il servizio e nessun accesso diretto come root.
# dall'host, senza entrare nella jail
doas pkg -C /usr/local/etc/pkg.conf -r /jails/jails/tor install doas
doas jexec tor service ldconfig start
doas jexec tor pw useradd gestore -m -s /bin/sh
echo "permit gestore as root" | doas tee /jails/jails-data/tor-data/usr/local/etc/doas.conf
Ogni jail ha anche il proprio messaggio di accesso, che dice all'operatore dove si trova e nient'altro: con più jail aperte, il comando giusto nella jail sbagliata è l'errore più facile.
printf 'jail: tor. Solo Tor, nessun altro programma.\nOgni comando privilegiato passa da doas e viene registrato.\n' \
| doas tee /jails/jails-data/tor-data/etc/motd.nodo
doas jexec tor sysrc update_motd=NO
# nella jail: stessa modifica a /etc/login.conf (umask 027 per default, 022 per root), poi
doas jexec tor cap_mkdb /etc/login.conf
doas jexec tor pw usermod root -L root
# i file scritti dall'host nella jail prendono la umask di chi li scrive: i permessi si danno
# sempre espliciti (install -m, chmod), come per le pagine del sito nella jail web
Con CBSD la radice di una jail è in /jails/jails/nome e i suoi dati in
/jails/jails-data/nome-data. Gli script della sezione
Antivirus, EDR e XDR presumono le jail direttamente sotto
/jails: i percorsi e i nomi dei dataset vanno adattati.
Le jail non si parlano tra loro, salvo le eccezioni della tabella. Le regole sono già in config/pf.conf; queste sono le righe che le realizzano.
# traduzione: soltanto tor e i2p hanno il NAT, e il NAT le marca
nat on $ext_if inet from $j_tor to any tag TOR -> ($ext_if)
nat on $ext_if inet from $j_i2p to any tag I2P -> ($ext_if)
# filtro: tra le jail passa solo ciò che è elencato. Gli indirizzi stanno su lo1, e il kernel
# instrada il traffico verso di loro attraverso lo1 stessa (jail_if)
pass quick on $jail_if proto tcp from $j_tor to $j_web port 8080 keep state # tor -> web
pass quick on $jail_if proto tcp from $j_iop to $j_i2p port 7656 keep state # iop -> SAM di i2pd
# verso Internet: dopo il NAT l'origine è em0, quindi si guarda l'etichetta
pass out quick on $ext_if proto tcp tagged TOR modulate state
pass out quick on $ext_if proto { tcp, udp } tagged I2P keep state
# web, iop ed esca non hanno né NAT né etichetta: cadono nel "block log all"
Livello 5: reti anonime
Per le basi (didattica): La sicurezza di rete, La rete internet.
Per i motivi (discussione): Tor, Architettura di Tor, I2P, Architettura della rete, I2P rispetto a Tor.
Tor
Nella jail tor gira Tor e nient'altro (vedi
Buone pratiche da Whonix). La configurazione che segue è quella
commentata riga per riga in Configurare torrc.
doas pkg -C /usr/local/etc/pkg.conf -r /jails/jails/tor install tor obfs4proxy-tor
doas jexec tor service ldconfig start
doas jexec tor sysrc tor_enable=YES
doas jexec tor install -d -o _tor -g _tor -m 700 /var/db/tor /var/run/tor # /var/run/tor: senza, il
# ControlSocket non si crea e Tor non parte (provato)
# /usr/local/etc/tor/torrc (nella jail tor)
RunAsDaemon 0
AvoidDiskWrites 1
ClientOnly 1
ExitPolicy reject *:*
ExitPolicy reject6 *:*
SOCKSPort 10.0.0.2:59353 IsolateSOCKSAuth IsolateClientAddr IsolateClientProtocol IsolateDestAddr IsolateDestPort
# Chi può usare il SOCKS, oltre a pf: l'host (che arriva con l'indirizzo della jail, 10.0.0.2)
# e gli ospiti della rete interna di Tutanum Lieve; tutti gli altri vengono rifiutati da Tor stesso
SocksPolicy accept 10.0.0.2, accept 10.0.1.0/24, reject *
ClientUseIPv4 1
ClientUseIPv6 0
ClientRejectInternalAddresses 1
AllowNonRFC953Hostnames 0
WarnPlaintextPorts 23,109,110,143,80
TestSocks 1
AutomapHostsOnResolve 1
AutomapHostsSuffixes .exit, .onion
VirtualAddrNetwork 10.192.0.0/10
ConnectionPadding 1
ReducedConnectionPadding 0
UseEntryGuards 1
NumEntryGuards 1
EnforceDistinctSubnets 1
NewCircuitPeriod 30
MaxCircuitDirtiness 600
CircuitsAvailableTimeout 86400
DormantCanceledByStartup 1
HardwareAccel 1
Schedulers Vanilla
# Porta di controllo: solo un socket locale, con autenticazione a cookie (per vanguards)
ControlSocket /var/run/tor/control
CookieAuthentication 1
# Bridge: attivarli se l'uso di Tor va nascosto al fornitore di rete. Le righe "Bridge" sono le proprie.
UseBridges 0
ClientTransportPlugin obfs4 exec /usr/local/bin/obfs4proxy managed
# Bridge obfs4 <indirizzo:porta> <impronta> cert=<certificato> iat-mode=0
Nel file non ci sono ExcludeNodes né StrictNodes. Escludere i paesi che
ospitano la maggior parte dei relay (Stati Uniti, Germania, Paesi Bassi, Francia) toglie al client gran
parte della rete: i guard possibili diventano pochi, il client si distingue da tutti gli altri, e un onion
service rallenta. Il Tor Project lo sconsiglia, e la protezione che promette è minore di quella che toglie.
Chi vuole comunque escludere qualcosa si limita al proprio paese per il guard, sapendo che anche questa è una
scelta che rende riconoscibili (vedi Configurare
torrc).
I bridge si ottengono e si scelgono come spiegato in
Il problema dei bridge: non si copiano da una guida, perché
un bridge pubblicato è un bridge noto. Il file state, che contiene i guard, entra nei backup e non
si cancella mai. Dalla versione 0.4.8 Tor include già vanguards-lite; per un onion service importante si
aggiunge l'addon vanguards completo, che si collega al socket di controllo
(vanguards --control_socket /var/run/tor/control), descritto negli
attacchi alla rete Tor.
doas jexec tor service tor start
# dal nodo: la jail esce davvero da Tor?
curl --socks5-hostname 10.0.0.2:59353 https://check.torproject.org/api/ip # -> "IsTor":true
# da ora i pacchetti dell'host e delle jail (pkg -C ... -r) passano da Tor
# /usr/local/etc/pkg.conf
PKG_ENV { SOCKS5_PROXY: "10.0.0.2:59353" }
# e la tabella del primo aggiornamento si svuota: l'host non esce più in chiaro
doas pfctl -t manutenzione -T flush
doas pfctl -t manutenzione -T show # nessun indirizzo
Il sistema di base si aggiorna in due modi, secondo come è stato installato. Con i set di distribuzione si
usa freebsd-update; con il sistema di base a pacchetti (pkgbase, quello delle immagini ufficiali
di FreeBSD 15.1) freebsd-update si rifiuta di partire e il base si aggiorna con pkg
dal repository FreeBSD-base. In entrambi i casi, a nodo sigillato, l'aggiornamento del base
avviene in modalità utente singolo: i suoi file portano il flag schg, che al securelevel 2 nessuno
può togliere. Lì Tor non gira, quindi per la durata dell'aggiornamento si riapre la tabella
manutenzione, e il fornitore di rete vedrà un collegamento ai server di FreeBSD. La procedura è
stata provata su FreeBSD 15.1.
# in modalità utente singolo (chiede la password di root); la radice è in sola lettura
mount -u -o rw / && mount -a
service netif start && service routing start
service pf start && service dnscrypt-proxy start
pfctl -t manutenzione -T add 0.0.0.0/0
freebsd-update fetch install # set di distribuzione
cbsd baseupdate ver=15.1 # la base condivisa dalle jail
pkg upgrade -r FreeBSD-base # pkgbase
pfctl -t manutenzione -T flush
# i file sostituiti tornano con i flag del pacchetto, non con quelli messi a mano:
# si rifà la sigillatura (i chflags di "Sigillare i file di avvio"), poi
exit # si prosegue con l'avvio normale
Fuori dalla modalità utente singolo si aggiornano soltanto i port, che passano da Tor: con pkgbase un
pkg upgrade senza -r proverebbe ad aggiornare anche il base e si fermerebbe sui file
sigillati.
Orologio
Il nodo non usa NTP in chiaro (vedi Il tempo come vettore
d'attacco), ma Tor e I2P hanno bisogno dell'ora giusta: Tor smette di funzionare con un'ora indietro o tre
avanti, e la rotazione delle chiavi di I2P si basa sulla data. All'installazione l'ora si imposta a mano; da quel
momento la si controlla attraverso Tor, come fa sdwdate di Whonix: si legge l'intestazione Date di
tre onion service indipendenti e si prende la mediana, mai una fonte sola.
#!/bin/sh
# /usr/local/sbin/ora-da-tor: mediana dell'ora di tre onion service, attraverso la jail tor
ora=$(for o in primo.onion secondo.onion terzo.onion; do
d=$(curl -sI --max-time 60 --socks5-hostname 10.0.0.2:59353 "http://$o/" \
| sed -n 's/^[Dd]ate: //p' | tr -d '\r')
[ -n "$d" ] && LC_ALL=C TZ=UTC date -j -f "%a, %d %b %Y %T GMT" "$d" +%s # nomi inglesi
done | sort -n | sed -n 2p)
[ -n "$ora" ] || { echo "meno di due fonti: nessuna correzione" >&2; exit 1; }
scarto=$(( ora - $(date +%s) ))
echo "scarto: ${scarto} s"
# al securelevel 2 il kernel sposta l'orologio al massimo di un secondo, non più di una volta al
# secondo, e non lo riporta indietro: un orologio indietro recupera un secondo per ogni secondo
if [ "$scarto" -gt 1 ]; then
while [ "$scarto" -gt 1 ]; do
date -f %s "$(( $(date +%s) + 1 ))" > /dev/null
sleep 1
scarto=$(( scarto - 1 ))
done
elif [ "$scarto" -lt -30 ]; then
echo "orologio avanti di $(( -scarto )) s: va corretto a mano in modalità utente singolo" >&2
fi
# una volta al giorno, da root
echo '17 4 * * * root /usr/local/sbin/ora-da-tor | logger -t ora-da-tor' | doas tee -a /etc/crontab
I tre indirizzi si scelgono tra servizi gestiti da gruppi diversi, che non hanno motivo di accordarsi. Un orologio avanti si riporta indietro soltanto a mano, in modalità utente singolo, dove il securelevel non è ancora salito; con il modulo RTC esterno del livello 1 capita di rado.
I2P
I2P è la rete di riferimento per i servizi tra nodi (vedi I2P e Configurare un nodo I2P).
doas pkg -C /usr/local/etc/pkg.conf -r /jails/jails/i2p install i2pd
doas jexec i2p service ldconfig start
doas jexec i2p sysrc i2pd_enable=YES
# /usr/local/etc/i2pd/i2pd.conf (nella jail i2p)
pidfile = /var/run/i2pd/i2pd.pid # dove lo cerca lo script rc: senza, "service i2pd stop" non ferma nulla
ipv4 = true
ipv6 = false
port = 24567 # porta fissa: la stessa di i2p_port in pf.conf (sceglierne una propria)
[http]
enabled = true
address = 10.0.0.3 # console web: pf la apre soltanto all'host
port = 7070
auth = true # e chiede comunque una password
user = operatore
pass = una-passphrase-propria
[httpproxy]
enabled = false # nessun proxy verso I2P: alla jail serve soltanto SAM
[socksproxy]
enabled = false
[sam]
enabled = true
address = 10.0.0.3 # bridge SAM per iop
port = 7656
# i2pd.conf contiene la password della console, e i2pd crea le sue chiavi leggibili da tutti
# (provato: router.keys, ntcp2.keys e ssu2.keys in 644): file e cartella si chiudono a mano
doas jexec i2p chown root:_i2pd /usr/local/etc/i2pd/i2pd.conf
doas jexec i2p chmod 640 /usr/local/etc/i2pd/i2pd.conf
doas jexec i2p chmod 700 /var/db/i2pd
Un router I2P pubblica il proprio indirizzo nella netDB: chiunque può sapere che quell'indirizzo partecipa a I2P, non che cosa vi si ospita. È il prezzo della partecipazione alla rete, e vale la pena saperlo prima di scegliere dove collocare il nodo.
iop
iop condivide tra i nodi, attraverso I2P, i file tracciati con Git, senza alcun server di repository. Su un nodo Classico serve a tenere allineati i server ridondati che stanno su reti diverse e i dispositivi che escono di casa; per quelli della propria rete resta il server di repository (vedi Forge).
La jail iop parla soltanto con il bridge SAM e non può scaricare nulla. iop si compila quindi
sull'host, come utente non privilegiato e attraverso Tor, da un commit preciso; nella jail entra solo
l'eseguibile, e sull'host il compilatore non resta. Il repository oggi non pubblica rilasci firmati: si fissa
il commit, se ne annota l'hash e lo si confronta con quello comunicato dall'autore per un canale diverso. Si
compila soltanto la riga di comando (iop-cli): l'interfaccia grafica non serve a un nodo.
Provato su FreeBSD 15.1 con pf attivo: tutto passa da Tor, e la compilazione richiede pochi minuti.
# sull'host, come operatore: sorgenti e dipendenze attraverso Tor
doas pkg install rust git
git -c http.proxy=socks5h://10.0.0.2:59353 clone https://gitlab.com/Rekcah/iop && cd iop
git checkout <commit> # un commit preciso, mai il ramo che si muove
git rev-parse HEAD # l'hash da confrontare; con i rilasci firmati: git verify-tag
CARGO_HTTP_PROXY=socks5h://10.0.0.2:59353 cargo build -p iop-cli --release --locked
doas mkdir -p /jails/jails-data/iop-data/usr/local/bin
doas install -m 755 target/release/iop /jails/jails-data/iop-data/usr/local/bin/iop
doas pkg delete -y rust git && doas pkg autoremove -y
# nella jail, come utente non privilegiato
doas jexec iop pw useradd gestore -m -s /bin/sh
# -l: ambiente di login di gestore (HOME=/home/gestore), dove iop tiene identità e configurazione
doas jexec -l -U gestore iop iop init # rispondere "s": identità cifrata con passphrase
doas jexec -l -U gestore iop iop profile use paranoid # dopo init: un init ripetuto riscrive config.toml
doas jexec -l -U gestore iop iop config set network.i2p_sam_addr 10.0.0.3:7656
doas jexec -l -U gestore iop iop doctor # tutti i controlli critici devono passare
# il demone: iop legge la passphrase dal terminale, se ne ha uno. Questo comando nella jail la chiede
# una volta e lo stacca dal terminale con daemon(8), che gliela passa dalla pipe (provato)
# /usr/local/bin/avvia-iop (nella jail iop)
#!/bin/sh
pgrep -U "$(id -u)" -f "iop daemon" > /dev/null && { echo "iop daemon è già in esecuzione"; exit 0; }
stty -echo; printf 'Passphrase di iop: '; read -r P; stty echo; echo
printf '%s\n' "$P" | /usr/sbin/daemon -o "$HOME/.iop/daemon.log" /usr/local/bin/iop daemon
unset P; sleep 5; tail -2 "$HOME/.iop/daemon.log"
doas jexec -l -U gestore iop avvia-iop # dalla console, dopo ogni riavvio
Due avvertenze emerse provando. doctor segnala "chiavi cifrate" in base all'impostazione
encrypt_key_files, non al file: se a init si risponde di no, la chiave resta in
chiaro e il controllo risulta comunque superato; allo stesso modo "permessi file chiave == 0600" risulta
superato anche quando la chiave non esiste ancora. E il controllo dell'orologio cerca strumenti NTP che sul nodo
non ci sono per scelta, quindi risulta "non verificabile": l'ora si controlla con lo script di
Orologio. Con l'identità cifrata il demone chiede la passphrase a ogni avvio, quindi
dopo un riavvio va lanciato a mano dalla console, ed è il prezzo di una chiave che non sta in chiaro sul
disco.
La sintassi dei comandi profile e config va confrontata con la
guida alla CLI della versione installata.
L'uso di iop per distribuire indicatori e configurazioni tra i nodi è descritto in
Integrare iop.
Livello 6: servizi
Per le basi (didattica): Applicazioni e web, Il trasporto, La sicurezza di rete.
Per i motivi (discussione): Servizi esposti, Web server, SSH, Trasferimento file: SFTP, mai FTP, Forge: il server di repository.
Un sito come onion service
Il server web sta nella jail web e non ascolta su Internet: lo raggiunge soltanto Tor, dalla
propria jail. Si usa nginx senza moduli superflui (vedi Web server).
doas pkg -C /usr/local/etc/pkg.conf -r /jails/jails/web install nginx
doas jexec web service ldconfig start
doas jexec web sysrc nginx_enable=YES
doas jexec web mkdir -p /usr/local/www/sito1
# /usr/local/etc/nginx/nginx.conf (nella jail web)
worker_processes 1;
error_log /var/log/nginx/error.log;
events { worker_connections 1024; }
http {
server_tokens off; # nessuna versione nelle intestazioni
include mime.types;
default_type application/octet-stream;
access_log off; # nessun registro dei visitatori
client_body_buffer_size 1k;
client_header_buffer_size 1k;
large_client_header_buffers 2 1k;
client_max_body_size 1k; # sito statico: nessun caricamento
sendfile on;
keepalive_timeout 65;
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header Referrer-Policy no-referrer;
server {
listen 10.0.0.10:8080;
server_name _;
root /usr/local/www/sito1;
index index.html;
location /riservata/ { # una cartella che non deve esistere per nessuno
deny all;
return 404;
}
location /protetta/ { # una cartella sotto password
auth_basic "Area protetta";
auth_basic_user_file /usr/local/etc/nginx/.htpasswd;
}
}
}
# password per l'area protetta (nella jail web)
printf 'utente:%s\n' "$(openssl passwd -apr1)" | doas tee /usr/local/etc/nginx/.htpasswd
doas chown root:www /usr/local/etc/nginx/.htpasswd && doas chmod 640 /usr/local/etc/nginx/.htpasswd
doas service nginx start
Nella jail tor si dichiara il servizio. Ogni sito ha il proprio indirizzo onion, ma sulla stessa
istanza di Tor i siti condividono il guard e la disponibilità: chi li osserva a lungo vede che si accendono e
si spengono insieme, e può ricondurli allo stesso nodo. Siti che non devono essere collegati tra loro stanno
su istanze di Tor separate, ciascuna nella propria jail, o meglio su nodi diversi.
# /usr/local/etc/tor/torrc (nella jail tor)
HiddenServiceDir /var/db/tor/sito1
HiddenServiceVersion 3
HiddenServicePort 80 10.0.0.10:8080
HiddenServicePoWDefensesEnabled 1
# un secondo sito: un'altra cartella, un altro indirizzo, un'altra porta
# HiddenServiceDir /var/db/tor/sito2
# HiddenServicePort 80 10.0.0.10:8081
doas jexec tor service tor restart
doas jexec tor cat /var/db/tor/sito1/hostname # l'indirizzo .onion del sito
La cartella /var/db/tor/sito1 contiene la chiave hs_ed25519_secret_key: è l'identità
del sito e non ha frase di recupero. Va copiata, cifrata, fuori dal nodo. I contenuti del sito non si
scaricano da un servizio in chiaro: arrivano con iop o attraverso Tor. Una piattaforma pubblica come Codeberg
può fare al massimo da specchio, raggiunto attraverso Tor con una chiave dedicata, per le ragioni spiegate in
Codeberg e le piattaforme ospitate da altri.
SSH
L'amministrazione remota avviene solo dalla rete locale o attraverso un onion service dedicato, diverso da quello dei siti. La configurazione è quella di SSH: sola autenticazione a chiave, niente root, elenco esplicito degli utenti.
# /etc/ssh/sshd_config (sull'host), IN CIMA al file: sshd usa la prima occorrenza di ogni opzione,
# e una riga già presente più in basso (o aggiunta da altri strumenti) vincerebbe sulla nostra
ListenAddress 192.168.178.3
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
AllowUsers operatore
MaxAuthTries 3
LoginGraceTime 20
X11Forwarding no
AllowAgentForwarding no
AllowTcpForwarding no # niente tunnel: dall'host si raggiungerebbero i servizi delle jail
AllowStreamLocalForwarding no
PermitTunnel no
ClientAliveInterval 300 # sessioni abbandonate chiuse dopo dieci minuti
ClientAliveCountMax 2
# le impostazioni effettive, non quelle scritte: questo è ciò che sshd applica davvero
doas sshd -T | grep -E "^(passwordauthentication|kbdinteractiveauthentication|permitrootlogin|allowtcpforwarding|allowusers) "
# atteso: no, no, no, no, operatore
Provando la guida in una macchina virtuale, un'immagine cloud aveva già una riga
PasswordAuthentication yes più in alto: la nostra veniva ignorata e sshd accettava le password.
Su un'installazione da DVD quella riga non c'è, ma la verifica con sshd -T costa un secondo. Gli
inoltri sono spenti perché le connessioni che sshd aprirebbe per conto dell'utente partono dall'host, a cui pf
lascia raggiungere i servizi delle jail.
L'onion service di amministrazione è riservato: senza la chiave di autorizzazione del client, Tor non
rivela nemmeno che il servizio esiste. pf lascia passare soltanto la jail tor verso la porta 22
dell'host.
# /usr/local/etc/tor/torrc (nella jail tor): un indirizzo diverso da quello dei siti
HiddenServiceDir /var/db/tor/amministrazione
HiddenServiceVersion 3
HiddenServicePort 22 192.168.178.3:22
# sul computer di amministrazione: una coppia di chiavi x25519 per l'autorizzazione
openssl genpkey -algorithm x25519 -out onion-auth.pem
priv=$(openssl pkey -in onion-auth.pem -outform DER | tail -c 32 | base32 | tr -d '=')
pub=$(openssl pkey -in onion-auth.pem -pubout -outform DER | tail -c 32 | base32 | tr -d '=')
# nella jail tor: la sola chiave pubblica, in una cartella che deve già esistere, poi si riavvia Tor
doas jexec tor install -d -o _tor -g _tor -m 700 /var/db/tor/amministrazione/authorized_clients
echo "descriptor:x25519:<valore di $pub>" | doas tee /jails/jails-data/tor-data/var/db/tor/amministrazione/authorized_clients/operatore.auth
# sul computer di amministrazione: la chiave privata, nella cartella indicata da ClientOnionAuthDir
echo "<indirizzo-senza-.onion>:descriptor:x25519:$priv" > onion_auth/amministrazione.auth_private
ssh -o ProxyCommand='nc -X 5 -x 127.0.0.1:9050 %h %p' operatore@<indirizzo>.onion
Altri servizi
Per il trasferimento di file si usa SFTP, mai FTP (vedi Trasferimento file). Per
gli avvisi un server XMPP auto-ospitato (vedi Alerting). Ogni servizio
nuovo segue lo stesso schema: una jail, un utente non privilegiato, un indirizzo onion o una destinazione I2P
propri, una riga in pf.conf per ciò che gli serve e niente di più.
Livello 7: monitoraggio e manutenzione
Per le basi (didattica): File system, Processi e thread.
Per i motivi (discussione): Monitoraggio e difesa, Backup, Snapshot, Verifica e integrità del software, Anti-forensics ed emergenza.
Monitoraggio e difesa
Un nodo non osservato è un nodo di cui non si sa nulla. Gli strumenti e gli script sono descritti in Monitoraggio e difesa; l'ordine in cui attivarli è questo.
- Integrità: una base di riferimento con
mtree, firmata e conservata fuori dal nodo (vedi Verifica e integrità del software). - Audit: le classi di eventi indicate nella sezione sull'EDR.
- Contenimento: la tabella
quarantenadi ipfw, il dataset della quarantena e lo scripttutanum-edr(vedi In pratica su FreeBSD). - Sentinella: prima in modalità di solo avviso, poi di blocco.
- Esca: la jail
escae gli honeytoken (vedi Honeypot). - Avvisi: un canale fuori banda e un segnale di vita periodico.
- Rapporti di sicurezza: FreeBSD ne produce già uno ogni notte (file setuid nuovi, account con
UID 0 o senza password, accessi falliti, pacchetti scartati dal firewall, vulnerabilità note). Si scrive
su file, da cui lo leggono il collettore e il canale di avviso. Il controllo delle vulnerabilità scarica
l'elenco con pkg, quindi passa da Tor grazie a
pkg.conf: senza, resterebbe bloccato da pf (provato).
# /etc/periodic.conf
daily_status_security_output="/var/log/security.log"
# 1. sul nodo: la base di riferimento, da rigenerare dopo ogni aggiornamento e copiare subito fuori
doas mtree -c -K sha256digest -p /usr/local > base.mtree
# 2. sul computer fidato: la si firma e la si conserva; sul nodo non resta
ssh-keygen -Y sign -f ~/.ssh/id_ed25519_mtree -n mtree base.mtree # -> base.mtree.sig
# 3. a ogni controllo: la firma si verifica sul computer fidato, poi la base torna sul nodo
ssh-keygen -Y verify -f firmatari -I operatore -n mtree -s base.mtree.sig < base.mtree
doas mtree -p /usr/local < base.mtree # nessun output = nessuna differenza
Una base conservata sul nodo stesso non prova nulla: chi può modificare i file può modificare anche lei.
Backup
Vale la regola 3-2-1 nella sua versione paranoica, con cifratura prima di ogni spostamento (vedi
Backup). Oltre ai dati, tre cose non devono mancare: le chiavi degli onion
service, il file state di Tor e le chiavi delle destinazioni I2P.
doas zfs snapshot -r zroot@$(date +%F) # istantanea mensile di tutto
doas zfs list -t snapshot
# copia cifrata su un supporto esterno, poi scollegato
doas zfs send -R zroot/jails@$(date +%F) | age -r <chiave-pubblica> > /mnt/esterno/jails-$(date +%F).zfs.age
Un backup non provato non esiste: almeno una volta per trimestre si ripristina una jail su un'altra macchina. Le istantanee conservano anche ciò che si cancella dopo, registri e file rimossi compresi: un dato che deve poter sparire va tolto anche dalle istantanee e dai backup che lo contengono (vedi Anti-forensics ed emergenza).
Aggiornamenti
doas pkg audit -F # ogni settimana
doas zfs snapshot -r zroot@prima-di-$(date +%F) # sempre, prima di aggiornare
doas mtree -p /usr/local < base.mtree # l'integrità si controlla PRIMA
# il sistema di base: in modalità utente singolo, vedi "Tor" al livello 5
doas pkg upgrade -r FreeBSD-ports # i port, attraverso Tor per via di pkg.conf
for j in tor i2p web iop esca; do
doas pkg -C /usr/local/etc/pkg.conf -r /jails/jails/$j upgrade -r FreeBSD-ports && doas jexec $j service ldconfig start
done
# e la base di riferimento si rigenera subito DOPO, nella stessa operazione
L'officina e lo scambio con il nodo
Il software che entra nel nodo — aggiornamenti dei port, le proprie applicazioni — non si compila sul nodo: lo fa un'officina separata, una seconda macchina che analizza e costruisce (il perché è in L'officina). Qui interessa come le due macchine si parlano, perché è la parte che decide la fiducia. Tre principi: è sempre il nodo a iniziare (l'officina risponde e basta, non ha né rotta né credenziali per raggiungere il nodo); l'officina esce solo dal SOCKS di Tor del nodo; la chiave di firma non lascia mai il nodo. Così un'officina compromessa non tocca il nodo né i segreti.
Il canale è SSH, ma non una shell: ogni chiave è legata a un solo comando imposto
(command= in authorized_keys), quindi il nodo può fare soltanto quella
cosa. Due chiavi, due usi: una parla con un dispatcher che accetta un elenco chiuso di richieste
(consegna l'inventario, ritira le issue, conferma), l'altra ritira il repository dei pacchetti in
sola lettura con rrsync.
# sul nodo: due chiavi dedicate e la chiave host dell'officina fissata
ssh-keygen -t ed25519 -N "" -f /var/db/officina/chiave-scambio
ssh-keygen -t ed25519 -N "" -f /var/db/officina/chiave-repo
ssh-keyscan -t ed25519 <officina> > /var/db/officina/known_hosts
# sull'officina, in ~scambio/.ssh/authorized_keys: una chiave = un comando
restrict,command="/usr/local/libexec/officina/scambio" ssh-ed25519 AAAA...nodo-scambio
restrict,command="rrsync -ro /var/db/officina/repository" ssh-ed25519 AAAA...nodo-repo
Il dispatcher legge la richiesta da SSH_ORIGINAL_COMMAND e la confronta con l'elenco chiuso
(inventario, issue-elenco, issue-conferma): tutto ciò che
arriva dall'officina è dato non fidato, quindi si valida con uno schema, si tronca a una
dimensione massima e non si esegue mai. Da cron, una volta all'ora, il nodo esegue il giro completo:
consegna l'inventario dei pacchetti, ritira le issue dell'analisi (le aggiunge all'agenda e, le più
urgenti, al notificatore del telefono), e ritira il repository compilato.
# il nodo ritira i pacchetti, li RIFIRMA con la propria chiave (mai sull'officina) e li pubblica
rsync -e "ssh -i /var/db/officina/chiave-repo -o UserKnownHostsFile=/var/db/officina/known_hosts" \
scambio@<officina>:/ /var/db/repository/ricevuto/
pkg repo /var/db/repository/ricevuto/ /var/db/chiavi/repo.key # ri-firma qui, non sull'officina
# la copia firmata finisce nella copia di lavoro del progetto iop, che la diffonde ai client via I2P
Per le applicazioni costruite così (per esempio un demone proprio), lo stesso giro porta l'eseguibile già compilato e analizzato: il nodo lo ritira e lo installa nella jail giusta. Se l'applicazione ha un'identità cifrata — una chiave sbloccata da una passphrase all'avvio — lo scambio dell'eseguibile e il riavvio restano un gesto manuale alla console: è una proprietà di sicurezza, non un limite. E conviene schedulare un controllo che dica non solo se il processo è vivo, ma se funziona davvero (la sua stessa diagnostica, se ce l'ha), facendo confluire un fallimento critico negli avvisi.
Sigillare i file di avvio
È l'ultimo passo, da fare quando tutto il resto funziona: qui si alza anche il securelevel, che dal riavvio
congela pf. Il securelevel impedisce di togliere il flag
schg, ma i file vanno marcati: tutto ciò che viene eseguito prima che il livello salga, altrimenti
basta modificare uno script di avvio e riavviare (vedi
File immutabili).
Il livello sale per ultimo: rcorder mette securelevel dopo tutti gli altri script,
rc.local e cbsdd compresi. Tutto ciò che rc legge prima va quindi
marcato, non soltanto /etc/rc.d: anche /etc/rc.local, /etc/defaults,
/etc/rc.conf.d, gli script del loader e i file che riempiono le tabelle. Un intruso che potesse
scrivervi un pfctl -d avrebbe il firewall spento al riavvio successivo.
CBSD avvia le jail in modo asincrono, quando il livello è già salito: da lì in poi il kernel non carica
più moduli. Le jail di CBSD montano la base con nullfs, che non è nel kernel GENERIC (provato: dopo la
sigillatura nessuna jail partiva, mount_nullfs: Invalid fstype), e /dev/fd con
fdescfs. Il primo si dichiara nel loader; il secondo si toglie alle jail, che non ne hanno bisogno.
# /boot/loader.conf
nullfs_load="YES"
# fdescfs: invece di caricarlo, non si monta (per ogni jail, a jail ferma)
doas cbsd jset jname=web mount_fdescfs=0
doas sysrc kern_securelevel_enable=YES kern_securelevel=2 # prima dei flag: rc.conf diventa immutabile
doas chflags -R schg /boot/kernel /boot/loader /boot/loader.conf /boot/loader.conf.d /boot/lua /boot/defaults
doas chflags -R schg /sbin /bin /lib /libexec /usr/bin /usr/sbin /usr/lib /usr/libexec
doas chflags -R schg /etc/rc.d /usr/local/etc/rc.d /etc/defaults /etc/rc.conf.d
doas chflags schg /etc/rc /etc/rc.subr /etc/rc.conf /etc/sysctl.conf /etc/fstab /etc/ttys \
/etc/pf.conf /etc/ipfw.rules /etc/login.conf /usr/local/etc/doas.conf \
/etc/rc.local /etc/crontab /etc/rctl.conf /etc/ipfw.mai_bloccare
doas reboot
# dopo il riavvio: le jail devono essere partite da sole
jls name securelevel # -> tutte, e tutte a 3
Resta fuori un rischio che la sigillatura non copre: i programmi in /usr/local lanciati da
rc prima che il livello salga (sshguard, dnscrypt-proxy, CBSD). Marcarli fermerebbe gli
aggiornamenti dei port; li sorveglia invece la base di riferimento di mtree, controllata prima di
ogni riavvio.
Da questo momento un aggiornamento del sistema di base richiede un passaggio in più: riavviare in modalità
utente singolo, togliere i flag con chflags -R noschg sugli stessi percorsi, aggiornare,
rimetterli e riavviare. La modalità utente singolo chiede la password di root: è il motivo per cui root non
si blocca. Gli aggiornamenti dei soli pacchetti e delle jail non ne sono toccati, salvo gli
script in /usr/local/etc/rc.d.
Verifica finale
Il nodo è pronto quando ogni riga dà il risultato atteso.
| Che cosa | Comando | Risultato atteso |
|---|---|---|
| IPv6 spento | ifconfig | grep inet6 | Nessuna riga. Sulle loopback il kernel assegna comunque ::1 e fe80::1: si tolgono da /etc/rc.local (ifconfig lo0 inet6 ::1 -alias, lo stesso per i due fe80::1) con ifconfig_lo0_ipv6="inet6 -auto_linklocal ifdisabled" e l'equivalente per lo1 in rc.conf (provato) |
| MAC casuale | ifconfig em0 | grep ether | Diverso a ogni riavvio |
| Securelevel | sysctl kern.securelevel | 2 o superiore |
| File di avvio immutabili | ls -lo /sbin/init /etc/rc.conf, poi doas chflags noschg /etc/rc.conf | Flag schg presente, e il secondo comando rifiutato |
| Jail al livello 3, senza bpf | doas jexec web sysctl kern.securelevel, doas jexec web ls /dev | 3, e nessun bpf, mem, kmem |
| W^X e ASLR | sysctl kern.elf64.allow_wx kern.elf64.aslr.enable | 0 e 1 |
| Messaggio di accesso | Aprire una sessione sull'host e in una jail | Solo il testo scelto: nessuna versione, nessun nome |
| Bando automatico | Quattro accessi SSH falliti da un altro computer della rete locale, poi doas ipfw table 22 list | L'indirizzo in tabella; il computer di amministrazione e la jail tor mai |
| Utente singolo protetto | Riavviare e scegliere la modalità utente singolo | Richiesta della password di root |
| Regole di doas | doas -C /usr/local/etc/doas.conf | Nessun output |
| sshd come lo applica davvero | doas sshd -T | grep -E "^(passwordauthentication|allowtcpforwarding|permitrootlogin) " | no per tutte e tre |
| Segreti di i2pd | doas jexec i2p ls -ld /var/db/i2pd /usr/local/etc/i2pd/i2pd.conf | drwx------ e -rw-r----- |
| Nessuna fuga in uscita | Sul router, per dieci minuti di uso normale: tcpdump -ni <porta del nodo> "ip6 or port 53 or port 123 or icmp" | Nessun pacchetto (provato: escono soltanto il traffico di Tor, di I2P e di DNSCrypt) |
| Nessun servizio sull'host | sockstat -4l | Solo sshd sulla rete locale e dnscrypt-proxy su 127.0.0.1 e 10.0.0.1 |
| pf congelato | doas pfctl -d | Rifiutato |
| L'host non esce in chiaro | doas pfctl -t manutenzione -T show, poi doas fetch -o /dev/null https://www.freebsd.org senza proxy | Nessun indirizzo, e il collegamento fallisce |
| Porte viste dalla rete | Da un altro computer: nmap -Pn -p- 192.168.178.3 | La 22, solo dalla rete locale, e la porta di I2P (i2p_port), che pf inoltra alla jail i2p (provato: nient'altro su 65535) |
| Niente timestamp | sysctl net.inet.tcp.rfc1323 net.inet.icmp.tstamprepl | 0 e 0 |
| DNS cifrato | drill freebsd.org @127.0.0.1 | Risposta, e nessuna richiesta in chiaro sulla porta 53 verso il router |
| Tor | curl --socks5-hostname 10.0.0.2:59353 https://check.torproject.org/api/ip | "IsTor":true |
| Isolamento tra le jail | doas jexec web nc -zv -w 3 10.0.0.3 7656 | Timeout, e una riga in pflog |
| Orologio | doas /usr/local/sbin/ora-da-tor | Scarto di pochi secondi |
| Le jail dei servizi non escono | doas jexec web nc -zv -w 3 1.1.1.1 443 | Timeout, e una riga in pflog |
| Sito onion | Da Tor Browser, l'indirizzo in hostname | La pagina, senza intestazioni di versione |
| Pacchetti | doas pkg audit -F | Nessuna vulnerabilità nota |
| Integrità | doas mtree -p /usr/local < base.mtree | Nessun output |
Sviluppi previsti
Parti del progetto non ancora coperte da questa guida:
- Virtualizzazione: bhyve per i carichi che non possono stare in una jail, con switch virtuali al posto dei bridge. Proxmox VE non è previsto per il Classico: le ragioni sono in Proxmox VE, e il suo uso è descritto solo in Tutanum Lieve.
- Cluster: più single board computer in parallelo per aumentare la capacità di calcolo.
- Variante GNU/Linux: Gentoo o Alpine con musl, doas, OpenZFS e nftables, per chi non può usare un sistema BSD.
- Infrastruttura fisica: refrigerazione e gruppo di continuità autocostruiti.
- Servizi: XMPP, condivisione temporanea di file, videochiamate.