Transferify
Retour au blog

Comment Transferify convertit chaque fichier dans son propre conteneur

On a souvent besoin d’un fichier dans un autre format avant de l’envoyer : une photo HEIC en JPG, un MOV en MP4, un DOCX en PDF. Transferify fait désormais cette étape pour vous, sur la page de conversion et comme mode du panneau d’envoi, d’où vous envoyez ensuite les fichiers convertis.

Le convertisseur est Transmute, un programme open source que nous exécutons nous-mêmes. Nous ne l’avons pas écrit, et nous ne pouvons pas faire confiance aux fichiers qu’on lui donne. Cet article explique comment nous l’exécutons pour que chaque conversion ne voie que son propre fichier et rien d’autre, et ce que la conversion change : pendant qu’un fichier est converti, il est lisible sur nos serveurs.

Le chemin d’une conversion. Le navigateur demande à notre API de créer une conversion, et l’API vérifie le type de fichier, sa taille, le nombre de conversions du mois et la paire de formats. Le navigateur envoie le fichier lisible dans une zone de transit du stockage dans l’UE. L’API demande au Worker de lancer la tâche, et le Worker lance un nouveau conteneur nommé d’après l’id de la conversion. Le Worker transmet en flux le fichier de la zone de transit vers le conteneur, puis renvoie le résultat en flux vers la zone de transit ; le conteneur ne détient aucun identifiant de stockage. Le navigateur lit le résultat et le chiffre avant de l’envoyer comme un transfert normal.Le chemin d’une conversion. Le navigateur demande à notre API de créer une conversion, et l’API vérifie le type de fichier, sa taille, le nombre de conversions du mois et la paire de formats. Le navigateur envoie le fichier lisible dans une zone de transit du stockage dans l’UE. L’API demande au Worker de lancer la tâche, et le Worker lance un nouveau conteneur nommé d’après l’id de la conversion. Le Worker transmet en flux le fichier de la zone de transit vers le conteneur, puis renvoie le résultat en flux vers la zone de transit ; le conteneur ne détient aucun identifiant de stockage. Le navigateur lit le résultat et le chiffre avant de l’envoyer comme un transfert normal.
Une conversion. Les petits messages passent par notre API ; le fichier va directement dans le stockage de transit, et seul le conteneur lancé pour cette tâche le convertit.

Transmute, exécuté par nous

Nous exécutons l’image publiée de Transmute, version 2.1.1, épinglée par son digest. Transmute est jeune, donc la version épinglée ne change que volontairement, et après chaque changement un test de bout en bout sur l’image réelle tourne dans notre environnement de développement.

Trois réglages comptent plus que les autres :

  • Transmute n’est jamais joignable depuis Internet.
  • Son réglage ALLOW_UNAUTHENTICATED reste à false.
  • Rien n’appelle le point d’accès de Transmute qui télécharge un fichier depuis une URL. Le seul fichier qu’une tâche voit est celui envoyé pour elle.

Un conteneur par conversion

Chaque conversion s’exécute dans son propre Cloudflare Container. Un Worker le lance par l’intermédiaire d’un Durable Object et le nomme d’après l’id de la conversion. Le conteneur n’est facturé que pendant qu’il tourne, et il s’arrête quand la tâche se termine.

Un conteneur par tâche supprime tout ce que les tâches partageraient sinon. Il n’y a ni convertisseur partagé, ni disque partagé, ni file d’attente devant une seule machine, et le fichier d’un compte ne se trouve jamais à côté de celui d’un autre. Le disque du conteneur disparaît avec le conteneur, donc rien de ce qu’une tâche a écrit sur ce disque ne lui survit.

Les conteneurs tournent dans le compte Cloudflare qui contient notre bucket de fichiers, parce que les fichiers s’y trouvent déjà. Les buckets de fichiers sont dans la juridiction eu de R2, et les conteneurs ont la contrainte de placement jurisdiction = "eu" : le fichier lisible reste donc en Europe pendant sa conversion.

Seul le Worker peut atteindre le conteneur

Un Cloudflare Container n’a pas d’adresse propre. Le seul chemin vers lui passe par le Worker qui l’a lancé.

