Un modulo pubblico che non parla mai con la rete privata produce comunque dati che la rete privata deve giudicare. Il tunnel cambia solo chi chiama chi. Tutto ciò che segue il download è una decisione di importazione e appartiene al lato privato. Questa rassegna segue un invio dal browser fino a un record interno consolidato.

Parti da dove atterra l'invio

Il browser pubblica verso un endpoint pubblico, che scrive una riga in un archivio di ingresso controllato e non restituisce altro che una conferma di ricezione. Quell'archivio è il punto di consegna e dovrebbe essere l'unica cosa in cui il livello pubblico può scrivere. Conserva lì il payload grezzo senza modifiche, insieme all'ora di arrivo e a un identificatore di record stabile.

Poi un worker privato estrae le righe in attesa attraverso il tunnel e decide cosa fare con ciascuna. Opera sotto la propria identità, che è l'argomento della sezione seguente.

curl --silent --fail \
  --cert /etc/importer/client.crt --key /etc/importer/client.key \
  "https://intake.corp.internal/api/pending?limit=20" \
  -o /var/lib/importer/pending.json

Dai all'importatore solo l'autorità che gli serve

Il processo web pubblico e l'identità che scrive nell'applicazione interna dovrebbero essere due principal distinti. Un server pubblico non ha bisogno di amministrazione interna solo perché deve consegnare dati di modulo, e un unico account condiviso per entrambi i lati trasforma qualsiasi bug web in una scrittura interna. Rilascia al worker una credenziale propria, limitale all'unica operazione di importazione e registra quale principal ha eseguito ogni importazione. È questa separazione a rendere significativo il confine del tunnel invece che decorativo.

Limita anche da quale fonte il worker può leggere. Se l'implementazione usa una coda o un servizio di trasferimento, quel servizio conserva la propria autenticazione e politica di accesso, perché la VPN non autentica nulla dentro il payload.

Una funzione di breve durata non è esente dalla stessa revisione. Mentre gira, la sua posizione di rete e le sue credenziali conferiscono ancora autorità, e una vita più breve restringe soltanto la finestra. La durata non sostituisce la decisione su quali operazioni l'identità può eseguire.

Valida prima che qualcosa di interno cambi

I campi dei moduli pubblici sono testo controllato dall'attaccante finché non si dimostri il contrario, e le linee guida di OWASP sulla validazione dell'input includono i flussi del backend tra le fonti non attendibili proprio per questo motivo. Un trasporto cifrato dice che i byte sono arrivati intatti, non che descrivono una richiesta consentita. Analizza e valida prima di ogni passo privilegiato, così una riga difettosa non raggiunge mai un percorso di codice in grado di agire.

Definisci i campi ammessi, i loro formati, le loro dimensioni e le regole di business che devono valere. Una data può essere ben formata e comunque errata per l'operazione richiesta, e un payload che nomina un account interno o uno stato di approvazione non deve poter concedere a se stesso nessuno dei due.

Invia le righe rifiutate a un esito di revisione invece di indovinare una correzione o di inoltrarle a una shell o a un'interfaccia diretta al database. Registra l'identificatore del record e la regola fallita, non ogni campo digitato dal richiedente.

Crea un punto di nuovo tentativo su una sola operazione

Se il record submission-1042 viene estratto due volte perché la prima conferma è andata persa, il worker ha bisogno di un modo durevole per riconoscere il record di origine e il suo risultato consolidato. Una pianificazione che esegue il lavoro una volta non dimostra che ogni riga sia stata applicata una volta. La consegna duplicata è normale, quindi il progetto deve assorbirla.

Usa il meccanismo di transazione e di gestione dei duplicati dell'applicazione interna, e mantieni l'identificatore di origine, l'esito e la conferma in un'unica sequenza coerente. Conferma solo dopo che l'operazione prevista è consolidata o assegnata in modo durevole, e non eliminare mai la riga di ingresso solo perché i byte sono stati scaricati.

Poi dimostralo con tre file di prova: una riga valida, una riga malformata e lo stesso identificatore di origine controllato consegnato due volte. Ognuno passa per lo stesso endpoint che usa il worker.

for f in fixtures/valid-submission.json fixtures/bad-date.json; do
  curl --silent --fail --cert /etc/importer/client.crt \
    -X POST --data @"$f" https://intake.corp.internal/api/records
done
curl --silent --fail --cert /etc/importer/client.crt \
  -X POST --data @fixtures/valid-submission.json https://intake.corp.internal/api/records

Le prime due righe devono finire in stati interni diversi, e la terza deve restituire l'identificatore già consolidato per quel record di origine invece di crearne un secondo. Qualsiasi altra cosa significa che l'importazione non è ancora ripetibile.

Articoli correlati