Home

Tutanum Lieve

Guida pratica

Tutanum Lieve

Introduzione

Per le basi (didattica): Il percorso della didattica.
Per i motivi (discussione): Quali sono le versioni di Tutanum?, Modello di minaccia.

Tutanum Lieve nasce per riutilizzare un computer che si possiede già: un vecchio portatile, un fisso dismesso. È la stessa infrastruttura del Classico, costruita però su hardware che non ha i requisiti per certificare il nodo come sicuro (vedi Quali sono le versioni di Tutanum?).

Non significa che un Lieve sia insicuro. Significa che nella macchina restano parti nascoste, che non si possono ispezionare né togliere del tutto e che potrebbero comprometterne sicurezza e integrità. Il lavoro di questa guida è duplice: ridurre quelle parti quanto si può, e compensare con mitigazioni attorno alla macchina ciò che dentro non si può risolvere.

Il percorso è questo: si valuta il computer, lo si bonifica, si installa il sistema seguendo la guida del Classico con le differenze indicate qui, e si aggiungono le mitigazioni proprie del Lieve. Dove l'hardware non regge FreeBSD direttamente c'è la variante con Proxmox.

Che cosa cambia rispetto al Classico

AspettoClassicoLieve
HardwareScelto e acquistato appostaDi recupero, con una storia che non si conosce
FirmwareLibero o verificabileQuasi sempre proprietario (UEFI del produttore)
Sottosistemi di gestioneAssenti o neutralizzatiIntel ME o AMD PSP presenti e attivi
Radio, microfoni, webcamAssentiPresenti: vanno tolti a mano
AlimentazioneGruppo di continuità esternoLa batteria del portatile fa da gruppo di continuità
InstallazioneSempre offlineOffline dove possibile; se serve la rete, il minimo indispensabile
FiduciaIl nodo può custodire i segreti più importantiIl nodo va trattato come potenzialmente osservato dal basso
VirtualizzazioneJail, e bhyve dove serveJail; in alternativa Proxmox sotto il nodo, se l'hardware lo richiede
Dal sistema operativo in suIdentico: stessi livelli, stesse configurazioni

Scegliere il computer di recupero

Per le basi (didattica): Architettura di Neumann, Microarchitettura.
Per i motivi (discussione): CPU, Intel Management Engine e AMD PSP, Single Board Computer.

Non tutti i computer vecchi vanno bene. Prima di iniziare si controlla la macchina contro questa lista.

  • Architettura supportata: il processore deve essere supportato da FreeBSD o OpenBSD. Un comune PC a 64 bit lo è.
  • Risorse: il riferimento resta 4 core e 4 GB di RAM. Con meno si riduce il numero di jail: Tor, un servizio e l'esca sono il minimo sensato.
  • Compatibilità con Libreboot o Coreboot: è la prima cosa da verificare. Se il modello è nell'elenco, il firmware si può sostituire e la macchina si avvicina molto a un Classico (vedi Intel Management Engine e AMD PSP).
  • Portatile con batteria estraibile: è la soluzione più semplice per l'alimentazione, e i modelli precedenti al 2013 si aprono senza attrezzi speciali (vedi Alimentazione).
  • Rete cablata: serve una porta ethernet. Il Wi-Fi non si usa.
  • Disco: attacco SATA, per montare una SSD nuova.
  • Lettore ottico: interno o USB, per installare da DVD-R.

Sono da scartare gli smartphone e i tablet: firmware chiuso, batteria fissa, radio sempre attive, nessun interruttore fisico (vedi Smartphone come server Tutanum). Un Raspberry Pi si avvia con firmware proprietario: può essere al massimo un Lieve, mai un Classico (vedi Single Board Computer).

Inventario delle parti nascoste

Per le basi (didattica): Memoria, bus e ingresso/uscita, Architettura di Neumann.
Per i motivi (discussione): Attacchi hardware delle intelligence, Impianti nel firmware, Impianti sulle interfacce.

Prima di toccare qualcosa si prende nota di che cosa c'è nella macchina. È la lista di ciò che andrà tolto, spento o compensato.

PartePerché è un problemaChe cosa si può fare
Intel ME, AMD PSPUn computer nel computer, sotto il sistema operativo, con accesso a memoria e rete Neutralizzare dove possibile, altrimenti isolare dalla rete
UEFI proprietarioNon verificabile, bersaglio di rootkit che sopravvivono alla reinstallazione Sostituire con Libreboot o Coreboot se supportato; altrimenti aggiornare, bloccare con password e installare in modalità BIOS
Wi-Fi, Bluetooth, modem cellulareCanali radio fuori dal controllo del firewallRimuovere la scheda
Webcam, microfono, altoparlantiSensori, e canali di esfiltrazioneScollegare i connettori
Disco esistenteContiene i dati e forse il malware del passato della macchinaSostituire con una SSD nuova
Porte con accesso diretto alla memoriaFireWire, ExpressCard, Thunderbolt leggono la RAMDisattivare nel firmware, sigillare
CPU datataMeltdown, Spectre e varianti, con microcodice non più aggiornatoAggiornare il microcodice, attivare le mitigazioni
Tasto di accensione elettronicoLa macchina "spenta" resta alimentataStaccare alimentazione e batteria quando deve essere davvero spenta

Da un sistema avviato da supporto esterno, o dopo l'installazione, si controlla che cosa la macchina dichiara:

# FreeBSD: c'è un sottosistema di gestione? quali schede di rete e radio? pciconf -lv | grep -i -B3 -E "management engine|MEI|HECI|AMT" pciconf -lv | grep -i -B3 -E "network|wireless|bluetooth" # processore e stato delle mitigazioni sysctl hw.model vm.pmap.pti hw.ibrs_disable hw.mds_disable hw.spec_store_bypass_disable

Bonifica dell'hardware

Per le basi (didattica): Radiotecnica, Diodi e alimentatori.
Per i motivi (discussione): BIOS e UEFI, Alimentazione, Esfiltrazione via radiofrequenza.