Cela rend inoffensif un comportement de Transmute. Après un démarrage, Transmute donne le compte administrateur à son premier appelant. Sur un convertisseur doté d’une adresse, le premier à l’appeler après un redémarrage en deviendrait le propriétaire. Ici, chaque tâche lance un conteneur neuf, et le Worker est son premier et seul appelant. À chaque démarrage, le Worker crée l’utilisateur de service de Transmute et lui demande un jeton, avec des secrets que notre Terraform définit.

Le Worker lui-même a un nom d’hôte public, comme toute route de Worker. Notre API l’appelle en HTTPS avec un jeton de service, pour quatre choses seulement : créer une tâche, lire son état, l’annuler et la supprimer. Ce sont de petits messages JSON ; l’API ne voit jamais les octets d’un fichier. Une règle de limitation de débit sur la route du Worker est la deuxième protection.

Notre API décide avant qu’un octet ne bouge

Le navigateur et notre API échangent au sujet d’une conversion via Connect RPC. Quand le navigateur crée une conversion, l’API vérifie quatre choses par rapport à votre plan : le type de fichier, sa taille, le nombre de conversions du mois et la paire de formats. Une conversion qui dépasse le nombre ou la taille de votre plan est refusée avec un motif comme FILE_CONVERSION_LIMIT_REACHED, et le site affiche les plans qui la permettraient. À ce stade, rien n’a été envoyé, et un fichier qui ne peut pas être converti peut toujours être envoyé tel quel.

Le Worker et le conteneur n’appliquent rien en matière de plans, de nombres ou de tailles. Ils exécutent ce que l’API a autorisé. Les règles se trouvent là où sont déjà le plan et le décompte : un seul endroit les vérifie, un seul endroit les modifie.

  • Le type d’un fichier est la famille de son format source : vidéo, audio, image ou document. Un MP4 converti en MP3 est une conversion vidéo, parce que la source est décodée comme une vidéo.
  • Le décompte réserve une conversion quand elle démarre et la compte quand elle réussit ; une conversion échouée ou annulée ne compte pas. Une nouvelle conversion est vérifiée par rapport à celles terminées plus celles encore en cours, donc deux onglets ouverts ne peuvent pas prendre tous les deux la dernière du mois.
  • La charge est plafonnée elle aussi : l’API limite le nombre de tâches qui tournent en même temps, par compte et au total, et les autres attendent.

La liste des formats vient elle aussi de Transmute, jamais d’une liste que nous tenons. Transmute indique ses entrées et ses sorties sous forme de deux ensembles, pas de paires, et il déduit le type d’un fichier de son extension : notre API envoie donc une fois un tout petit fichier pour chaque extension d’entrée et garde en cache les cibles que Transmute propose pour elle. Le dernier mot revient quand même à la réponse de Transmute pour votre vrai fichier ; un choix qu’il refuse échoue avec un message clair et ne compte pas.

Le fichier prend une autre route

Les octets des fichiers ne passent jamais par RPC, parce que la passerelle devant notre API plafonne un message à 1 Mio. Le navigateur envoie l’original avec des URL multipart présignées, en utilisant le même code de stockage qu’un envoi de transfert hébergé, vers une clé de transit dans notre bucket de fichiers : 1-days/file-conversions/<account>/<conversion id>/source. R2 le chiffre au repos.

Le Worker transmet la source en flux depuis sa liaison R2 vers Transmute, puis renvoie en flux le fichier converti dans le bucket. Les octets ne passent jamais par notre API, et le conteneur lui-même ne détient aucun identifiant de stockage.

Quand chaque copie est supprimée

QuoiQuand c’est supprimé
L’original, dans la zone de transitPar notre API, à la fin de la tâche
Le fichier converti, dans la zone de transitPar notre API, 24 heures après sa création
Un envoi que le navigateur n’a jamais terminéAnnulé par notre API dans les 24 heures
Tout ce qui est sur le disque du conteneurAvec le conteneur, à la fin de la tâche
Tout ce qui reste sous le préfixe de transitPar la règle d’un jour du bucket, en deuxième ligne

Ce sont les suppressions faites par l’API elle-même qui tiennent la promesse qu’un fichier converti a disparu au bout de 24 heures. La règle du bucket est là au cas où l’une d’elles n’aurait pas lieu. Sans l’annulation par l’API, le bucket seul garderait les parties d’un envoi inachevé pendant 7 jours.

