Aller au contenu

URL présignées S3 : génération, pièges d'expiration et 403 après rotation

Découvrez pourquoi une URL présignée S3 renvoie une erreur 403 bien avant l'expiration que vous aviez demandée, et comment choisir une durée que vos identifiants de signature peuvent vraiment tenir. Vous saurez distinguer une fenêtre expirée d'une session expirée, et corriger chacun des deux cas.

Ce guide s’adresse à vous si vous distribuez des URL présignées S3 et qu’elles se mettent à renvoyer une erreur 403, le code HTTP qui veut dire accès refusé. Une URL présignée est un lien temporaire vers un seul fichier dans le stockage Amazon S3, qui transporte lui-même la permission de le télécharger ou de le téléverser. La plainte habituelle, c’est un lien qui meurt quelques heures après que vous en avez demandé un valide une semaine, et la durée choisie est rarement en cause. Une URL signée par un rôle IAM (l’identité temporaire sous laquelle roule une fonction Lambda ou un serveur EC2) meurt avec la session de ce rôle. La réponse courte, c’est de signer avec des identifiants dont vous contrôlez la durée de vie, ou de générer chaque lien à la demande et de le laisser vivre quelques minutes.

Ce qui se passe

Quand vous générez une URL présignée, rien n’est enregistré chez AWS. Le SDK calcule localement une signature SigV4 à partir de quatre entrées : les identifiants, la requête exacte (bucket, clé, méthode, en-têtes signés), la région et la fenêtre d’expiration (X-Amz-Expires). Quand quelqu’un utilise l’URL, S3 recalcule la signature et vérifie trois choses : la signature correspond, l’heure actuelle est dans la fenêtre, et les identifiants de signature auraient le droit de faire cette requête en ce moment même.

Chaque vérification a son propre mode d’échec. La fenêtre est plafonnée à 7 jours (604800 secondes) en SigV4 — en demander plus échoue dès la génération. La vérification de signature échoue si quoi que ce soit dans la requête diffère de ce qui a été signé : un Content-Type différent sur un PUT qui en avait signé un, un proxy qui ajoute des en-têtes qui faisaient partie de la signature, ou une région qui ne correspond pas. Et la vérification de permission en direct signifie qu’une URL présignée est révoquée implicitement quand les permissions du signataire le sont — ou quand la session du signataire se termine.

Ce dernier point est le piège de la rotation. Un profil d’instance EC2 livre des identifiants temporaires qui tournent sur un horaire de quelques heures ; les identifiants de Lambda et des tâches ECS se comportent pareil. Une URL signée avec ces identifiants inclut le jeton de session, et quand la session expire — typiquement entre une et douze heures plus tard, hors de votre contrôle — chaque URL signée par elle retourne 403 avec ExpiredToken ou AccessDenied, même si X-Amz-Expires disait 7 jours.

Quoi vérifier en premier

  • Lisez le vrai corps d’erreur. AccessDenied: Request has expired veut dire la fenêtre ; ExpiredToken veut dire la session de signature ; SignatureDoesNotMatch veut dire que la requête diffère de ce qui a été signé. Le corps XML vous dit lequel — ne déboguez pas à l’aveugle.
  • Qu’est-ce qui a signé l’URL — les clés longue durée d’un utilisateur IAM, ou la session temporaire d’un rôle?
  • Combien de temps après la génération l’échec est-il survenu? Des échecs regroupés quelques heures après la génération, avant l’expiration demandée, pointent vers l’expiration de session.
  • Le client a-t-il modifié la requête — en-têtes ajoutés, type de contenu changé, redirection suivie vers une autre région?

Comment vérifier

Générez une URL saine et inspectez ses paramètres :

# Présignature GET simple depuis la CLI
aws s3 presign s3://my-bucket/report.pdf --expires-in 3600

Décodez ce qu’une URL défaillante prétend : X-Amz-Date (moment de signature), X-Amz-Expires (fenêtre en secondes), X-Amz-Credential (qui a signé, quelle portée région/date), et — l’indice — la présence de X-Amz-Security-Token signifie que des identifiants temporaires l’ont signée. Puis regardez l’échec réel :

# Le corps d'erreur XML est le diagnostic; -i montre le statut
curl -si "https://my-bucket.s3.ca-central-1.amazonaws.com/report.pdf?X-Amz-..." | head -30

Vérifiez sous quelle identité votre chemin de génération roule vraiment :

aws sts get-caller-identity
# Sur EC2 : la session du rôle d'instance a-t-elle encore des heures devant elle?
aws sts get-session-token 2>/dev/null || echo "identifiants de rôle, la rotation s'applique"

