Sécurisez et optimisez vos transferts de fichiers avec les signed URLs Cloud Storage

En tant qu'architecte cloud, je vois encore trop souvent une anti-pattern tenace dans le développement d'applications web : le streaming de fichiers à travers le serveur backend.
Le scénario est classique : un utilisateur doit uploader une photo de profil ou télécharger une facture PDF. L'application reçoit le fichier sur le serveur API, le met en mémoire tampon, puis l'envoie vers Google Cloud Storage (GCS).
Pourquoi est-ce une mauvaise idée ?
-
Goulot d'étranglement : Votre serveur API (Compute Engine, Cloud Run ou GKE) consomme de la CPU et de la RAM pour du simple transit de données.
-
Latence : Vous ajoutez un saut supplémentaire (Client -> Serveur -> GCS).
-
Coûts : Vous payez pour la bande passante et le temps de calcul de votre serveur, alors que GCS est conçu pour ingérer des pétaoctets à moindre coût.
La solution architecturale élégante ? Les URL signées.
Dans cet article, nous allons voir comment déléguer le transfert de fichiers directement au client (le navigateur) de manière totalement sécurisée, en utilisant Python et le SDK GCP.
Qu'est-ce qu'une Signed URL ?
Une Signed URL est un lien temporaire qui donne une permission spécifique (lecture, écriture, suppression) sur un objet précis de votre bucket, pour une durée limitée.
Imaginez un valet de parking. Vous ne lui donnez pas les clés de votre maison (vos identifiants IAM Admin), mais un ticket temporaire qui lui permet juste de garer la voiture (uploader un fichier) et qui expire au bout de 5 minutes.
- Demande : Le client (Frontend) demande au Backend l'autorisation d'uploader un fichier (ex: avatar.jpg).
- Signature : Le Backend, via ses crédentiels IAM (Service Account), génère une URL signée cryptographiquement.
- Envoi : Le Backend renvoie cette URL au client.
- Action : Le client effectue un PUT ou un GET directement sur cette URL vers Google Cloud Storage.
Implémentation technique
Pour cet exemple, nous allons générer une URL permettant à un utilisateur d'uploader un fichier (méthode PUT).
Prérequis
- Un bucket GCS privé (pas d'accès public allUsers).
- Un Service Account avec le rôle roles/storage.objectCreator (pour l'upload) ou roles/storage.objectViewer (pour le download).
Le code (Backend Python)
Utilisons la méthode generate_signed_url du SDK Python.
from google.cloud import storage
import datetime
def generate_upload_url(bucket_name, blob_name, content_type):
"""
Génère une URL signée v4 pour uploader un objet.
"""
storage_client = storage.Client()
bucket = storage_client.bucket(bucket_name)
blob = bucket.blob(blob_name)
url = blob.generate_signed_url(
version="v4",
# L'URL expire dans 15 minutes
expiration=datetime.timedelta(minutes=15),
# Autorise uniquement la méthode PUT
method="PUT",
# Sécurité critique : force le type de contenu
content_type=content_type,
)
return url
# Exemple d'utilisation
# L'utilisateur veut uploader 'mon-image.png'
signed_url = generate_upload_url(
"mon-bucket-app",
"uploads/user_123/mon-image.png",
"image/png"
)
print(f"URL d'upload générée : {signed_url}")
Le code (Frontend JavaScript)
Côté client, plus besoin d'authentification complexe. L'URL contient déjà le token nécessaire.
async function uploadFile(file) {
// 1. Récupérer l'URL signée depuis votre API
const response = await fetch('/api/get-upload-url', {
method: 'POST',
body: JSON.stringify({ name: file.name, type: file.type })
});
const { signedUrl } = await response.json();
// 2. Uploader directement vers GCS
const result = await fetch(signedUrl, {
method: 'PUT',
body: file,
headers: {
'Content-Type': file.type // Doit matcher celui défini lors de la signature !
}
});
if (result.ok) {
console.log('Upload réussi directement sur le Cloud Storage !');
}
}
Les pièges à éviter
C'est ici que l'expérience fait la différence entre un code qui "marche" et une architecture de production.
1. La configuration CORS
C'est l'erreur numéro 1. Par défaut, votre bucket GCS refusera les requêtes venant de votre domaine (ex: mon-app.com). Vous devez configurer le CORS sur le bucket via la CLI gcloud ou Terraform.
Créez un fichier cors.json :
[
{
"origin": ["https://mon-app.com"],
"method": ["PUT", "GET", "HEAD"],
"responseHeader": ["Content-Type"],
"maxAgeSeconds": 3600
}
]
Appliquez-le :
2. Validez le Content-Type
Ne laissez jamais le champ content_type vide lors de la génération de la signature. Si vous ne le fixez pas, un utilisateur malveillant pourrait uploader un script exécutable (application/x-sh) à la place d'une image, ouvrant la porte à des failles de sécurité si ce fichier est ensuite servi à d'autres utilisateurs.
3. Gestion des noms de fichiers
Ne faites pas confiance aux noms de fichiers envoyés par les utilisateurs. Générez le nom de l'objet côté backend (par exemple avec un UUID) pour éviter les collisions et les caractères spéciaux non gérés.
Conclusion
L'utilisation des Signed URLs sur Google Cloud Storage est un "quick win" architectural. Vous déchargez votre infrastructure, améliorez l'expérience utilisateur grâce au réseau global de Google, et maintenez un niveau de sécurité granulaire.
C'est typiquement le genre d'optimisation Cloud Native qui permet de passer d'une application "bricolée" à une infrastructure capable de scaler.