Pourquoi le fichier converti est quand même chiffré dans votre navigateur

Aujourd’hui, chaque fichier hébergé est chiffré dans votre navigateur avec AES-256-GCM avant qu’un seul de ses octets soit envoyé, avec une clé créée sur votre appareil, comme l’explique comment Transferify chiffre les fichiers dans votre navigateur. Notre service ne déchiffre pas le contenu des fichiers.

Un convertisseur ne peut pas travailler sur un fichier chiffré : la question était donc de savoir où le fichier converti devient un transfert chiffré. Il y avait deux façons de faire.

Par le navigateur, ce que nous avons construit. Le navigateur envoie l’original lisible, le conteneur le convertit, et le navigateur relit le résultat depuis le stockage par plages et le fait passer directement dans l’envoi chiffré existant, si bien que rien n’est stocké dans le navigateur. Le fichier converti est ensuite envoyé comme n’importe quel autre fichier, donc les transferts fonctionnent exactement comme avant. Le prix, c’est le trafic : un gros fichier fait trois fois le trajet, à l’aller, au retour, puis de nouveau à l’aller.

Sur le serveur, ce que nous avons laissé de côté dans cette version. Notre API chiffrerait elle-même le fichier converti et le placerait dans le transfert, ce qui économise un trajet. Mais l’API détiendrait alors au même moment la clé d’un fichier et son contenu lisible, ce qu’elle ne fait jamais aujourd’hui. Il lui faudrait aussi une deuxième copie du code de chiffrement du navigateur, en Java.

Le compromis, en clair

Pour convertir un fichier, nous devons pouvoir le lire. Jusqu’à la fin de la tâche, l’original est lisible sur nos serveurs : dans la zone de transit, où le stockage le chiffre au repos, et dans le conteneur qui le convertit. Le fichier converti reste dans la zone de transit jusqu’à 24 heures, pour que vous puissiez le télécharger ou l’envoyer, puis il est supprimé. C’est vrai quelle que soit la façon dont vous envoyez le fichier ensuite : un fichier que vous convertissez puis envoyez en pair à pair est quand même passé d’abord par notre convertisseur.

C’est aussi pour cela qu’un fichier converti n’est jamais envoyé en Zéro connaissance. En mode Zéro connaissance, nous ne recevons jamais la clé et ne détenons jamais rien que nous puissions lire, et un fichier converti a été lisible sur nos serveurs. Tant qu’un fichier converti est dans la liste, l’option Zéro connaissance est désactivée, et elle revient quand le fichier est retiré.

Si un fichier attaque le convertisseur

Les convertisseurs comme LibreOffice et ffmpeg sont une cible connue des fichiers piégés : nous prévoyons donc le cas d’un fichier qui prend le contrôle du convertisseur.

Un tel fichier ne voit que sa propre tâche : son propre conteneur, son propre fichier source et le Worker qui l’a lancé. Il ne pourrait atteindre ni le fichier d’un autre compte, ni notre base de données, ni notre API, ni aucune clé, ni aucun transfert stocké. Le conteneur démarre avec son accès à Internet coupé et ne détient aucun jeton de stockage : il n’a donc aucune sortie et rien à atteindre. Le conteneur est neuf pour cette tâche et se termine avec elle, donc aucun fichier d’une tâche précédente n’est sur son disque et aucune tâche suivante ne s’exécutera dedans. Il n’a pas d’adresse, donc rien sur Internet ne peut l’appeler.

Ce que le conteneur d’une conversion peut atteindre. À l’intérieur se trouvent Transmute ainsi que la source et le résultat propres à cette tâche. Son seul appelant est le Worker qui l’a lancé. Hors de sa portée se trouvent les conteneurs des autres conversions, les fichiers des autres comptes, notre base de données, notre API, les clés de chiffrement et les transferts stockés. Internet ne peut pas appeler le conteneur, car il n’a pas d’adresse.Ce que le conteneur d’une conversion peut atteindre. À l’intérieur se trouvent Transmute ainsi que la source et le résultat propres à cette tâche. Son seul appelant est le Worker qui l’a lancé. Hors de sa portée se trouvent les conteneurs des autres conversions, les fichiers des autres comptes, notre base de données, notre API, les clés de chiffrement et les transferts stockés. Internet ne peut pas appeler le conteneur, car il n’a pas d’adresse.
Un fichier qui s’échappe du convertisseur ne trouve qu’une tâche : son propre conteneur, son propre fichier et le Worker. Tout le reste est hors de sa portée.