Aprire e togliere

  1. Staccare tutto: alimentatore e batteria, e tenere premuto il tasto di accensione per scaricare i condensatori.
  2. Fotografare l'interno prima di toccare, come riferimento per il rimontaggio e per il futuro.
  3. Rimuovere la scheda Wi-Fi e Bluetooth: di solito un modulo a innesto con due cavetti d'antenna. Rimuovere anche l'eventuale modem cellulare e la SIM.
  4. Scollegare webcam, microfono e altoparlanti: i connettori sono sulla scheda madre o nel bordo dello schermo.
  5. Sostituire il disco con una SSD SATA nuova. Il vecchio disco non si riusa nel nodo: va cancellato e distrutto come indicato in Anti-forensics ed emergenza.
  6. Pulire e controllare: polvere, pasta termica, ventola. Una macchina che scalda in modo irregolare è anche un canale laterale in più.
  7. Cercare ciò che non dovrebbe esserci: componenti aggiunti, saldature rifatte, moduli sulle porte (vedi Impianti sulle interfacce).
  8. Richiudere, sigillare e fotografare.

Firmware

Se la macchina è supportata da Libreboot o Coreboot, quella è la strada, e va percorsa seguendo la documentazione del progetto per il modello esatto. In tutti gli altri casi il firmware del produttore resta, e lo si mette nelle condizioni di fare meno danni possibile.

  1. Aggiornarlo all'ultima versione, scaricata dal sito del produttore sul computer fidato e verificata. È l'unica operazione che richiede di fidarsi ancora del produttore, e si fa una volta sola.
  2. Impostare una password per la configurazione e per l'avvio.
  3. Disattivare: avvio da rete (PXE) e da USB, Wake-on-LAN, Intel AMT e ogni funzione di gestione remota, Thunderbolt, FireWire, lettore di impronte, TPM se non lo si usa.
  4. Attivare la compatibilità BIOS (CSM o Legacy): l'installazione avverrà con tabella GPT in modalità BIOS, evitando UEFI (vedi BIOS e UEFI).
  5. Ordine di avvio: solo il disco interno, dopo l'installazione.

Per Intel ME esistono due interventi, entrambi per utenti esperti: lo strumento me_cleaner, che riduce il firmware di ME al minimo necessario all'avvio, e il bit HAP, che gli chiede di spegnersi dopo l'inizializzazione. Richiedono di leggere e riscrivere il chip del firmware con un programmatore esterno, e un errore rende la macchina inutilizzabile. Non eliminano ME: ne riducono la superficie. Chi non può applicarli passa direttamente alle mitigazioni.

Alimentazione e coperchio

Il portatile porta con sé il proprio gruppo di continuità. Perché funzioni da server vanno cambiate due abitudini della macchina: non deve mai sospendersi (in sospensione le chiavi del disco restano in memoria) e non deve reagire alla chiusura del coperchio.

# /etc/sysctl.conf hw.acpi.lid_switch_state=NONE # chiudere il coperchio non fa nulla hw.acpi.sleep_button_state=NONE hw.acpi.power_button_state=S5 # il tasto di accensione spegne, non sospende # /etc/rc.conf powerd_enable="YES" # frequenza della CPU regolata sul carico: meno calore, meno consumo

La batteria va controllata: una batteria gonfia si sostituisce subito. Quando il nodo deve essere davvero spento, si staccano sia l'alimentatore sia la batteria.

Installazione

Per le basi (didattica): Sistemi operativi, File system.
Per i motivi (discussione): Distribuzioni, Crittografia.

Si segue la guida del Classico dal livello 2 in poi, con queste differenze.

Passo del ClassicoNel Lieve
Scaricare e verificare l'immagineImmagine amd64; per le macchine a 32 bit, che FreeBSD non supporta più nelle versioni recenti, si passa a OpenBSD
InstallazioneSchema di partizionamento "GPT (BIOS)". Se la macchina richiede la rete per completare l'installazione, la si collega per il minimo indispensabile e si prosegue offline
Prima configurazioneSi aggiungono le righe per coperchio e alimentazione viste sopra
HardeningSi aggiungono microcodice e mitigazioni della CPU, qui sotto
Firewall e DNSIdentico, più il filtro sul router descritto nelle mitigazioni
JailIdentico. Con poca RAM si rinuncia per prime alle jail iop e i2p, mai all'esca
Livelli 5, 6 e 7Identici
# microcodice del processore: è software non libero, ma corregge difetti che nessun altro strato può correggere doas pkg install cpu-microcode # /boot/loader.conf cpu_microcode_load="YES" cpu_microcode_name="/boot/firmware/intel-ucode.bin" vm.pmap.pti=1 # Meltdown machdep.hyperthreading_allowed=0 # niente hyper-threading: toglie una famiglia di canali laterali # /etc/sysctl.conf: mitigazioni per i processori datati hw.ibrs_disable=0 # Spectre v2 hw.mds_disable=3 # MDS, scelta automatica della mitigazione migliore hw.spec_store_bypass_disable=2 # Spectre v4 machdep.syscall_ret_flush_l1d=1 # svuotamento della cache L1D: impostazione paranoica, qui giustificata machdep.mitigations.rngds.enable=1

Le ragioni del microcodice sono in Hardening: le indicazioni di Kicksecure, quelle delle mitigazioni in CPU e in Canali laterali fisici.

Variante con Proxmox

Per le basi (didattica): Memoria virtuale, Processi e thread.
Per i motivi (discussione): Proxmox VE, Namespace, container e jail.

Se FreeBSD non riconosce l'hardware del computer di recupero, o se il nodo deve fare da banco di prova con istantanee e cloni, il Lieve si può costruire sopra Proxmox VE: sotto c'è Linux che parla con il ferro, sopra una macchina virtuale FreeBSD che ospita il nodo Tutanum. Vantaggi, limiti e valori non rispettati sono discussi in Proxmox VE, da leggere prima di scegliere questa strada. Per il Classico non è prevista.

ConvieneNon conviene
FreeBSD non vede la scheda di rete o il discoFreeBSD gira bene direttamente
Almeno 8 GB di RAM e un processore con VT-x o AMD-V4 GB di RAM, o nessuna estensione di virtualizzazione
Ruolo di banco di prova, esca o nodo ridondatoIl nodo custodisce chiavi importanti
Più sistemi diversi sulla stessa macchinaUn solo servizio

Installare e ridurre

