Come Transferify converte ogni file in un container tutto suo
Spesso serve un file in un altro formato prima di inviarlo: una foto HEIC come JPG, un MOV come MP4, un DOCX come PDF. Ora Transferify fa questo passaggio per Lei, nella pagina di conversione e come modalità del pannello di invio, da cui poi invia i file convertiti.
Il convertitore è Transmute, un programma open source che eseguiamo noi stessi. Non l’abbiamo scritto noi e non possiamo fidarci dei file che le persone gli danno. Questo articolo spiega come lo eseguiamo perché ogni conversione veda solo il proprio file e nient’altro, e che cosa cambia con la conversione: mentre un file viene convertito, è leggibile sui nostri server.
Transmute, eseguito da noi
Eseguiamo l’immagine pubblicata di Transmute, versione 2.1.1, fissata tramite digest. Transmute è giovane, quindi la versione fissata cambia solo di proposito, e dopo ogni cambio gira nel nostro ambiente di sviluppo un test che percorre l’intero flusso sull’immagine reale.
Tre impostazioni contano più delle altre:
- Transmute non è mai raggiungibile da internet.
- La sua impostazione
ALLOW_UNAUTHENTICATEDrestafalse. - Nulla chiama l’endpoint di Transmute che scarica un file da un URL. L’unico file che un lavoro vede è quello caricato per lui.
Un container per ogni conversione
Ogni conversione gira nel proprio Cloudflare Container. Un Worker lo avvia tramite un Durable Object e gli dà come nome l’id della conversione. Il container viene fatturato solo mentre gira, e si ferma quando il lavoro termina.
Un container per ogni lavoro elimina tutto ciò che i lavori altrimenti condividerebbero. Non c’è un convertitore condiviso, né un disco condiviso, né una coda davanti a una sola macchina, e il file di un account non sta mai accanto a quello di un altro. Il disco del container finisce con il container, quindi nulla di ciò che un lavoro ha scritto su quel disco gli sopravvive.
I container girano nell’account Cloudflare che contiene il nostro bucket dei file, perché i file sono già lì. I bucket dei file sono nella giurisdizione eu di R2, e i container hanno il vincolo di posizionamento jurisdiction = "eu", quindi il file leggibile resta in Europa mentre viene convertito.
Solo il Worker può raggiungere il container
Un Cloudflare Container non ha un indirizzo proprio. L’unica strada per arrivarci passa dal Worker che lo ha avviato.
Questo rende innocuo un comportamento di Transmute. Dopo un avvio, Transmute assegna l’account di amministratore a chi lo chiama per primo. Su un convertitore con un indirizzo, chi chiamasse per primo dopo un riavvio ne diventerebbe il padrone. Qui ogni lavoro avvia un container nuovo, e il Worker è il suo primo e unico chiamante. A ogni avvio il Worker crea l’utente di servizio di Transmute e gli chiede un token, usando segreti che imposta il nostro Terraform.
Il Worker stesso ha un hostname pubblico, come ogni route di un Worker. La nostra api lo chiama via HTTPS con un token di servizio, solo per quattro cose: creare un lavoro, leggerne lo stato, annullarlo ed eliminarlo. Sono piccoli messaggi JSON; l’api non vede mai i byte di un file. Una regola di rate limiting sulla route del Worker è la seconda protezione.
La nostra api decide prima che si muova un byte
Il browser e la nostra api parlano di una conversione tramite Connect RPC. Quando il browser crea una conversione, l’api controlla quattro cose rispetto al Suo piano: il tipo di file, la sua dimensione, il conteggio del mese e la coppia di formati. Una conversione oltre il conteggio o la dimensione del Suo piano viene rifiutata con un motivo come FILE_CONVERSION_LIMIT_REACHED, e il sito mostra i piani che la consentirebbero. A quel punto non è stato caricato nulla, e un file che non si può convertire si può comunque inviare così com’è.
Il Worker e il container non applicano nulla su piani, conteggi o dimensioni. Eseguono ciò che l’api ha consentito. Le regole stanno dove si trovano già il piano e il conteggio, quindi c’è un solo posto che le controlla e un solo posto che le cambia.
- Il tipo di un file è la famiglia del suo formato di origine: video, audio, immagine o documento. Un MP4 convertito in MP3 è una conversione video, perché l’origine viene decodificata come video.
- Il conteggio riserva una conversione quando parte e la conta quando riesce; una fallita o annullata non conta. Una nuova conversione viene verificata rispetto a quelle finite più quelle ancora in corso, così due schede aperte non possono prendersi entrambe l’ultima del mese.
- Il carico ha anch’esso un limite: l’api limita quanti lavori girano insieme, per account e in totale, e gli altri aspettano.
Anche l’elenco dei formati viene da Transmute, mai da un elenco che teniamo noi. Transmute indica i suoi input e output come due insiemi, non come coppie, e decide il tipo di un file dalla sua estensione, quindi la nostra api carica una volta un file minuscolo per ogni estensione di input e tiene in cache i formati di destinazione che Transmute offre per essa. L’ultima parola resta comunque la risposta di Transmute per il Suo file reale; una scelta che rifiuta fallisce con un messaggio chiaro e non conta.
Il file prende un’altra strada
I byte dei file non passano mai per RPC, perché il gateway davanti alla nostra api limita un messaggio a 1 MiB. Il browser carica l’originale con URL multipart prefirmati, usando lo stesso codice di storage di un caricamento di un trasferimento ospitato, verso una chiave temporanea nel nostro bucket dei file: 1-days/file-conversions/<account>/<conversion id>/source. R2 lo cifra a riposo.
Il Worker trasmette in streaming l’origine dal suo binding R2 a Transmute, poi rimanda in streaming il file convertito nel bucket. I byte non passano mai dalla nostra api, e il container stesso non possiede credenziali di storage.
Quando viene eliminata ogni copia
| Cosa | Quando viene eliminato |
|---|---|
| L’originale, nell’area temporanea | Dalla nostra api, quando il lavoro termina |
| Il file convertito, nell’area temporanea | Dalla nostra api, 24 ore dopo la sua creazione |
| Un caricamento che il browser non ha mai finito | Annullato dalla nostra api entro 24 ore |
| Qualsiasi cosa sul disco del container | Con il container, quando il lavoro termina |
| Qualsiasi cosa rimasta sotto il prefisso temporaneo | Dalla regola di un giorno del bucket, come seconda linea |
Sono le eliminazioni fatte dall’api stessa a mantenere la promessa che un file convertito sparisce dopo 24 ore. La regola del bucket serve nel caso in cui una di esse non avvenga. Senza l’annullamento da parte dell’api, il bucket da solo conserverebbe le parti di un caricamento incompiuto per 7 giorni.
Perché il file convertito viene comunque cifrato nel Suo browser
Oggi ogni file ospitato viene cifrato nel Suo browser con AES-256-GCM prima che ne venga inviato anche un solo byte, con una chiave creata sul Suo dispositivo, come spiegato in come Transferify cifra i file nel browser. Il nostro servizio non decifra il contenuto dei file.
Un convertitore non può lavorare su un file cifrato, quindi la domanda era dove il file convertito diventa un trasferimento cifrato. C’erano due strade.
Attraverso il browser, ed è ciò che abbiamo costruito. Il browser carica l’originale leggibile, il container lo converte, e il browser rilegge il risultato dallo storage a blocchi e lo passa direttamente al caricamento cifrato esistente, così nel browser non viene memorizzato nulla. Il file convertito viene poi inviato come qualsiasi altro file, quindi i trasferimenti funzionano esattamente come prima. Il costo è il traffico: un file grande fa il viaggio tre volte, su, giù e di nuovo su.
Sul server, ed è ciò che abbiamo lasciato fuori da questa versione. La nostra api cifrerebbe da sé il file convertito e lo metterebbe nel trasferimento, risparmiando un viaggio. Ma l’api avrebbe allora nello stesso momento la chiave di un file e il suo contenuto leggibile, cosa che oggi non fa mai. Le servirebbe anche una seconda copia del codice di cifratura del browser, in Java.
Il compromesso, detto chiaramente
Per convertire un file dobbiamo poterlo leggere. Finché il lavoro non termina, l’originale è leggibile sui nostri server: nell’area temporanea, dove lo storage lo cifra a riposo, e dentro il container che lo converte. Il file convertito resta nell’area temporanea fino a 24 ore, così può scaricarlo o inviarlo, e poi viene eliminato. Questo vale in qualunque modo invii il file dopo: un file che converte e poi invia peer-to-peer è comunque passato prima dal nostro convertitore.
È anche il motivo per cui un file convertito non viene mai inviato come Zero-Knowledge. In modalità Zero-Knowledge non riceviamo mai la chiave e non abbiamo mai nulla che possiamo leggere, e un file convertito è stato leggibile sui nostri server. Finché un file convertito è nell’elenco, l’opzione Zero-Knowledge è disattivata, e torna disponibile quando il file viene rimosso.
Se un file attacca il convertitore
Convertitori come LibreOffice e ffmpeg sono un bersaglio noto per file costruiti ad arte, quindi mettiamo in conto un file che prende il controllo del convertitore.
Un file del genere vede solo il proprio lavoro: il proprio container, il proprio file di origine e il Worker che lo ha avviato. Non potrebbe raggiungere il file di un altro account, il nostro database, la nostra api, alcuna chiave o alcun trasferimento archiviato. Il container parte con l’accesso a internet disattivato e non possiede alcun token di storage, quindi non ha vie d’uscita né nulla da raggiungere. Il container è nuovo per questo lavoro e finisce con esso, quindi sul suo disco non c’è il file di nessun lavoro precedente e nessun lavoro successivo girerà al suo interno. Non ha un indirizzo, quindi nulla su internet può chiamarlo.
Il video gira sulla CPU
Transmute ricodifica il video con libx264, sulla CPU. Non ha una via con GPU, e i Cloudflare Containers non offrono un tipo con GPU. Il video è quindi il caso lento, e poiché un container viene fatturato per il tempo in cui gira, un lavoro lento è anche un lavoro costoso.
Ogni lavoro video può durare fino a due ore. Transmute non può fermare un lavoro in corso, ma il Worker può chiudere il container. Un lavoro che supera il limite viene chiuso, segnalato come troppo lungo, non conteggiato nel Suo piano e non riavviato. La nostra api avvia un lavoro solo quando il suo tipo e la sua dimensione rientrano nel limite di tempo alle velocità di conversione che abbiamo misurato.
Ecco come sono queste velocità nel nostro ambiente di sviluppo, con file di test generati. Una foto da 3 MB, un PDF di 10 pagine trasformato in immagini e un WAV da 50 MB trasformato in MP3 hanno richiesto ciascuno circa mezzo minuto, e un documento Word da 1 MB ha impiegato 48 secondi per diventare un PDF. Da 12 a 15 secondi circa di ogni lavoro sono l’avvio del container, quindi per un file piccolo l’avvio è la parte più grande dell’attesa. Il tempo se ne va nel video: un video 1080p a circa 17 Mbit/s ha richiesto 2 minuti e 43 secondi per 100 MB, 23 minuti per 1 GB e 91 minuti per 4 GB.
Nemmeno Cloudflare promette che un container giri per un tempo stabilito. Se un container si ferma prima, il lavoro fallisce con questo motivo, non viene conteggiato e può rifare la conversione.
Provare
Converta un file nella pagina di conversione, poi lo invii. Il browser cifra il file convertito come qualsiasi altro trasferimento.
Domande frequenti
Il mio file è leggibile sui vostri server mentre viene convertito?
Sì. Un convertitore ha bisogno del file leggibile. L’originale va in un’area temporanea del nostro storage nell’UE, che lo cifra a riposo, e la nostra api lo elimina quando la conversione termina. Il file convertito resta lì 24 ore, così può scaricarlo o inviarlo, e poi viene eliminato. Quando lo invia, il browser lo cifra come qualsiasi altro file.
Posso convertire un file in un trasferimento Zero-Knowledge?
Può convertirlo, ma un file convertito non viene mai inviato come Zero-Knowledge. In modalità Zero-Knowledge non riceviamo mai la chiave, quindi non abbiamo mai nulla che possiamo leggere, e un file convertito è stato leggibile sui nostri server. Finché un file convertito è nell’elenco, l’opzione Zero-Knowledge è disattivata.
Il mio file può finire accanto a quello di qualcun altro?
No. Ogni conversione gira nel proprio container, chiamato con l’id della conversione, avviato per quel lavoro e chiuso quando il lavoro termina. Non c’è un convertitore condiviso né un disco condiviso.
È il convertitore a decidere che cosa consente il mio piano?
No. La nostra api controlla il tipo di file, la sua dimensione, il conteggio del mese e la coppia di formati prima che il browser carichi qualcosa. Il Worker e il container eseguono solo ciò che l’api ha consentito.
Perché un video lungo fallisce invece di finire più tardi?
Il video viene ricodificato sulla CPU, e ogni lavoro video può durare fino a due ore. Transmute non può fermare un lavoro in corso, quindi il Worker che ha avviato il container lo chiude. La conversione viene segnalata come troppo lunga, non viene conteggiata nel Suo piano e non viene riavviata.