La vidéo tourne sur le CPU

Transmute réencode la vidéo avec libx264, sur le CPU. Il n’a pas de chemin GPU, et les Cloudflare Containers ne proposent aucun type avec GPU. La vidéo est donc le cas lent, et comme un conteneur est facturé pour le temps pendant lequel il tourne, une tâche lente est aussi une tâche chère.

Chaque tâche vidéo peut durer jusqu’à deux heures. Transmute ne sait pas arrêter une tâche en cours, mais le Worker peut mettre fin au conteneur. Une tâche qui dépasse la limite est arrêtée, signalée comme trop longue, n’est pas décomptée de votre plan et n’est pas relancée. Notre API ne lance une tâche que si son type et sa taille tiennent dans la limite de temps aux vitesses de conversion que nous avons mesurées.

Voici à quoi ressemblent ces vitesses sur notre environnement de développement, avec des fichiers de test générés. Une photo de 3 Mo, un PDF de 10 pages transformé en images et un WAV de 50 Mo transformé en MP3 ont chacun pris environ une demi-minute, et un document Word de 1 Mo a mis 48 secondes à devenir un PDF. Environ 12 à 15 secondes de chaque tâche correspondent au démarrage du conteneur, donc pour un petit fichier, le démarrage représente l’essentiel de l’attente. C’est la vidéo qui prend du temps : une vidéo 1080p à environ 17 Mbit/s a pris 2 minutes 43 secondes pour 100 Mo, 23 minutes pour 1 Go et 91 minutes pour 4 Go.

Cloudflare ne promet pas non plus qu’un conteneur tourne pendant une durée donnée. Si un conteneur s’arrête plus tôt, la tâche échoue avec ce motif, elle n’est pas décomptée, et vous relancez la conversion.

Essayez

Convertissez un fichier sur la page de conversion, puis envoyez-le. Votre navigateur chiffre le fichier converti comme n’importe quel autre transfert.

Questions fréquentes

Mon fichier est-il lisible sur vos serveurs pendant sa conversion ?

Oui. Un convertisseur a besoin du fichier lisible. L’original va dans une zone de transit de notre stockage dans l’UE, qui le chiffre au repos, et notre API le supprime à la fin de la conversion. Le fichier converti y est gardé 24 heures pour que vous puissiez le télécharger ou l’envoyer, puis il est supprimé. Quand vous l’envoyez, votre navigateur le chiffre comme n’importe quel autre fichier.

Puis-je convertir un fichier dans un transfert Zéro connaissance ?

Vous pouvez le convertir, mais un fichier converti n’est jamais envoyé en Zéro connaissance. En mode Zéro connaissance, nous ne recevons jamais la clé, nous ne détenons donc jamais rien que nous puissions lire, et un fichier converti a été lisible sur nos serveurs. Tant qu’un fichier converti est dans la liste, l’option Zéro connaissance est désactivée.

Mon fichier peut-il se retrouver à côté de celui de quelqu’un d’autre ?

Non. Chaque conversion s’exécute dans son propre conteneur, nommé d’après l’id de la conversion, lancé pour cette tâche et arrêté quand la tâche se termine. Il n’y a ni convertisseur partagé ni disque partagé.

Est-ce le convertisseur qui décide de ce que permet mon plan ?

Non. Notre API vérifie le type de fichier, sa taille, le nombre de conversions du mois et la paire de formats avant que votre navigateur n’envoie quoi que ce soit. Le Worker et le conteneur n’exécutent que ce que l’API a autorisé.

Pourquoi une longue vidéo échoue-t-elle au lieu de finir plus tard ?

La vidéo est réencodée sur le CPU, et chaque tâche vidéo peut durer jusqu’à deux heures. Transmute ne sait pas arrêter une tâche en cours, donc le Worker qui a lancé le conteneur y met fin. La conversion est signalée comme trop longue, n’est pas décomptée de votre plan et n’est pas relancée.

Pages associées

Retour en haut