La bonifica dell'hardware resta quella descritta sopra. Poi si installa Debian in forma minima su un volume cifrato, offline dove possibile, e vi si aggiunge Proxmox seguendo la documentazione ufficiale della versione corrente: è la fase di manutenzione, l'unica in cui l'host esce direttamente su Internet. Subito dopo lo si riduce e lo si chiude. Tutto ciò che segue è stato provato su Proxmox VE 9.2, installato così su Debian 13, con il nodo FreeBSD 15.1 accanto: in questo caso lo storage dei dischi è local, una directory, e non local-zfs, che esiste solo installando Proxmox su ZFS; per avere le istantanee i dischi si creano in formato qcow2. Installando invece dall'immagine ISO di Proxmox, il disco è in LVM: local non accetta dischi di macchine virtuali e si usa local-lvm, LVM-thin, che fa comunque le istantanee. In quel caso si controllano prima di tutto il resto due cose: il repository di Ceph, che a un nodo singolo non serve e va disattivato, e la partizione EFI in /etc/fstab. Se manca, grub-install non trova dove scrivere e gli aggiornamenti del bootloader non arrivano.

findmnt /boot/efi || echo "manca: aggiungere UUID=<della partizione vfat> /boot/efi vfat defaults 0 1" apt install grub-efi-amd64 && grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=proxmox mv /etc/apt/sources.list.d/ceph.sources /etc/apt/sources.list.d/ceph.sources.disabled # nodo singolo: niente cluster né alta disponibilità systemctl disable --now pve-ha-lrm pve-ha-crm corosync systemctl disable --now rpcbind.service rpcbind.socket # niente deduplicazione della memoria tra ospiti (canale laterale): in Proxmox VE 9 ksmtuned non è # installato di default (pacchetto ksm-control-daemon); se c'è, si spegne, e in ogni caso si controlla systemctl disable --now ksmtuned 2>/dev/null cat /sys/kernel/mm/ksm/run # 0: KSM spento # niente uscite automatiche in chiaro. La procedura ufficiale installa chrony (NTP verso i pool di # Debian), Debian aggiorna gli indici ogni giorno e pve-daily-update scarica l'elenco dei modelli systemctl disable --now chrony apt-daily.timer apt-daily-upgrade.timer pve-daily-update.timer # niente annunci di nomi sulla rete locale (dove Debian usa systemd-resolved: LLMNR sulla porta 5355) mkdir -p /etc/systemd/resolved.conf.d printf "[Resolve]\nLLMNR=no\nMulticastDNS=no\n" > /etc/systemd/resolved.conf.d/tutanum.conf systemctl restart systemd-resolved # il repository a pagamento: proxmox-ve lo aggiunge da solo, e senza abbonamento risponde 401 echo "Enabled: no" >> /etc/apt/sources.list.d/pve-enterprise.sources # /etc/default/pveproxy: l'interfaccia web ascolta solo in locale LISTEN_IP="127.0.0.1" systemctl restart pveproxy spiceproxy

Poi le difese del kernel e della CPU. Il microcodice è software non libero e sta nel componente non-free-firmware, che una Debian minimale non ha. Proxmox imposta da sé rp_filter in modalità lasca, di cui ha bisogno il suo firewall, e lo applica dopo i file di /etc/sysctl.d: lo si lascia così. I namespace utente restano attivi, perché servono ai container non privilegiati (provato: si avviano normalmente con tutte le impostazioni qui sotto).

# microcodice e mitigazioni, sull'host sed -i "s/^Components: main$/Components: main non-free-firmware/" /etc/apt/sources.list.d/debian.sources apt update && apt install intel-microcode # oppure amd64-microcode grep -H . /sys/devices/system/cpu/vulnerabilities/* # nessun "Vulnerable" # /etc/default/grub: niente hyper-threading. Poi: update-grub GRUB_CMDLINE_LINUX_DEFAULT="quiet nosmt" # /etc/sysctl.d/99-tutanum.conf net.ipv6.conf.all.disable_ipv6 = 1 net.ipv4.tcp_timestamps = 0 kernel.kptr_restrict = 2 kernel.dmesg_restrict = 1 kernel.unprivileged_bpf_disabled = 1 net.core.bpf_jit_harden = 2 kernel.yama.ptrace_scope = 2 kernel.kexec_load_disabled = 1 kernel.sysrq = 0 fs.protected_symlinks = 1 fs.protected_hardlinks = 1 fs.protected_fifos = 2 fs.protected_regular = 2 net.ipv4.conf.all.accept_redirects = 0 net.ipv4.conf.all.send_redirects = 0 net.ipv4.conf.all.accept_source_route = 0 net.ipv4.conf.all.log_martians = 1 # /tmp e /dev/shm non eseguono (apt, container e backup funzionano ugualmente) mkdir -p /etc/systemd/system/tmp.mount.d printf "[Mount]\nOptions=mode=1777,strictatime,nosuid,nodev,noexec,size=50%%%%,nr_inodes=1m\n" \ > /etc/systemd/system/tmp.mount.d/noexec.conf echo "tmpfs /dev/shm tmpfs defaults,nosuid,nodev,noexec 0 0" >> /etc/fstab apt install auditd

Su Debian con il disco cifrato restano in chiaro kernel e initramfs, non solo il piccolo codice di avvio come su FreeBSD, e non c'è un securelevel che li protegga: chi ha accesso fisico ha un bersaglio più grande. Si mette una password a GRUB, che lascia avviare il sistema ma non modificare le voci, e si conserva fuori dalla macchina l'impronta di /boot, da confrontare a ogni controllo.

# GRUB: avvio libero, modifica delle voci solo con la password grub-mkpasswd-pbkdf2 # copiare l'hash "grub.pbkdf2...." printf 'set superusers="admin"\npassword_pbkdf2 admin grub.pbkdf2....\n' >> /etc/grub.d/40_custom sed -i 's/^CLASS="--class gnu-linux --class gnu --class os"/CLASS="--class gnu-linux --class gnu --class os --unrestricted"/' /etc/grub.d/10_linux update-grub # l'impronta di /boot, da portare sul computer fidato e confrontare find /boot -type f -exec sha256sum {} + | sort -k2 > boot.sha256

