Cum convertește Transferify un fișier într-un container propriu
Adesea ai nevoie de un fișier în alt format înainte să-l trimiți: o fotografie HEIC ca JPG, un MOV ca MP4, un DOCX ca PDF. Transferify face acum acest pas pentru tine, pe pagina de conversie și ca mod al panoului de trimitere, de unde trimiți mai departe fișierele convertite.
Convertorul este Transmute, un program open-source pe care îl rulăm noi. Nu l-am scris noi și nu putem avea încredere în fișierele pe care i le dau oamenii. Articolul acesta explică cum îl rulăm astfel încât fiecare conversie să-și vadă doar propriul fișier și nimic altceva, și ce schimbă conversia: cât timp un fișier e convertit, el poate fi citit pe serverele noastre.
Transmute, rulat de noi
Rulăm imaginea publicată a lui Transmute, versiunea 2.1.1, fixată prin digest. Transmute e un proiect tânăr, așa că versiunea fixată se schimbă doar intenționat, iar după fiecare schimbare rulează pe mediul nostru de dezvoltare un test care parcurge tot drumul unei conversii pe imaginea reală.
Trei setări contează mai mult decât restul:
- Transmute nu poate fi accesat niciodată din internet.
- Setarea lui
ALLOW_UNAUTHENTICATEDrămânefalse. - Nimic nu apelează endpointul lui Transmute care descarcă un fișier de la o adresă URL. Singurul fișier pe care îl vede o sarcină este cel încărcat pentru ea.
Un container pentru fiecare conversie
Fiecare conversie rulează în propriul Cloudflare Container. Un Worker îl pornește printr-un Durable Object și îl numește după id-ul conversiei. Containerul e facturat doar cât rulează și se oprește când se încheie sarcina.
Un container pentru fiecare sarcină elimină tot ce ar avea altfel în comun sarcinile. Nu există un convertor comun, nici un disc comun, nici o coadă în fața unei singure mașini, iar fișierul unui cont nu stă niciodată lângă fișierul altui cont. Discul containerului dispare odată cu containerul, așa că nimic din ce a scris o sarcină pe acel disc nu rămâne după ea.
Containerele rulează în contul Cloudflare care găzduiește bucketul nostru de fișiere, pentru că fișierele sunt deja acolo. Bucketurile de fișiere sunt în jurisdicția eu a R2, iar containerele au constrângerea de plasare jurisdiction = "eu", așa că fișierul lizibil rămâne în Europa cât timp e convertit.
Doar Worker-ul poate ajunge la container
Un Cloudflare Container nu are o adresă proprie. Singura cale spre el trece prin Worker-ul care l-a pornit.
Asta face inofensiv un comportament al lui Transmute. După pornire, Transmute dă contul de administrator primului care îl apelează. Pe un convertor cu adresă, oricine îl apela primul după o repornire ar fi devenit stăpânul lui. Aici fiecare sarcină pornește un container nou, iar Worker-ul este primul și singurul care îl apelează. La fiecare pornire, Worker-ul creează utilizatorul de serviciu al lui Transmute și îi cere un token, folosind secrete pe care le setează Terraform-ul nostru.
Worker-ul are el însuși un hostname public, ca orice rută de Worker. API-ul nostru îl apelează prin HTTPS cu un token de serviciu, pentru patru lucruri și atât: să creeze o sarcină, să-i citească starea, s-o anuleze și s-o șteargă. Sunt mesaje JSON mici; API-ul nu vede niciodată octeții unui fișier. O regulă de limitare a ratei pe ruta Worker-ului este a doua protecție.
API-ul nostru decide înainte să se miște vreun octet
Browserul și API-ul nostru vorbesc despre o conversie prin Connect RPC. Când browserul creează o conversie, API-ul verifică patru lucruri față de planul tău: tipul fișierului, mărimea lui, numărul de conversii din lună și perechea de formate. O conversie care depășește numărul sau mărimea permise de planul tău e refuzată cu un motiv precum FILE_CONVERSION_LIMIT_REACHED, iar site-ul îți arată planurile care ar permite-o. În acel moment nu s-a încărcat nimic, iar un fișier care nu poate fi convertit poate fi trimis în continuare așa cum e.
Worker-ul și containerul nu impun nimic legat de planuri, numărul de conversii sau mărimi. Ele rulează ce a permis API-ul. Regulile stau acolo unde sunt deja planul și numărătoarea, așa că un singur loc le verifică și un singur loc le schimbă.
- Tipul unui fișier este familia formatului sursă: video, audio, imagine sau document. Un MP4 convertit în MP3 este o conversie video, pentru că sursa e decodată ca video.
- Numărătoarea rezervă o conversie când pornește și o numără când reușește; una eșuată sau anulată nu se numără. O conversie nouă e verificată față de cele terminate plus cele încă în lucru, așa că două file deschise nu pot lua amândouă ultima conversie a lunii.
- Volumul e limitat și el: API-ul limitează câte sarcini rulează în același timp, pe cont și în total, iar restul așteaptă.
Lista de formate vine tot de la Transmute, niciodată dintr-o listă ținută de noi. Transmute își raportează intrările și ieșirile ca două mulțimi, nu ca perechi, și decide tipul unui fișier după extensie, așa că API-ul nostru încarcă o singură dată un fișier minuscul pentru fiecare extensie de intrare și păstrează în cache formatele țintă pe care Transmute le oferă pentru ea. Ultimul cuvânt îl are tot răspunsul lui Transmute pentru fișierul tău real; o alegere pe care o refuză eșuează cu un mesaj clar și nu se numără.
Fișierul merge pe alt drum
Octeții fișierelor nu trec niciodată prin RPC, pentru că gateway-ul din fața API-ului nostru limitează un mesaj la 1 MiB. Browserul încarcă originalul cu URL-uri multipart presemnate, folosind același cod de stocare ca o încărcare de transfer găzduit, la o cheie temporară din bucketul nostru de fișiere: 1-days/file-conversions/<account>/<conversion id>/source. R2 îl păstrează criptat.
Worker-ul trimite sursa în flux din binding-ul lui R2 către Transmute, apoi trimite fișierul convertit înapoi în bucket, tot în flux. Octeții nu trec niciodată prin API-ul nostru, iar containerul însuși nu deține nicio credențială de stocare.
Când e ștearsă fiecare copie
| Ce | Când e șters |
|---|---|
| Originalul, în zona temporară | De API-ul nostru, când se încheie sarcina |
| Fișierul convertit, în zona temporară | De API-ul nostru, la 24 de ore după ce a fost creat |
| O încărcare pe care browserul n-a terminat-o | Anulată de API-ul nostru în cel mult 24 de ore |
| Orice de pe discul containerului | Odată cu containerul, când se încheie sarcina |
| Orice a rămas sub prefixul temporar | Prin regula de o zi a bucketului, ca a doua linie de apărare |
Ștergerile făcute chiar de API sunt cele care țin promisiunea că un fișier convertit dispare după 24 de ore. Regula bucketului există pentru cazul în care una dintre ele nu are loc. Fără anularea făcută de API, bucketul singur ar păstra părțile unei încărcări neterminate 7 zile.
De ce fișierul convertit e totuși criptat în browserul tău
Astăzi fiecare fișier găzduit e criptat în browserul tău cu AES-256-GCM înainte să plece vreun octet din el, cu o cheie creată pe dispozitivul tău, cum explicăm în cum criptează Transferify fișierele în browser. Serviciul nostru nu decriptează conținutul fișierelor.
Un convertor nu poate lucra cu un fișier criptat, așa că întrebarea a fost unde devine fișierul convertit un transfer criptat. Existau două variante.
Prin browser, varianta pe care am construit-o. Browserul încarcă originalul lizibil, containerul îl convertește, iar browserul citește rezultatul înapoi din stocare pe bucăți și îl trimite direct în încărcarea criptată existentă, așa că nimic nu e stocat în browser. Fișierul convertit e apoi trimis ca orice alt fișier, deci transferurile funcționează exact ca înainte. Costul e traficul: un fișier mare face drumul de trei ori, în sus, în jos și din nou în sus.
Pe server, varianta lăsată deoparte în această versiune. API-ul nostru ar cripta singur fișierul convertit și l-ar pune în transfer, ceea ce scutește un drum. Dar atunci API-ul ar ține în același moment cheia unui fișier și conținutul lui lizibil, lucru pe care azi nu-l face niciodată. I-ar trebui și o a doua copie a codului de criptare din browser, în Java.
Compromisul, spus direct
Ca să convertim un fișier, trebuie să-l putem citi. Până se încheie sarcina, originalul poate fi citit pe serverele noastre: în zona temporară, unde stocarea îl păstrează criptat, și în containerul care îl convertește. Fișierul convertit rămâne în zona temporară cel mult 24 de ore, ca să-l poți descărca sau trimite, apoi e șters. Asta e valabil oricum ai trimite fișierul după aceea: un fișier pe care îl convertești și apoi îl trimiți peer-to-peer a trecut totuși mai întâi prin convertorul nostru.
Tot de aceea un fișier convertit nu e trimis niciodată ca Zero-Knowledge. În modul Zero-Knowledge nu primim niciodată cheia și nu deținem niciodată ceva ce am putea citi, iar un fișier convertit a putut fi citit pe serverele noastre. Cât timp un fișier convertit e în listă, opțiunea Zero-Knowledge e oprită și revine când fișierul e scos.
Dacă un fișier atacă convertorul
Convertoare precum LibreOffice și ffmpeg sunt o țintă cunoscută pentru fișiere create special, așa că ne pregătim pentru un fișier care preia controlul convertorului.
Un astfel de fișier vede doar propria sarcină: propriul container, propriul fișier sursă și Worker-ul care l-a pornit. N-ar putea ajunge la fișierul altui cont, la baza noastră de date, la API-ul nostru, la vreo cheie sau la vreun transfer stocat. Containerul pornește cu accesul la internet oprit și nu deține niciun token de stocare, așa că nu are pe unde să iasă și nimic la care să ajungă. Containerul e nou pentru această sarcină și se încheie odată cu ea, așa că pe discul lui nu e fișierul niciunei sarcini anterioare și nicio sarcină ulterioară nu va rula în el. Nu are adresă, așa că nimic de pe internet nu-l poate apela.
Conversiile video rulează pe CPU
Transmute recodează videoclipurile cu libx264, pe CPU. Nu are o variantă pe GPU, iar Cloudflare Containers nu oferă un tip cu GPU. Așa că videoclipurile sunt cazul lent și, pentru că un container e facturat pentru timpul cât rulează, o sarcină lentă e și una scumpă.
Fiecare sarcină video poate rula cel mult două ore. Transmute nu poate opri o sarcină aflată în lucru, dar Worker-ul poate opri containerul. O sarcină care depășește limita e oprită, raportată ca prea lungă, nu se scade din planul tău și nu e pornită din nou. API-ul nostru pornește o sarcină doar când tipul și mărimea ei încap în limita de timp, la vitezele de conversie pe care le-am măsurat.
Iată cum arată aceste viteze pe mediul nostru de dezvoltare, cu fișiere de test generate. O fotografie de 3 MB, un PDF de 10 pagini transformat în imagini și un WAV de 50 MB transformat în MP3 au durat fiecare cam o jumătate de minut, iar un document Word de 1 MB a avut nevoie de 48 de secunde ca să devină PDF. Cam 12 până la 15 secunde din fiecare sarcină sunt pornirea containerului, așa că pentru un fișier mic pornirea e cea mai mare parte a așteptării. La video se duce timpul: un video 1080p la circa 17 Mbit/s a durat 2 minute și 43 de secunde pentru 100 MB, 23 de minute pentru 1 GB și 91 de minute pentru 4 GB.
Nici Cloudflare nu promite că un container rulează un anumit timp. Dacă un container se oprește mai devreme, sarcina eșuează cu acest motiv, nu se numără și convertești din nou.
Încearcă
Convertește un fișier pe pagina de conversie, apoi trimite-l. Browserul tău criptează fișierul convertit ca pe orice alt transfer.
Întrebări frecvente
Fișierul meu poate fi citit pe serverele voastre cât timp e convertit?
Da. Un convertor are nevoie de fișierul lizibil. Originalul ajunge într-o zonă temporară din stocarea noastră din UE, care îl păstrează criptat, iar API-ul nostru îl șterge când se încheie conversia. Fișierul convertit rămâne acolo 24 de ore, ca să-l poți descărca sau trimite, apoi e șters. Când îl trimiți, browserul tău îl criptează ca pe orice alt fișier.
Pot converti un fișier într-un transfer Zero-Knowledge?
Îl poți converti, dar un fișier convertit nu e trimis niciodată ca Zero-Knowledge. În modul Zero-Knowledge nu primim niciodată cheia, deci nu deținem niciodată ceva ce am putea citi, iar un fișier convertit a putut fi citit pe serverele noastre. Cât timp un fișier convertit e în listă, opțiunea Zero-Knowledge e oprită.
Poate ajunge fișierul meu lângă fișierul altcuiva?
Nu. Fiecare conversie rulează în containerul ei, numit după id-ul conversiei, pornit pentru acea sarcină și oprit când sarcina se încheie. Nu există un convertor comun și nici un disc comun.
Convertorul decide ce îmi permite planul?
Nu. API-ul nostru verifică tipul fișierului, mărimea lui, numărul de conversii din lună și perechea de formate înainte ca browserul tău să încarce ceva. Worker-ul și containerul rulează doar ce a permis API-ul.
De ce eșuează un videoclip lung în loc să se termine mai târziu?
Videoclipurile sunt recodate pe CPU, iar fiecare sarcină video poate rula cel mult două ore. Transmute nu poate opri o sarcină aflată în lucru, așa că Worker-ul care a pornit containerul îl oprește. Conversia e raportată ca prea lungă, nu se scade din planul tău și nu e pornită din nou.