Introduzione: Il Momento della Fenice#
Nel precedente episodio di questo diario tecnico, ho documentato il fallimento drammatico del tentativo di trasformare DevPod in una enclave Zero Trust. Il conflitto fondamentale tra l’architettura “Convenience-First” di DevPod e i miei requisiti di sicurezza ha portato a una conclusione inevitabile: dovevo abbandonare completamente lo strumento.
Tuttavia, come ogni ingegnere sa, il fallimento è spesso la madre dell’innovazione. Le ceneri di DevPod sono diventate il terreno fertile per qualcosa di nuovo: TazPod, una CLI personalizzata in Go progettata da zero per affrontare le sfide di sicurezza specifiche che DevPod non poteva gestire.
Questa è la storia di come ho costruito TazPod dalla v1.0 alla v9.9, trasformandolo dalla fragilità degli script Bash alla robustezza di Go, dai mount globali all’isolamento dei namespace, e dai compromessi di convenienza alla vera sicurezza Zero Trust.
Fase 1: Le Fondamenta in Go - TazPod v1.0#
La prima decisione tecnica è stata radicale: abbandonare l’idea di un ambiente che si autoconfigura “magicamente” via SSH. Avevo bisogno di determinismo.
Il Ragionamento: Perché Go?#
Dopo l’incubo degli script Bash in DevPod, avevo bisogno di un linguaggio con:
- Tipizzazione forte per prevenire errori a runtime.
- Eccellente integrazione con Docker attraverso l’SDK.
- Compilazione cross-platform per la futura portabilità.
- Gestione robusta degli errori senza la fragilità delle “trap” di Bash.
Go offre un vantaggio critico per un tool di questo tipo: l’accesso diretto alle syscall del sistema operativo e la capacità di compilare in un singolo binario statico.
L’Architettura: Design Command-First#
Ho strutturato TazPod attorno a un set centrale di comandi, gestiti da uno switch principale nel main.go. Questo approccio trasforma il container in un “Demone di Sviluppo”. È lì, in attesa (sleep infinity), ma inerte. La magia avviene quando ci entriamo.
// cmd/tazpod/main.go (Snippet della funzione up)
func up() {
// ... caricamento configurazione ...
runCmd("docker", "run", "-d",
"--name", cfg.ContainerName,
"--privileged", // Necessario per montare i loop device
"--network", "host",
"-e", "DISPLAY="+display,
"-v", cwd+":/workspace", // Mount del progetto corrente
"-w", "/workspace",
cfg.Image,
"sleep", "infinity") // Il container resta vivo in attesa
}La prima implementazione era essenzialmente una traduzione diretta degli script Bash. Funzionava, ma soffriva ancora dello stesso problema di mount globale che affliggeva DevPod. Chiunque avesse accesso docker exec poteva vedere i segreti.
Fase 2: La Svolta di Sicurezza - TazPod v2.0 (Ghost Edition)#
Durante una revisione della sicurezza il 17 gennaio, ho identificato una falla critica: se sbloccavo il vault e un altro utente accedeva al container, poteva leggere tutti i segreti. La soluzione è arrivata da una fonte inaspettata: i Linux Mount Namespaces.
Il Concetto: “Ghost Mode”#
L’idea era rivoluzionaria: invece di montare il vault globalmente, creare un namespace isolato dove solo la sessione corrente potesse vedere i segreti montati.
In Linux, i mount point sono globali per namespace. Se creo un nuovo namespace di mount e monto un disco al suo interno, quel disco esiste solo per i processi che vivono in quel namespace. Per il processo padre (e per l’host), quel mount point è semplicemente una directory vuota.
L’Implementazione: Magia di unshare#
La chiave è stata usare unshare -m per creare un nuovo namespace di mount. Ecco cosa succede “sotto il cofano” quando un utente digita la password del vault:
- Trigger: L’utente lancia
tazpod pull. - Fork & Unshare: Il binario Go esegue se stesso con privilegi elevati usando
unshare:sudo unshare --mount --propagation private /usr/local/bin/tazpod internal-ghost - Enclave Creation: Il nuovo processo
internal-ghostnasce in un universo parallelo di mount. - Decryption: All’interno di questo universo, usiamo
cryptsetupper aprire il filevault.img(montato via loop device) e montarlo su/home/tazpod/secrets. - Drop Privileges: Una volta montato il disco, il processo “degrada” i suoi privilegi da root all’utente
tazpode lancia una shell Bash.
Il Risultato:
- Tu (nella ghost shell): Vedi i segreti, usi kubectl, lavori normalmente.
- Intrusi (in altre shell): Vedono una directory
~/secretsvuota. - Exit: Quando esci, il namespace sparisce, portando con sé il mount.
Fase 3: La Rivoluzione dell’IDE - TazPod v3.0#
Con DevPod andato, ho perso l’esperienza integrata di VS Code. Ho deciso di abbracciare un workflow puramente da terminale con Neovim (configurazione LazyVim).
L’Integrazione LazyVim#
Ho investito tempo significativo per perfezionare il setup di Neovim direttamente nell’immagine Docker base. Volevo che l’IDE fosse pronto immediatamente, senza dover attendere il download dei plugin al primo avvio.
# Installazione LazyVim e sync plugin headless
RUN git clone https://github.com/LazyVim/LazyVim ~/.config/nvim && \
nvim --headless "+Lazy! sync" +qa && \
nvim --headless "+MasonInstall all" +qaIl Risultato: Un ambiente di sviluppo completo pronto in secondi, con Tree-sitter, LSP e tutti i plugin pre-compilati.
Fase 4: La Battaglia per la Persistenza di Infisical#
Risolto l’isolamento del filesystem, ho dovuto affrontare la gestione dell’identità. Uso Infisical per gestire i segreti centralizzati. Tuttavia, Infisical ha bisogno di salvare un token di sessione locale (solitamente in ~/.infisical).
Se il container è effimero, ad ogni riavvio dovrei fare login. Inaccettabile. Se salvo il token su un volume Docker, è esposto in chiaro sull’host. Inaccettabile.
L’Investigazione: Il Bug “Cannibale”#
L’idea era semplice: spostare la cartella .infisical dentro il vault criptato e usare un bind-mount per farla apparire nella home utente solo quando il vault è aperto.
Durante l’implementazione in Go, ho incontrato un bug critico che ho soprannominato “Il Cannibale”. La funzione di migrazione, pensata per spostare i vecchi token nel vault, aveva un difetto logico che portava alla cancellazione del contenuto se i percorsi coincidevano.
La Soluzione: Il Bridge Blindato#
Ho riscritto la logica implementando controlli rigorosi:
- Check preliminare: Verifico se il mount è già attivo leggendo
/proc/mounts. - Doppio Bridge: Monto sia la configurazione (
.infisical) che il keyring di sistema (infisical-keyring) dentro il vault (.infisical-vaulte.infisical-keyring). - Ownership Recursiva: Un problema ricorrente era che i file creati durante il mount (da root) non erano leggibili dall’utente. Ho aggiunto un
chown -R tazpod:tazpodforzato su tutta la struttura.tazpodad ogni operazione di init o mount.
Ora, la sessione sopravvive ai riavvii, ma esiste fisicamente solo all’interno del file criptato vault.img.
Fase 5: Da Hack a Prodotto (TazPod v9.9)#
A questo punto, avevo un sistema funzionante ma grezzo. Per renderlo un vero strumento “Zero Trust” utilizzabile da altri, serviva una pulizia profonda e una standardizzazione.
Standardizzazione e “Smart Init”#
Ho introdotto il comando tazpod init. Invece di dover copiare manualmente file di configurazione, la CLI ora analizza la directory corrente e genera:
- Una cartella nascosta
.tazpod/. - Un
config.yamlpre-compilato, permettendo di scegliere il “verticale” (base, k8s, gemini) tramite un argomento (es.tazpod init gemini). - Un template di
secrets.ymlper mappare le variabili d’ambiente di Infisical. - Un
.gitignoreche esclude automaticamente il vault e la memoria locale dell’AI (montata in./.geminiper persistere i ricordi del progetto).
Il Problema della Collisione dei Nomi#
Lanciando più progetti TazPod contemporaneamente, ho notato che Docker andava in conflitto sui nomi dei container (tazpod-lab). Ho implementato una logica di naming dinamico in Go nella versione v9.9:
cwd, _ := os.Getwd()
dirName := filepath.Base(cwd)
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
containerName := fmt.Sprintf("tazpod-%s-%d", dirName, rng.Intn(9000000)+1000000)Ora ogni progetto ha un’identità unica, permettendo di lavorare su più cluster o clienti in parallelo senza sovrapposizioni.
Riflessioni Post-Sviluppo#
Il passaggio da DevPod a TazPod è stato un esercizio di sottrazione. Ho rimosso l’interfaccia grafica, ho rimosso l’agente di sincronizzazione, ho rimosso l’astrazione SSH gestita.
In cambio, ho ottenuto:
- Sicurezza Verificabile: So esattamente dove risiede ogni byte di dati sensibili (nella RAM del processo Ghost).
- Portabilità Totale: Il progetto è autocontenuto. Basta avere Docker e il binario TazPod.
- Velocità: Senza overhead di agenti, l’avvio della shell è istantaneo una volta scaricata l’immagine.
Il Progetto su GitHub#
Ho deciso di rilasciare TazPod come progetto Open Source sotto licenza MIT. Non è solo uno script personale, ma un framework completo per chi, come me, vive nel terminale e non vuole compromessi sulla sicurezza.
L’installazione è ora ridotta a una singola riga:
curl -sSL https://raw.githubusercontent.com/tazzo/tazpod/master/scripts/install.sh | bashPer maggiori dettagli tecnici e per consultare la documentazione completa del progetto, vi invito a visitare il repository ufficiale su GitHub: https://github.com/tazzo/tazpod.
Il prossimo passo? Utilizzare TazPod per completare il refactoring Terraform del cluster TazLab, sapendo che le chiavi di accesso sono finalmente al sicuro.