Il firewall dell'host è quello di Proxmox. Va scritto con attenzione, perché fa due cose che non si vedono: accetta in automatico, dall'insieme management, le porte 22, 8006, 3128, 5900-5999 e 60000-60050, e in quell'insieme mette tutta la rete locale riconosciuta. Una regola come "SSH solo dal computer di amministrazione" da sola quindi non basta (provato: pve-firewall compile mostrava ancora l'intera 192.168.178.0/24). La rete locale si ridefinisce con l'alias local_network. In uscita il criterio è quello del nodo: l'host non esce da solo, e per aggiornamenti e ora usa il SOCKS di Tor del nodo (vedi qui sotto).

# /etc/pve/firewall/cluster.fw [OPTIONS] enable: 1 policy_in: DROP policy_out: DROP [ALIASES] local_network 192.168.178.10 # "rete di gestione" = il solo computer di amministrazione [IPSET management] 192.168.178.10 [RULES] IN SSH(ACCEPT) -source 192.168.178.10 OUT ACCEPT -dest 192.168.178.3 -p tcp -dport 59353 # il SOCKS di Tor del nodo, e nient'altro # con più computer di amministrazione: l'alias contiene un solo indirizzo, l'insieme tutti # [ALIASES] local_network 192.168.178.10 # [IPSET management] 192.168.178.10 192.168.178.11 # [RULES] una riga IN SSH(ACCEPT) per ciascuno pve-firewall compile | grep "add PVEFW-0-management" # soltanto 192.168.178.10 pve-firewall status # enabled/running

Il firewall richiede il kernel di Proxmox: con il kernel di Debian manca il modulo per i bridge e le regole non vengono mai caricate (provato: pve-firewall status resta su "pending changes"). Dopo l'installazione si riavvia, e uname -r deve finire con -pve.

Sull'host Debian valgono poi le stesse scelte del Classico tradotte per GNU/Linux: doas al posto di sudo, sshguard (con il backend per nftables) contro la forza bruta, un messaggio di accesso che non dice nulla del sistema (vedi Configurare doas.conf, Difesa dalla forza bruta e Il messaggio di accesso). sshd si configura come nel Classico, con le righe in cima al file, ListenAddress 192.168.178.2 e la verifica con sshd -T. Con una differenza: l'interfaccia web si raggiunge solo con un tunnel SSH, e il AllowTcpForwarding no del Classico lo impedirebbe. Sull'host Proxmox si apre il solo inoltro locale, e soltanto verso la porta dell'interfaccia.

# /etc/ssh/sshd_config dell'host Proxmox, in cima: al posto di AllowTcpForwarding no AllowTcpForwarding local PermitOpen 127.0.0.1:8006

L'interfaccia web si raggiunge solo attraverso SSH, con un utente proprio e il secondo fattore. Il ruolo PVEVMAdmin gestisce macchine e container ma non utenti e permessi; root@pam resta per le operazioni sull'host, e riceve anch'esso un secondo fattore.

# un utente di amministrazione nel realm di Proxmox, con un ruolo limitato pveum user add operatore@pve --password # chiede la password: serve un terminale pveum acl modify / --users operatore@pve --roles PVEVMAdmin # senza terminale (da uno script) pveum si ferma su --password: si usa l'API locale # pvesh create /access/users --userid operatore@pve --password "$PASSWORD" # secondo fattore TOTP: segreto in base32, lo stesso inserito nell'app sul telefono pvesh create /access/tfa/operatore@pve --type totp --description telefono \ --totp "otpauth://totp/pve:operatore@pve?secret=<SEGRETO>&issuer=pve" --value <CODICE ATTUALE> # dal proprio computer: poi si apre https://127.0.0.1:8006 ssh -L 8006:127.0.0.1:8006 operatore@192.168.178.2

Provato: con la sola password il ticket non concede alcun permesso, un codice sbagliato è respinto e solo il codice giusto apre la sessione.

Uscite: soltanto attraverso il nodo

Dopo la fase di manutenzione l'host Proxmox non ha più accesso diretto a Internet: aggiornamenti, ora e modelli passano dal SOCKS di Tor del nodo. Il SOCKS però sta in una jail dentro la macchina virtuale, quindi il nodo gli apre una sola porta sul proprio indirizzo, e soltanto per l'host.

# /etc/pf.conf del nodo pve = "192.168.178.2" # l'host Proxmox (nella sezione delle macro) # nella sezione di traduzione rdr on $ext_if inet proto tcp from $pve to ($ext_if) port $tor_socks -> $j_tor port $tor_socks # nella sezione di filtro, prima di "Verso Internet" pass in quick on $ext_if inet proto tcp from $pve to $j_tor port $tor_socks keep state # /usr/local/etc/tor/torrc (nella jail tor): anche Tor accetta l'host SocksPolicy accept 10.0.0.2, accept 10.0.1.0/24, accept 192.168.178.2, reject * # sull'host Proxmox: apt attraverso Tor (Debian e Proxmox) printf 'Acquire::http::Proxy "socks5h://192.168.178.3:59353";\nAcquire::https::Proxy "socks5h://192.168.178.3:59353";\n' \ > /etc/apt/apt.conf.d/90tutanum-tor apt update

Con chrony spento, l'ora arriva come sul nodo: la mediana dell'intestazione Date di tre onion service, attraverso lo stesso SOCKS. Su Linux non c'è il limite del securelevel e l'orologio si corregge in un passo (provato: spostato indietro di due minuti, torna in pari).

#!/bin/sh # /usr/local/sbin/ora-da-tor (host Proxmox): mediana dell'ora di tre onion service, attraverso il nodo P=socks5h://192.168.178.3:59353 ora=$(for o in primo.onion secondo.onion terzo.onion; do d=$(curl -sI --max-time 60 -x $P "http://$o/" | sed -n 's/^[Dd]ate: //p' | tr -d '\r') [ -n "$d" ] && date -u -d "$d" +%s 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" if [ "${scarto#-}" -gt 5 ]; then date -s "@$ora" > /dev/null command -v hwclock > /dev/null && hwclock --systohc # util-linux-extra, se c'è fi

pveam scarica soltanto direttamente, quindi i modelli dei container si prendono con curl attraverso il nodo, verificando prima la firma dell'indice con la chiave di rilascio di Proxmox e poi l'impronta del modello.

