Immagina una cooperativa che ha messo su un sistema di brain AI per i suoi dipendenti. Ogni persona ha il suo container Docker, il suo assistente AI, i suoi dati. Funziona. Ma a un certo punto qualcuno dice: "e se il bot potesse mandare email?" E si apre il vaso di Pandora.
Il punto non è far mandare email a un container. Quello è facile: installi smtplib, metti le credenziali, e via. Il punto è che se un container può mandare email a chiunque, hai un problema. Un prompt injection, un utente curioso, un bug — e il tuo bot manda 500 email al CEO di un cliente con scritto "ciao sono un'intelligenza artificiale e ho letto i vostri dati finanziari".
La soluzione è un container gateway che fa da unico punto di uscita verso il mondo esterno, con regole, whitelist, audit log, e la possibilità di dire "no" a una richiesta prima che faccia danni.
Docker ha tre tipi di rete. La bridge è quella di default: ogni container può parlare con gli altri e con internet. Comoda per sviluppo, pericolosa per produzione. La host elimina l'isolamento di rete. La overlay serve per cluster multi-nodo.
Quella che conta è la bridge con configurazione custom. Crei una rete con subnet specifica — 172.20.0.0/24 — e dai IP statici a ogni container. Questo ti permette di scrivere regole firewall precise: "il container .13 non può raggiungere la porta 587 su internet, ma può raggiungere la porta 8025 sul container .6".
La mappa IP di EmiBrain:
172.20.0.2 = nginx (reverse proxy web)
172.20.0.3 = admin (WebSSH, file manager)
172.20.0.4 = php (Laravel app)
172.20.0.6 = gateway (firewall applicativo)
172.20.0.10-16 = workspace container (uno per utente)
I workspace stanno nel range .10-.20 apposta: per poter scrivere regole iptables che bloccano tutto il range in un colpo.
Docker ha un DNS interno automatico. Due container sulla stessa rete si raggiungono per nome. emibrain-ws-franzini può fare curl http://emibrain-gateway:8025/health e Docker risolve il nome all'IP senza configurare niente.
Se domani sposti il gateway su un altro IP, il DNS si aggiorna automaticamente. Zero configurazione nei client. Il DNS funziona solo tra container sulla stessa rete — un altro layer di isolamento.
Il gateway è un container Python minimalista. Un http.server della standard library con pyyaml per la configurazione. Fa tre cose:
Espone endpoint HTTP sulla rete interna. L'endpoint principale è POST /email/send — accetta JSON con slug del brain, destinatario, oggetto e corpo. Ci sono anche GET /health e GET /stats.
Valida ogni richiesta contro una whitelist. Un file YAML montato read-only, nella cartella infrastruttura — non nei brain degli utenti. Ogni brain ha una lista di destinatari consentiti. Destinatario non in lista? 403. Brain non in lista? 403. Lista vuota? 403. Default: blocca tutto.
Logga ogni richiesta con audit trail. Timestamp, brain, destinatario, risultato. Docker raccoglie i log automaticamente.
Il gateway NON è esposto su internet. Nessun port mapping nel docker-compose. È raggiungibile solo dalla rete Docker interna.
Il gateway da solo non basta. Se un workspace raggiunge smtp.purelymail.com:587 direttamente, può bypassare il gateway con smtplib. Il gateway è un guardrail applicativo, non un muro.
Il muro sono le iptables. Regole firewall del kernel Linux a livello di pacchetto — molto più in basso di qualsiasi applicazione:
iptables -I FORWARD -s 172.20.0.13 -p tcp --dport 587 -j DROP
Qualsiasi pacchetto TCP dall'IP .13 verso porta 587: buttato via. Il container non riceve un errore — riceve un timeout. Non c'è modo di aggirare questa regola dall'interno del container.
Bloccate le porte 25, 465 e 587 per il range workspace (.10-.20). Admin (.3) e gateway (.6) non bloccati — il gateway deve mandare email per conto dei workspace.
Doppia barriera: anche se un workspace provasse SMTP diretto, il pacchetto non arriverebbe mai. L'unica strada è il gateway.
Sono complementari, non alternativi.
Le iptables operano al livello 3-4 OSI (IP e TCP). Sanno dire "blocca questa porta", ma non sanno se un'email va a giobi@giobi.com o a hacker@evil.com. Bloccano intere connessioni.
Il gateway opera al livello 7 (HTTP). Legge il corpo della richiesta, capisce il destinatario, controlla la whitelist. Ma se qualcuno bypassa il gateway, non può fare niente.
Insieme: le iptables tolgono la possibilità di bypassare. Il gateway aggiunge la logica di business. Due livelli molto più robusti di uno solo.
Il sidecar mette un proxy di controllo dentro ogni container. N copie della logica, N configurazioni da allineare, N punti di rottura.
Il gateway centralizzato: un singolo container, una configurazione, un punto da monitorare. Se cade, nessuno manda email — ma restart: unless-stopped lo riavvia in secondi.
Per 5-50 utenti, il centralizzato è la scelta giusta. Più semplice, più auditabile, single point of failure accettabile.
La whitelist NON sta nei brain. Sta in /var/emibrain/config/, accessibile solo a root.
volumes:
- /var/emibrain/config:/etc/gateway:ro
Il :ro è read-only. Anche bucando il container gateway, non si può modificare la whitelist. I workspace non hanno nemmeno il mount.
Chi esegue il codice non può modificare le regole. Chi definisce le regole non esegue il codice. Il gateway applica senza poter cambiare.
restart: unless-stopped — Docker riavvia automaticamente dopo crash, reboot, kernel update.
/health — monitoring esterno (Uptime Kuma ogni 30s). Restituisce uptime.
/stats — contatori: email inviate, bloccate, errori, breakdown per brain. Si azzera a ogni riavvio.
Il pattern scala. Domani il gateway potrebbe gestire:
API esterne: workspace che interroga OpenAI passa dal gateway. Rate limiting, whitelist endpoint, audit.
Webhook in uscita: notifiche a Slack, CRM — il gateway valida la destinazione.
Stessa architettura: container centralizzato, YAML read-only, iptables blocco diretto, gateway unica via d'uscita. Cambiano solo gli endpoint.
Scheduler sul host controlla le caselle email ogni 5 minuti via IMAP. Trova email nuova per Paola (brain franzini). Lancia docker exec nel container. Claude legge il brain, compone la risposta, chiama il gateway.
Il gateway riceve la richiesta. Whitelist: franzini può mandare solo a certi indirizzi. Destinatario OK? Carica credenziali SMTP dal .env. Compone messaggio con header threading. Invia. Logga. Restituisce 200.
Se Claude avesse provato un indirizzo non autorizzato: 403. Se avesse provato smtplib diretto: iptables drop.
Tre layer — iptables, gateway, whitelist — per agenti AI che mandano email in autonomia senza rischi. Non è paranoia: è il minimo quando dai a un LLM la capacità di comunicare col mondo esterno a nome di qualcuno.