Torna agli scritti

Il log non è una cosa sola

Operazioni, concessioni e accessi hanno volume, retention e criticità diversi. E non tutti i guasti della traccia devono avere la stessa conseguenza.

Quasi ogni sistema ha una tabella audit_log con una colonna JSONB in cui ci sta tutto: login, errori, modifiche di configurazione, concessioni di role, aperture di documenti. Funziona finché qualcuno non fa una vera domanda di governance, e a quel punto interrogarla diventa archeologia.

Divido la traccia in tre piani, perché rispondono a domande diverse:

PianoDomandaEsempi
OperativoChe cosa è successo nel sistema?login, errori, configurazione, elaborazione
ConcessioneChi ha dato quale potere a chi, e quando?role, grant diretti, share, revoche
AccessoChi ha visto o consultato quale contenuto?apertura, download, stampa, consultazione di microdati

Ogni piano ha volume, retention, indici e criticità propri. La concessione ha poco volume e molto valore probatorio. L'accesso ha un volume alto e richiede il partizionamento per tempo. L'operativo è rumore utile. Mescolare tutto fa dipendere una traccia importante da un writer pensato per un altro scopo.

Stato non è evento

Una tabella user_role dice chi ha il role oggi. Non dice chi lo aveva tre mesi fa. Per questo esiste una tabella append-only di eventi. Servono entrambe, e una non sostituisce l'altra.

Una tabella di stato dice chi ha accesso. Solo la traccia degli eventi dice come ci è arrivato.

Asimmetria dei guasti

Non tutti i guasti dell'audit devono avere la stessa conseguenza:

NaturaSe la traccia falliscePerché
operativabest-effortil fatto può essere già avvenuto
concessionefail-closedun potere invisibile è inaccettabile
accesso sensibilefail-closedun accesso senza evidenza rompe l'accountability

Questa è una decisione regolatoria e operativa, e ha un costo: il fail-closed mette lo storage della traccia nel percorso critico. Prima di adottarlo, misura p50 e p99 dell'insert, il tasso di errore causato dalla traccia, la crescita giornaliera, il bloat e il comportamento del pool. La policy non può esigere una disponibilità che l'infrastruttura non è stata dimensionata per sostenere.

Concessione ed evento nella stessa transazione

BEGIN;
  INSERT INTO grant_event (...);
  INSERT INTO user_role (...);
  UPDATE app_user
     SET permission_version = permission_version + 1
   WHERE id = ...;
COMMIT;

Se una qualsiasi fase fallisce, rollback di tutto. Un evento che afferma una concessione inesistente è grave quanto una concessione senza evento. Senza una Unit of Work globale, basta un helper ristretto:

security_transaction(operation):
    begin
    operation(shared_session)
    commit_or_rollback

E la regola che dà i denti all'helper: qualsiasi call site di concessione fuori da esso è un bug strutturale, e la CI lo cerca.

Registrare prima di servire

Quando l'accesso sensibile è fail-closed, l'ordine è autorizzare, registrare l'evento e solo allora serializzare e restituire il contenuto. L'errore residuo è registrare un accesso che non è arrivato all'utente perché qualcosa è fallito dopo. Questo errore “per eccesso” è verificabile. L'inverso, contenuto consegnato senza evento, è esattamente ciò che la policy vuole impedire.

Registra la via

Quando qualcuno apre una risorsa, conviene salvare per quale strada l'autorizzazione è stata soddisfatta:

access_via = own_partition | share | all_partitions | platform_bypass | legacy

Se domani lo share viene revocato, la traccia continua a spiegare l'accesso di oggi senza dover ricostruire lo stato passato. Quando più di una via è vera nello stesso momento, definisci una precedenza e mantienila, oppure registrale tutte. L'importante è non lasciare ambiguità storiche.

Cosa non registrare

Il polling dell'interfaccia non è accesso. Elencare metadati e aprire contenuti sono eventi diversi, e registrare ogni refresh automatico genera un volume enorme con poco valore probatorio. Definisci che cosa conta come accesso prima di accendere il writer.

E ricorda che la traccia stessa è un dato personale: identità, IP, orario, oggetto consultato, pattern di utilizzo. Ha bisogno di controllo degli accessi, di una retention esplicita e, negli ambienti più esigenti, di un utente applicativo senza UPDATE né DELETE su di essa.