P=socks5h://192.168.178.3:59353; B=http://download.proxmox.com/images curl -x $P -O $B/aplinfo-pve-9.dat -O $B/aplinfo-pve-9.dat.asc gpgv --keyring /usr/share/doc/pve-manager/trustedkeys.gpg aplinfo-pve-9.dat.asc aplinfo-pve-9.dat grep -A20 "^Package: alpine-3.24-default" aplinfo-pve-9.dat | grep -E "^(Location|sha512sum):" curl -x $P -o /var/lib/vz/template/cache/alpine-3.24-default_20260714_amd64.tar.xz \ $B/system/alpine-3.24-default_20260714_amd64.tar.xz sha512sum /var/lib/vz/template/cache/alpine-3.24-default_20260714_amd64.tar.xz # = sha512sum dell'indice

Provato con il firewall attivo: sulla porta verso Internet dell'host non esce alcun pacchetto, l'unica destinazione è il SOCKS del nodo, e i tentativi di uscita diretta (HTTPS, DNS, NTP, pve-daily-update) vengono scartati.

Due reti

Una rete collega alla porta fisica, l'altra esiste solo dentro la macchina. Sulla prima sta il nodo Tutanum, sulla seconda tutti gli altri ospiti, che così escono soltanto attraverso di lui.

Gli indirizzi sono quelli del Classico: nella zona del nodo, 192.168.178.0/24, il router è .1, l'host Proxmox .2, il nodo Tutanum .3 e il computer di amministrazione .10.

# /etc/network/interfaces auto vmbr0 # verso il router: host Proxmox (.2) e nodo Tutanum (.3) iface vmbr0 inet static address 192.168.178.2/24 gateway 192.168.178.1 bridge-ports enp3s0 bridge-stp off bridge-fd 0 auto vmbr1 # rete interna: nessuna porta fisica iface vmbr1 inet manual bridge-ports none bridge-stp off bridge-fd 0

La macchina virtuale del nodo

# immagine di FreeBSD, verificata come nel Classico, in /var/lib/vz/template/iso/ qm create 100 --name nodo --ostype other --machine q35 --bios seabios \ --memory 4096 --cores 2 --cpu host \ --scsihw virtio-scsi-single --scsi0 local:32,format=qcow2,discard=on,ssd=1 \ --net0 virtio,bridge=vmbr0,firewall=1 \ --net1 virtio,bridge=vmbr1,firewall=1 \ --ide2 local:iso/FreeBSD-RELEASE-amd64-dvd1.iso,media=cdrom \ --onboot 1 # meno dispositivi emulati, meno codice di QEMU esposto all'ospite qm set 100 --balloon 0 --tablet 0 --agent 0 --vga serial0 --serial0 socket qm start 100

Con il disco su local-lvm (installazione da ISO) la riga del disco diventa --scsi0 local-lvm:32,discard=on,ssd=1. Senza scheda video, con --vga serial0, tutto passa dalla console seriale, che si apre dall'interfaccia web o con qm terminal 100: anche il BIOS della macchina virtuale scrive lì, e lì compare la richiesta della passphrase di GELI (provato). Il programma di installazione di FreeBSD però usa lo schermo: al menu del loader si sceglie "Escape to loader prompt" e si scrive set console=comconsole e poi boot; sul sistema installato console="comconsole" va in /boot/loader.conf. Ogni volta che l'host si riavvia il nodo si ferma sulla passphrase, e finché non la si inserisce l'host resta senza uscite: è il prezzo del disco cifrato.

qm terminal 100 # dall'host: la console seriale del nodo (Ctrl+O per uscire) # GELI Passphrase for disk0p3: <- la si digita qui, a ogni avvio

firewall=1 sulla scheda non basta: senza un file per la macchina virtuale con enable: 1 le regole per lei sono zero (provato). Il filtro in ingresso resta a pf, dentro il nodo; quello di Proxmox serve a impedire che la macchina usi un indirizzo MAC o IP diverso dal suo, cioè che un ospite compromesso si finga il nodo. Lo stesso vale per ogni container sulla rete interna.

# /etc/pve/firewall/100.fw [OPTIONS] enable: 1 ipfilter: 1 macfilter: 1 policy_in: ACCEPT # l'ingresso lo filtra pf, dentro il nodo policy_out: ACCEPT [IPSET ipfilter-net0] # gli unici indirizzi che il nodo può usare 192.168.178.3 [IPSET ipfilter-net1] 10.0.1.1

Dalla console della macchina virtuale si segue la guida del Classico dal passo dell'installazione, con queste differenze.

  • Avvio in modalità BIOS (seabios), coerente con la scelta "GPT (BIOS)".
  • Cifratura anche dentro l'ospite: il disco della macchina virtuale si cifra comunque con GELI, così che i suoi backup siano cifrati ovunque finiscano.
  • Interfaccia di rete: si chiama vtnet0 anziché em0: in pf.conf basta cambiare la macro ext_if. La seconda, vtnet1, è la rete interna per gli altri ospiti: il nodo ne è il router e li fa uscire soltanto attraverso Tor.
  • Microcodice e mitigazioni della CPU: si applicano sull'host Proxmox, non nell'ospite.
  • Niente MAC casuale: ether random del Classico è incompatibile con il macfilter di Proxmox, che lascia passare soltanto il MAC assegnato alla scheda: con un MAC nuovo a ogni avvio il nodo resterebbe isolato. L'indirizzo lo vede comunque solo il router di casa.
  • Il DNS passa da Tor: come indicato nelle mitigazioni, quindi in pf.conf le due righe che lasciano uscire dnscrypt-proxy (user 978) si commentano: l'host del nodo non ha più alcuna uscita propria.
# /etc/rc.conf del nodo: le due schede (niente "ether random") ifconfig_vtnet0="inet 192.168.178.3/24" ifconfig_vtnet1="inet 10.0.1.1/24" # /etc/pf.conf del nodo, nella sezione delle jail: gli ospiti usano soltanto il SOCKS di Tor. # Niente NAT e niente gateway_enable: tutto il resto cade nel default-deny. pass in quick on vtnet1 proto tcp from 10.0.1.0/24 to $j_tor port $tor_socks keep state
  • Jail, Tor, I2P, servizi e difesa: identici al Classico, dentro la macchina virtuale.

Istantanee, backup e altri ospiti

