@ :~/lab/kochab-infrastruttura-ai-guardian$
← cd ~/lab

./run kochab-infrastruttura-ai-guardian

Kochab: costruire fiducia in codice — la stella che non cancella mai

Diario tecnico di Kochab: agente outbound-only in Go, audit di sicurezza mappato CIS/NIST, task firmati Ed25519 e una regola — non cancella mai — garantita in quattro modi indipendenti.

status
draft
project
kochab
updated
2026-07-07
source
github.com/vcnngr/kochab-agent
tags
#devops#golang#audit sicurezza#cis nist#ed25519#postgresql
generate cover # alt: Costellazione dove ogni stella è un server, la stella polare in cima veglia sulle altre, alcune serene alcune in allerta

Kochab è la stella in punta al Piccolo Carro — quella che indica il nord, ferma mentre tutto ruota. Il nome è una promessa: un punto fermo che veglia sui tuoi server. Ma un tool che gira con privilegi su tutte le tue macchine è utile solo se ti fidi. Questo diario parla di come la fiducia si scrive in codice.

Il problema

Chi gestisce una manciata di server vive in una zona scomoda: troppo piccolo per i tool enterprise, troppo grande per controllare a mano. La sicurezza è la prima a scivolare — chiavi SSH permissive, firewall lasciato aperto “temporaneamente” due anni fa, DKIM/SPF/DMARC mai sistemati.

Architettura: tre pezzi, una separazione netta

  • Agente (Go, un binario statico da ~9.7 MB, .deb da 4.6 MB): gira su ogni host sotto systemd. Solo in uscita — nessun DB locale, nessuna UI, nessuna porta in ascolto. Il go.mod ha una sola dipendenza diretta. Superficie d’attacco minima.
  • Platform (Go): API + worker fuori banda. Possiede l’inventario, il motore di audit, la firma dei task. PostgreSQL è l’unica fonte di verità.
  • App: PWA React 19 / Vite 8 / Tailwind 4 che disegna la fleet come costellazione.

Perché l’agente chiama casa, e non il contrario

Ho scartato SSH-exec e agenti in ascolto. L’agente fa long-polling HTTP, ispirato ai tunnel Cloudflare — “il demone chiama casa, mai il contrario”:

  • GET /v1/tasks: 204 = niente da fare, riconnetti; 200 = un task; 410 Gone = nodo dismesso, l’agente smette di pollare per sempre.
  • Timeout client 90s, apposta più lungo dei 60s di attesa lato server.
  • TLS minimo 1.3, autenticazione richiesta via HMAC-SHA256 sul body.
  • Loop poll → verifica firma → esegui → riporta, backoff esponenziale 2s→60s.

I task sono firmati Ed25519 dalla platform e verificati dall’agente prima di eseguire. Il messaggio canonico è identico ai due lati:

// "taskID|taskType|hex(sha256(payload))|RFC3339(timestamp)"
msg := fmt.Sprintf("%s|%s|%x|%s", taskID, taskType,
    sha256.Sum256(payload), timestamp.UTC().Format(time.RFC3339))
sig := ed25519.Sign(privKey, msg)

Sottigliezza che mi è costata un bug: il timestamp firmato deve coincidere al secondo con il created_at salvato, o la verifica salta.

Il motore di audit

Dieci controlli nel seed iniziale, ognuno mappato a CIS e NIST: SSH solo a chiave (CIS-5.3.4 / AC-7), firewall default-deny, TLS ≥1.2, permessi su shadow, fail2ban, servizi non esposti, DKIM/SPF/DMARC, backup presente, Docker senza host-network. Ogni regola è un file Go che si auto-registra: una regola nuova = un file nuovo, nessuno switch cablato.

type CheckFunc func(ctx context.Context) (passed bool, context map[string]any, err error)

Le precondizioni (binario assente) sono modellate come pass-con-contesto, non errori, così il report resta sensato su host eterogenei. Il check SSH è il più raffinato: concatena sshd_config + i .d/*.conf nell’ordine effettivo, sonda ogni porta configurata per capire se è esposta pubblicamente, e passa quei segnali al calcolo di severità.

Onestà da diario: nel codice spedito non c’è ancora un LLM. È tutto deterministico. L’AI è progettata per stare fuori dal loop di esecuzione — genererà nuove regole di audit che vengono poi “cristallizzate” nella stessa forma deterministica e approvate da un umano. Il ragionamento: “90% di accuratezza per passo = 59% di successo su 5 passi”. Quindi la logica sta nel codice, l’LLM al massimo scrive regole, mai tocca un host vivo.

”Non cancella mai” — garantito in quattro modi

Questa è la parte di cui vado più fiero, perché è fiducia resa meccanica:

  1. I nodi sono solo soft-deleted. DELETE /v1/nodes/{id} valida la ragione contro un’allowlist e fa un UPDATE removed_at = now(), mai un DELETE SQL.
  2. Una guardia CI impone il filtro. Uno script fallisce la build se una query sui nodi non ha removed_at IS NULL. Esiste perché quattro veri bug “nodo zombie” (un nodo rimosso che si rianimava) erano sfuggiti.
  3. L’audit trail è fisicamente immutabile, in Postgres, non nel codice:
CREATE FUNCTION prevent_audit_trail_mutation() RETURNS trigger AS $$
BEGIN
  RAISE EXCEPTION 'audit_trail is append-only: % operations are forbidden', TG_OP;
END; $$ LANGUAGE plpgsql;
CREATE TRIGGER audit_trail_immutable_delete BEFORE DELETE ON audit_trail
  FOR EACH ROW EXECUTE FUNCTION prevent_audit_trail_mutation();
  1. I finding si risolvono, non si cancellano. E i fix non vengono mai eseguiti dal sistema: sono resi come shell copia-incolla per un umano.

C’è anche un’asimmetria che mi piace: il fix “disabilita password SSH” è fail-closed — resta nascosto se un preflight non prova che esiste già una chiave funzionante (per non chiuderti fuori). Ogni altra regola è fail-open, così una sonda ballerina non nasconde mai un fix legittimo.

La costellazione

La fleet è un campo di stelle. Due funzioni pure, testate: una piazza i nodi in modo radiale deterministico, l’altra mappa (stato, severità, ultimo battito) → stato visivo, dove la raggiungibilità vince sulla severità (un nodo irraggiungibile è offline, punto). Accessibilità di serie: target da 44×44px, aria-label in italiano, attivazione con Enter/Spazio, animazione disattivata con prefers-reduced-motion. E una vista di trasparenza che mostra all’operatore esattamente cosa l’agente ha trasmesso — perché raccogliamo solo metadati, mai contenuti di log.

Gotcha onesti

  • Drift README/realtà: la doc dice “Watermill per gli eventi”, il codice fa Postgres LISTEN/NOTIFY + polling TanStack. Watermill è rimandato post-MVP. Meglio dirlo.
  • Regole in doppia rappresentazione (Go + seed SQL) create un rischio di disallineamento, mitigato solo da un grep in CI. Tassa di manutenzione accettata per tenere i metadati CIS nel DB.

Come va

Corro verso l’MVP. I tre pezzi compilano, le immagini sono a release candidate, e lo testo sulle mie macchine reali — il modo migliore per capire se una stella-guardiana funziona è farla vegliare su qualcosa che, se salta, mi rovina la giornata.

Codice: github.com/vcnngr/kochab-agent · install via get.kochab.ai. Agente Apache-2.0, platform BSL.