Les événements de données CloudTrail (si activés sur le bucket) montrent le GetObject échoué avec le code d’erreur — utile quand le client défaillant est le navigateur d’un client que vous ne pouvez pas instrumenter.

Le runbook

  1. Alignez l’expiration sur le signataire. Si les URL doivent vivre plus d’une heure ou deux, signez-les avec des identifiants dont vous contrôlez la durée de vie : un utilisateur IAM dédié à la présignature (clés longue durée, politique étroitement limitée), ou acceptez l’horizon plus court et régénérez. Pour les URL signées par un rôle, traitez le plafond pratique comme la durée de session restante, pas 7 jours.
  2. Générez à la demande, pas d’avance. Le patron qui élimine toute la classe de problèmes : le client demande une URL à votre API au moment de l’utiliser, en reçoit une valide quelques minutes, et l’utilise immédiatement. L’expiration courte devient un atout de sécurité plutôt qu’un bogue.
  3. Pour les téléversements PUT, signez ce que le client enverra. Si vous signez Content-Type: image/jpeg, le navigateur doit envoyer exactement ça. Fixez-le dans le code client ou laissez le type de contenu hors de la signature (selon le SDK) — les écarts produisent SignatureDoesNotMatch.
  4. Fixez la région. Signer pour us-east-1 et requêter contre un point de terminaison de bucket en ca-central-1 échoue. Configurez le client SDK avec la vraie région du bucket ; ne comptez pas sur les redirections.
  5. N’oubliez pas KMS. Pour les objets SSE-KMS, le signataire a besoin de kms:Decrypt (GET) ou kms:GenerateDataKey (PUT) au moment de l’utilisation. Un signataire qui perd l’accès à la clé invalide ses URL en circulation.
  6. Vérifiez le décalage d’horloge pour les 403 quasi instantanés. Des URL qui échouent immédiatement après génération avec des erreurs de style expiration signifient habituellement que l’horloge de l’hôte générateur est décalée de quelques minutes — corrigez NTP.

Quand c’est la mauvaise approche

Les URL présignées sont des octrois par objet, pour une seule opération. Si vous en générez des milliers par chargement de page, ou tentez d’accorder la navigation d’un préfixe entier, il vous faut plutôt des témoins signés CloudFront, un proxy applicatif ou des S3 Access Points. Et une URL présignée est un jeton au porteur — quiconque la détient a l’accès, alors les URL dans un courriel ou les liens longue durée méritent un vrai point de téléchargement authentifié, pas une présignature de 7 jours.

Erreurs fréquentes

  • Demander --expires-in 604800 depuis une Lambda et s’attendre à ce que l’URL survive la semaine — la session de la fonction meurt avant.
  • Traiter SignatureDoesNotMatch comme un problème d’expiration et régénérer en boucle, alors que le client envoie un en-tête qui n’a pas été signé.
  • Intégrer des URL présignées dans des réponses d’API mises en cache ou des pages en cache CDN, si bien que les clients reçoivent des URL expirées dans le cache.
  • Faire la rotation des clés d’accès de l’utilisateur IAM de présignature et supprimer la vieille clé immédiatement — chaque URL en circulation signée avec elle meurt à cet instant ; faites chevaucher les clés pour la plus longue expiration que vous émettez.

Prévention

  • Standardisez un seul chemin de présignature dans votre base de code — une fonction, une identité, une politique d’expiration — plutôt que des appels SDK éparpillés.
  • Journalisez la portée X-Amz-Credential avec chaque URL générée pour qu’un rapport de 403 se retrace au signataire et au moment de signature en quelques secondes.
  • Alertez sur les pointes de 403 sur les chemins présignés ; elles marquent habituellement un événement de rotation ou un déploiement qui a changé l’identité de signature.
  • Gardez les expirations aussi courtes que le flux de travail le tolère — des minutes pour les flux interactifs — et documentez le comportement SigV4 pour l’équipe à partir de la source sur https://docs.aws.amazon.com/AmazonS3/ .

Une fois qu’on a intégré qu’une URL présignée n’est durable que tant que les identifiants de son signataire le sont, les 403 cessent d’être mystérieux et deviennent prévisibles. Si personne dans votre équipe ne possède vraiment l’environnement AWS de bout en bout, c’est la racine de la plupart de ces problèmes — Stack Harbor opère des comptes AWS au quotidien sous Opérations AWS, et vous pouvez réserver une revue AWS.