qm snapshot 100 prima-aggiornamento # prima di ogni aggiornamento del nodo qm rollback 100 prima-aggiornamento # se qualcosa va storto qm clone 100 101 --name prova --full # un clone su cui provare, da tenere sulla rete interna # backup cifrato prima ancora di toccare il disco: la chiave privata non sta sull'host vzdump 100 --mode snapshot --compress zstd --stdout | age -r <chiave-pubblica> > nodo-$(date +%F).vma.zst.age # un container solo per servizi interni e sacrificabili: sempre non privilegiato, sempre sulla rete interna # (il modello si scarica attraverso il nodo, vedi "Uscite: soltanto attraverso il nodo") pct create 200 local:vztmpl/alpine-3.24-default_20260714_amd64.tar.xz --unprivileged 1 \ --memory 256 --net0 name=eth0,bridge=vmbr1,ip=10.0.1.20/24,gw=10.0.1.1,firewall=1 pct config 200 | grep -c nesting # 0: niente container annidati # /etc/pve/firewall/200.fw: il container usa soltanto il suo indirizzo [OPTIONS] enable: 1 ipfilter: 1 macfilter: 1 [IPSET ipfilter-net0] 10.0.1.20

Tutto ciò che è esposto verso l'esterno sta in una macchina virtuale, mai in un container. Il clone di prova va rigenerato nelle sue identità (chiavi SSH, chiavi degli onion service, destinazioni I2P) prima di essere acceso in rete: due nodi con le stesse chiavi si tradiscono a vicenda. Provato: il backup passato in age si decifra e restituisce l'archivio della macchina; il container gira con il profilo AppArmor di Proxmox in modalità enforce.

Un'applicazione che non entra in una jail

Non tutto il software sta in una jail FreeBSD. Un'applicazione distribuita come insieme di container Linux (più servizi che cooperano, un fornitore di identità, uno o più database, un proxy davanti) non gira né in una jail né sotto il Linuxulator: le servono un kernel Linux e un motore di container (Docker o podman). Il Lieve la ospita come ogni altro ospite interno: una macchina virtuale propria sulla rete vmbr1, senza uscita se non il SOCKS di Tor del nodo, raggiungibile da fuori solo come eepsite I2P (o come onion). Vale la regola già vista: ciò che è esposto sta in una macchina virtuale, mai in un container, e nessun ospite parla con il nodo — è il nodo a interrogarli.

La disciplina è quella dell'officina: l'ospite non ha un gateway predefinito, scarica immagini e aggiornamenti soltanto attraverso il proxy Tor del nodo (il motore di container si configura con quel proxy), e aggiorna tirando dal proprio repository, mai ricevendo una spinta. I segreti che l'applicazione genera al primo avvio (file .env, chiavi, password dei database) restano sulla macchina virtuale e si salvano anche fuori, come per il nodo. Un'applicazione del genere pesa: l'host deve avere RAM a sufficienza (un fornitore di identità e due database non stanno in poche centinaia di MiB), altrimenti conviene rimandarla o ridisegnarla in servizi nativi nelle jail.

# sull'host Proxmox: una VM Linux per l'applicazione, sulla rete interna, senza uscita propria qm create 103 --name applicazione --memory 8192 --cores 2 \ --net0 virtio,bridge=vmbr1,firewall=1 --scsihw virtio-scsi-single --ostype l26 # nell'ospite (Debian): indirizzo interno, nessun gateway, rotta verso le jail attraverso il nodo, IPv6 spento # 10.0.1.3/24 ; ip route add 10.0.0.0/24 via 10.0.1.1 ; il motore di container con proxy 10.0.0.2:59353

Sul nodo bastano due regole oltre a quella, già presente, che fa uscire gli ospiti da Tor: il nodo deve poter interrogare l'ospite (per l'officina è lo scambio su SSH; vedi la messa in opera dell'officina nel Classico), e la jail i2p deve poter raggiungere il proxy dell'applicazione perché un tunnel di i2pd lo pubblichi. Il nodo instrada tra la rete interna e quella delle jail, quindi serve gateway_enable="YES" in rc.conf.

# /etc/pf.conf del nodo: scambio verso l'ospite ed esposizione dell'applicazione su I2P app = "10.0.1.3" # il nodo interroga l'ospite (officina: comando imposto; applicazione: lo si tira in aggiornamento) pass out quick on vtnet1 proto tcp from (vtnet1) to $app port 22 keep state # la jail i2p raggiunge il proxy dell'applicazione: un tunnel server di i2pd lo rende un eepsite pass out quick on vtnet1 proto tcp from $j_i2p to $app port 8080 keep state # /usr/local/etc/i2pd/tunnels.conf nella jail i2p: l'applicazione diventa un eepsite [applicazione] type = server host = 10.0.1.3 # la VM sulla rete interna port = 8080 # il proxy dell'applicazione keys = applicazione.dat inport = 80

La parte amministrativa dell'applicazione, se c'è, resta protetta dal suo stesso fornitore di identità o da un auth_basic davanti al proxy: un eepsite non è pubblico solo perché è raggiungibile. E poiché dietro un operatore domestico si finisce spesso sotto CGNAT (nessun indirizzo pubblico proprio), l'ingresso diretto dall'esterno non è comunque possibile: Tor e I2P non ne hanno bisogno — i primi raggiungono il nodo con un rendez-vous in uscita, i secondi attraverso gli introducer — mentre un inoltro di porta sul router di casa non servirebbe a nulla.

Pi-hole per la rete di casa

Per le basi (didattica): Applicazioni e web, La rete internet.
Per i motivi (discussione): Pi-hole, I limiti.

Un computer di recupero è il posto giusto per Pi-hole, il filtro DNS che protegge i dispositivi di casa da pubblicità, tracciamento e telemetria. Che cosa fa, che cosa non fa e quali valori del progetto non rispetta è discusso in Pi-hole. Richiede GNU/Linux: si installa su un Lieve dedicato con una distribuzione minima, oppure in una macchina virtuale della variante con Proxmox, collegata a vmbr0 perché deve servire la rete locale. Per il solo nodo basta la lista di blocco di dnscrypt-proxy descritta nel Classico.

Installare

Pi-hole serve la rete di casa, che è separata dalla zona del nodo: negli esempi la rete di casa è 192.168.1.0/24 con Pi-hole in 192.168.1.4, il nodo resta in 192.168.178.3. Tra le due zone il router lascia passare una sola cosa, il DNS da Pi-hole al nodo; se Pi-hole è una macchina virtuale della variante con Proxmox, vale lo stesso al contrario, con il router che lascia arrivare a Pi-hole soltanto il DNS della rete di casa. Non si usa lo script passato direttamente alla shell: si scarica un rilascio preciso, lo si controlla e solo allora si esegue.

git clone --depth 1 --branch <rilascio> https://github.com/pi-hole/pi-hole.git && cd pi-hole git rev-parse HEAD # l'hash, da confrontare con quello del rilascio su un altro canale # la firma del commit, se c'è, è spesso quella di GitHub (chiave B5690EEEBB952194, i merge fatti dal sito): # dice che il commit è passato da GitHub, non chi l'ha scritto less "automated install/basic-install.sh" # leggere ciò che sta per girare come root sudo bash "automated install/basic-install.sh" pihole setpassword # password dell'interfaccia web

Configurare

# a monte c'è il dnscrypt-proxy del nodo, mai il resolver del provider pihole-FTL --config dns.upstreams '["192.168.178.3#53"]' # risponde solo alla rete locale pihole-FTL --config dns.listeningMode LOCAL # l'interfaccia web ascolta solo sulla macchina stessa: la si raggiunge con un tunnel SSH pihole-FTL --config webserver.port '127.0.0.1:8080' # il registro delle richieste: una delle due pihole-FTL --config misc.privacylevel 3 # solo filtro: nessun dettaglio registrato pihole-FTL --config database.maxDBdays 3 # sensore: dettaglio conservato per tre giorni systemctl restart pihole-FTL # nella prova il nuovo livello di privacy valeva solo dal riavvio # liste: quelle predefinite più i domini malevoli raccolti dal nodo pihole-FTL sqlite3 /etc/pihole/gravity.db \ "INSERT INTO adlist (address, enabled, comment) VALUES ('file:///etc/pihole/ioc-domini.txt', 1, 'IOC del nodo');" pihole -g # ricompila le liste

Sul nodo, dnscrypt-proxy ascolta anche sull'indirizzo della rete locale e pf lo apre soltanto a Pi-hole: è l'unica eccezione alla regola "nessun servizio sull'host".

# dnscrypt-proxy.toml del nodo listen_addresses = ['127.0.0.1:53', '10.0.0.1:53', '192.168.178.3:53'] # /etc/pf.conf del nodo, nella sezione dell'host pass in quick on $ext_if proto { tcp, udp } from 192.168.1.4 to $nodo port domain keep state

I comandi sono stati provati su Pi-hole 6.4.3 (FTL 6.7.1) in Debian 13: le cinque chiavi vengono accettate, l'interfaccia web ascolta soltanto su 127.0.0.1:8080, la lista locale file:// entra nella compilazione e i suoi domini rispondono 0.0.0.0. Con versioni diverse vanno confrontati con la documentazione. Il file ioc-domini.txt arriva dal nodo con iop, come gli altri indicatori (vedi Integrare iop).

Impedire di scavalcarlo

Il router distribuisce via DHCP l'indirizzo di Pi-hole come unico DNS. Ma un dispositivo può ignorarlo, quindi il router deve anche dirottare e bloccare.

# sul router (pf) pihole = "192.168.1.4" nodo = "192.168.178.3" table <doh> persist file "/etc/pf.doh" # indirizzi dei server DoH noti: lista da aggiornare table <dns_ammessi> const { $pihole } # le negazioni si scrivono con una tabella # ogni richiesta DNS che non viene da Pi-hole finisce comunque a Pi-hole rdr on $lan proto { tcp, udp } from ! <dns_ammessi> to any port 53 -> $pihole port 53 block in quick on $lan proto { tcp, udp } from ! <dns_ammessi> to any port 853 # DNS su TLS block in quick on $lan proto tcp from any to <doh> port 443 # DNS su HTTPS # tra la rete di casa e la zona del nodo passa soltanto il DNS di Pi-hole pass in quick on $lan proto { tcp, udp } from $pihole to $nodo port 53 keep state

Verificare e usare

Che cosaComandoRisultato atteso
Un dominio in listadrill doubleclick.net @192.168.1.40.0.0.0
Un dominio normaledrill freebsd.org @192.168.1.4Risposta, passata dal nodo
Il dirottamentoDa un dispositivo: drill doubleclick.net @8.8.8.80.0.0.0: ha risposto Pi-hole
Niente DNS in chiaro verso l'esternotcpdump -ni $wan port 53 sul routerNessun pacchetto
Interfaccia webnmap -p 80,443,8080 192.168.1.4Porte chiuse
Perché un nome è bloccatopihole -q nome.exampleLa lista che lo contiene

Se Pi-hole è usato come sensore, il suo registro entra nel collettore del nodo: un dispositivo che in un'ora chiede centinaia di nomi inesistenti merita un avviso.

# i dieci dispositivi con più risposte NXDOMAIN nell'ultima ora (reply_type 2 = NXDOMAIN: verificato su Pi-hole 6.4.3, FTL 6.7.1) pihole-FTL sqlite3 /etc/pihole/pihole-FTL.db \ "SELECT client, COUNT(*) FROM queries WHERE timestamp > strftime('%s','now','-1 hour') AND reply_type = 2 GROUP BY client ORDER BY 2 DESC LIMIT 10;"

Se Pi-hole cade, in casa non si risolve più nulla. La soluzione non è rimettere il DNS del provider, e nemmeno aprire a tutta la casa il dnscrypt-proxy del nodo, che bucherebbe la zona separata: è distribuire come secondo DNS un secondo Pi-hole, configurato allo stesso modo.

Mitigazioni aggiuntive

Per le basi (didattica): Il collegamento dati, La sicurezza di rete.
Per i motivi (discussione): Firewall, Attacchi con accesso fisico, Honeypot.

Ciò che non si è potuto togliere dalla macchina si compensa attorno a essa. Il principio è semplice: dare per scontato che sotto il sistema operativo ci sia qualcosa che il sistema operativo non vede, e fare in modo che quel qualcosa non abbia dove andare né molto da prendere.

Rete: filtrare fuori dalla macchina

Il traffico generato da Intel ME non passa dal sistema operativo, quindi pf non lo vede e non lo può bloccare. Il filtro va messo dove ME non arriva.

  • Una scheda di rete che ME non controlla: la gestione remota di Intel usa la scheda di rete integrata. Collegando il nodo attraverso una scheda diversa, USB o su slot, e lasciando la porta integrata vuota e disattivata nel firmware, si toglie a ME la sua via d'uscita abituale.
  • Default-deny sul router: dal nodo si esce solo verso i propri bridge o guard di Tor. Perché il corridoio resti così stretto, nel Lieve anche dnscrypt-proxy passa da Tor (proxy = 'socks5://10.0.0.2:59353', force_tcp = true e, nella sezione [anonymized_dns], routes = []: provato, con i relay attivi ogni richiesta attraverso Tor va in timeout, e Tor nasconde già l'indirizzo; vedi Proxy), e I2P è la prima jail a cui si rinuncia: i suoi peer sono indirizzi qualunque, e ospitarlo allarga il corridoio a tutta Internet. Tutto il resto viene scartato e registrato dal router, non dal nodo. È il principio di corridor descritto in Buone pratiche da Whonix, applicato un passo più fuori.
  • Zona separata: il nodo sta in una rete propria (DMZ o VLAN) da cui non vede gli altri dispositivi di casa.
  • Osservare da fuori: ogni tanto si registra sul router il traffico del nodo con Tor e I2P fermi (e quindi anche il DNS, che passa da Tor). Non deve uscire nulla. Qualunque pacchetto in quel momento viene da sotto il sistema operativo.
# sul router (qui pf), con il nodo in 192.168.178.3: esce solo verso i bridge, il resto è una fuga nodo = "192.168.178.3" table <ponti> persist file "/etc/pf.ponti" # i propri bridge, oppure i guard letti dal file state di Tor pass in quick on $dmz proto tcp from $nodo to <ponti> modulate state # solo se il nodo ospita I2P: il corridoio si allarga a qualunque indirizzo #pass in quick on $dmz proto { tcp, udp } from $nodo to any keep state block in log quick on $dmz from $nodo to any # prova a servizi fermi: per un'ora non deve comparire alcun pacchetto tcpdump -ni $dmz host 192.168.178.3 and not arp

Con UseBridges 0 l'elenco <ponti> contiene il guard scelto da Tor, che si legge nel file state della jail tor e cambia di rado: quando cambia, il router lo segnala come fuga, ed è il momento di aggiornare l'elenco a mano.

Ruolo: che cosa affidare a un Lieve

Un nodo Lieve non custodisce i segreti che non ci si può permettere di perdere. Si presta invece molto bene ad alcuni ruoli.

  • Nodo ridondato: una copia dei servizi su un'altra rete, tenuta allineata con iop. Se cade, non ha nulla che il nodo principale non abbia.
  • Esca: un honeypot su hardware di recupero è il suo uso ideale (vedi Honeypot).
  • Destinazione di backup: riceve solo copie già cifrate altrove, di cui non possiede la chiave.
  • Filtro DNS della rete di casa: Pi-hole, che richiede GNU/Linux e quindi non può stare sul nodo Classico.
  • Banco di prova: per provare aggiornamenti e configurazioni prima di portarli sul Classico.

Le chiavi di lungo periodo (firma degli indicatori, identità degli onion service principali, chiavi dei backup) restano su un Classico o fuori linea.

Accesso fisico

  • Cifratura del disco e spegnimento vero: con la batteria inserita la RAM resta alimentata più a lungo. In caso di emergenza si staccano alimentatore e batteria (vedi Attacchi con accesso fisico).
  • Console: la sessione sulla console si chiude sempre, e la shell si disconnette da sola dopo qualche minuto di inattività.
  • Sigilli e fotografie valgono come per il Classico, e contano di più: un portatile si apre in pochi minuti.

Verifica finale

Oltre alla verifica finale del Classico, un Lieve deve superare questi controlli.

Che cosaComeRisultato atteso
Nessuna radiopciconf -lv | grep -i -E "wireless|bluetooth" e usbconfigNessun dispositivo
Nessun sensoreusbconfig, cat /dev/sndstatNessuna webcam, nessun ingresso audio
Microcodice caricatodmesg | grep -i microcodeVersione aggiornata all'avvio
Mitigazioni attivesysctl vm.pmap.pti hw.ibrs_active hw.mds_disable_stateAttive
Nessuna sospensioneChiudere il coperchioIl nodo resta raggiungibile
AutonomiaStaccare l'alimentatoreIl nodo resta acceso, e parte un avviso
Silenzio a servizi fermitcpdump sul router per un'oraNessun pacchetto dal nodo
Solo con Proxmox: l'host non esce da soloSul router, per dieci minuti: tcpdump -ni <porta> host 192.168.178.2Soltanto connessioni verso 192.168.178.3, porta 59353
Solo con Proxmox: rete di gestionepve-firewall compile | grep "add PVEFW-0-management"Soltanto il computer di amministrazione
Solo con Proxmox: secondo fattoreAccesso all'interfaccia web con la sola passwordRichiesta del codice TOTP
Solo con Proxmox: anti-falsificazionepve-firewall compile | grep -c tap100i0Più di zero regole, tra cui il filtro sul MAC
Solo con Proxmox: kernel e serviziuname -r, systemctl is-enabled chrony pve-daily-update.timer-pve, e disabled
Solo con Proxmox: interfaccia webDa un altro computer: nmap -p 8006 192.168.178.2Porta chiusa
Solo con Proxmox: rete internaDa un ospite su vmbr1, a nodo Tutanum spentoNessuna uscita
Avvio bloccatoProvare ad avviare da USBRifiutato, o richiesta della password del firmware

Quando un Lieve diventa un Classico

La distinzione non è per sempre. Un nodo Lieve si può considerare un Classico quando il firmware del produttore è stato sostituito con Libreboot o Coreboot, il sottosistema di gestione è stato neutralizzato e la catena di avvio si può verificare a ogni accensione. Finché una di queste condizioni manca, restano in vigore tutte le mitigazioni di questa pagina.