Autoriser l’accès temporaire à un fichier

CloudflareBeginner
Pratiquer maintenant

Introduction

Un agent du support doit partager brièvement un export synthétique privé, sans rendre son compartiment public ni distribuer des identifiants de stockage. Vous allez générer une URL de téléchargement présignée, observer son expiration et vérifier que l’identifiant qui l’a signée ne peut pas accéder à un autre compartiment appartenant au laboratoire.

Terminez d’abord les laboratoires Organiser un compartiment de documents et Diffuser des documents avec un Worker. Cette machine virtuelle indépendante utilise Node.js 22.22.0, Wrangler 4.131.1 et AWS SDK 3.888.0, dans des versions fixées. Le compte doit disposer de R2 actif et avoir l’autorisation de créer un jeton d’objet utilisateur limité. Seuls deux petits fichiers synthétiques sont utilisés. Consultez la tarification de R2 et l’accès présigné ; aucun domaine acheté n’est requis. Ne partagez pas les liens ni les identifiants générés. Supprimez les deux compartiments et révoquez uniquement les jetons de ce laboratoire avant de partir.

Créer des compartiments isolés pour tester l’accès

Dans cette étape, vous autorisez cette machine virtuelle et créez un compartiment temporaire. L’autorisation de l’appareil confirme votre compte d’apprentissage. La gestion des compartiments R2 utilise un jeton d’API distinct, limité à ce compte.

Lancez Bash pour utiliser la syntaxe des commandes ci-dessous, puis accédez au projet préparé et vérifiez les outils disponibles. Gardez ce même terminal ouvert afin que les variables contenant les noms de ressources restent disponibles :

bash
cd /home/labex/project/r2-lab
export PATH="$PWD/.tools/node-v22.22.0-linux-x64/bin:$PATH"
node --version
npx wrangler --version

Dans votre propre navigateur, autorisez le code d’appareil affiché. Vérifiez le compte d’apprentissage ainsi que les autorisations de lecture du compte et de l’utilisateur demandées avant d’accepter :

npx wrangler login --device --browser=false --scopes account:read user:read
npx wrangler whoami --json

Vérifiez que loggedIn: true est présent. Lisez le nom du compte, même si un seul compte est affiché. Remplacez YOUR_ACCOUNT_ID ci-dessous par l’identifiant réel de 32 caractères de ce compte. openssl rand -hex 6 génère douze caractères hexadécimaux aléatoires afin d’éviter toute collision avec une exécution précédente. Le document here-doc écrit un fichier de configuration standard ; le shell y remplace vos variables.

ACCOUNT_ID=YOUR_ACCOUNT_ID
RUN_ID=$(openssl rand -hex 6)
NAME="labex-c05-r04-$RUN_ID"
BUCKET="$NAME-docs"
cat > wrangler.jsonc <<JSON
{"name":"$NAME","account_id":"$ACCOUNT_ID","compatibility_date":"2026-07-30","r2_buckets":[{"binding":"DOCUMENTS","bucket_name":"$BUCKET"}]}
JSON

Pour gérer les compartiments, ouvrez la page API Tokens de votre profil Cloudflare et créez un jeton personnalisé portant le nom de ce laboratoire. Accordez l’autorisation Account → Workers R2 Storage → Edit et limitez Account Resources au compte d’apprentissage dont vous avez enregistré l’identifiant. Définissez une expiration courte. N’incluez pas d’autres comptes ni d’autorisations sans rapport. Cette autorisation au niveau du compte permet de créer et de supprimer des compartiments ; le jeton limité aux objets de l’étape suivante ne le permet pas.

Copiez le jeton une seule fois dans cette invite cachée de la machine virtuelle. umask 077 limite l’accès au fichier à votre utilisateur ; read -s masque la saisie. Le fichier utilise la variable de jeton standard de Wrangler et est exclu de Git.

umask 077
read -r -s -p 'R2 management API token: ' R2_MANAGEMENT_TOKEN; printf '\n'
printf 'CLOUDFLARE_API_TOKEN=%s\n' "$R2_MANAGEMENT_TOKEN" > .env.management
unset R2_MANAGEMENT_TOKEN

Utilisez --env-file=.env.management uniquement pour les commandes de gestion R2 ; la commande whoami continue de vérifier l’autorisation de l’appareil sur la machine virtuelle.

Placez --env-file à la fin de chaque commande Wrangler pour que sa liste de fichiers ne soit pas étendue au nom de la commande. Après la création de chaque compartiment, si Wrangler propose d’ajouter une liaison à la configuration, saisissez n et appuyez sur Entrée. La configuration contient déjà la liaison prévue.

npx wrangler r2 bucket create "$BUCKET" --env-file=.env.management

Listez vos compartiments et recherchez le nom généré exact. Les autres compartiments appartiennent à d’autres travaux ; ne les modifiez pas.

npx wrangler r2 bucket list --env-file=.env.management

Dans le Dashboard, ouvrez Storage & databases → R2 → Overview, sélectionnez ce compartiment précis et vérifiez que la liste des objets est vide. Dans ses paramètres, laissez l’URL de développement publique et les domaines personnalisés désactivés. Le nom d’un compartiment dans le Dashboard confirme son identité ; les vérifications de téléchargement ultérieures confirmeront les octets stockés.

Créez un deuxième compartiment appartenant à ce laboratoire. Il représente une limite de stockage indépendante ; ne remplacez pas ce compartiment par un compartiment d’un autre exercice. Avec l’identifiant de gestion, envoyez un fichier synthétique dans chaque compartiment :

CONTROL="$NAME-control"
npx wrangler r2 bucket create "$CONTROL" --env-file=.env.management
npx wrangler r2 object put "$BUCKET/documents/report.txt" --remote --file document.txt --content-type text/plain --env-file=.env.management
npx wrangler r2 object put "$CONTROL/retained/handbook.txt" --remote --file retained.txt --content-type text/plain --env-file=.env.management

Laissez l’accès public désactivé pour les deux compartiments. À l’étape suivante, vous autoriserez le nouvel identifiant à lire uniquement le compartiment de documents.

Limiter l’identifiant de signature

Dans cette étape, vous créez un identifiant en lecture seule limité à un seul compartiment. Une URL présignée ne peut pas accorder davantage d’accès que l’identifiant utilisé pour la signer. Le jeton de gestion peut administrer les deux compartiments du laboratoire ; il ne doit donc pas servir d’identité de signature dans cette leçon.

L’API compatible S3 permet aux SDK de stockage standard d’accéder à R2. Elle utilise une paire de clés d’accès distincte, et non le jeton d’appareil de Wrangler. Dans R2 Overview, utilisez Account Details → API Tokens → Manage, puis créez un User API token portant le nom de ressource généré pour ce laboratoire. Choisissez Object Read only, limitez le jeton à ce nouveau compartiment précis et sélectionnez une courte expiration si le formulaire le propose. Ne choisissez ni tous les compartiments ni l’accès Admin. Gardez cette page de jeton ouverte jusqu’à ce que vous ayez enregistré le secret à usage unique.

Utilisez les invites Bash suivantes dans la machine virtuelle. read -s masque la saisie ; umask 077 rend le fichier d’identifiants lisible uniquement par votre utilisateur. Ces noms sont les variables d’environnement standard d’AWS SDK. Collez l’Access Key ID et la Secret Access Key dans leurs invites respectives, puis appuyez sur Entrée. Ne collez pas la valeur du jeton d’API général.

umask 077
read -r -s -p 'Access Key ID: ' AWS_ACCESS_KEY_ID; printf '\n'
read -r -s -p 'Secret Access Key: ' AWS_SECRET_ACCESS_KEY; printf '\n'
printf 'AWS_ACCESS_KEY_ID=%s\nAWS_SECRET_ACCESS_KEY=%s\n' "$AWS_ACCESS_KEY_ID" "$AWS_SECRET_ACCESS_KEY" > .env.s3
unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY

Écrivez un client SDK standard réutilisable. Le SDK exige une chaîne de région ; R2 utilise auto. La lecture de la configuration existante garantit que les opérations de la CLI et du SDK ciblent le même compte et le même compartiment.

cat > storage.mjs <<'JS'
import { S3Client } from "@aws-sdk/client-s3";
import { readFileSync } from "node:fs";
const config = JSON.parse(readFileSync("wrangler.jsonc", "utf8"));
export const Bucket = config.r2_buckets[0].bucket_name;
export const s3 = new S3Client({
  region: "auto",
  endpoint: `https://${config.account_id}.r2.cloudflarestorage.com`,
  credentials: {
    accessKeyId: process.env.AWS_ACCESS_KEY_ID,
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
  }
});
JS

Sélectionnez uniquement le compartiment -docs ; laissez le compartiment -control en dehors du périmètre de ce jeton. Le client storage.mjs existant ne lit .env.s3 que si --env-file=.env.s3 est présent. N’utilisez ni l’autorisation Admin ni la portée couvrant tous les compartiments.

Avant de partager une URL, confirmez le compartiment sélectionné dans le formulaire du jeton. La requête réelle vers le compartiment de contrôle, à l’étape suivante, vérifiera indépendamment que cette limite fonctionne.

Jeton en lecture seule limité au compartiment de documents

Cet exemple sélectionne Object Read only et uniquement le compartiment -docs généré ; le compartiment -control est exclu. Le nom généré sera différent. Le TTL de 24 heures du jeton est distinct de la validité de cinq minutes ou cinq secondes de chaque URL signée. Le formulaire indique le périmètre choisi ; les requêtes distantes acceptées et rejetées prouvent les permissions réelles.

Signer et tester des téléchargements temporaires

Dans cette étape, vous signez des requêtes GET pour un objet et une période précis. La signature s’effectue localement, sans contacter R2 ; la création d’une URL ne prouve donc pas que l’autorisation fonctionne. R2 évalue la signature lorsqu’un client utilise l’URL. Toute personne qui possède l’URL peut utiliser l’opération qu’elle autorise jusqu’à son expiration ; ne mettez donc pas ces liens dans des journaux, des messages ou des captures d’écran.

Écrivez un petit programme de signature. expiresIn est exprimé en secondes. Le fichier stocke une URL valide cinq minutes, une URL valide cinq secondes et une URL délibérément hors périmètre, sans les afficher :

cat > sign.mjs <<'JS'
import { GetObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import { readFileSync, writeFileSync } from "node:fs";
import { s3, Bucket } from "./storage.mjs";
const config = JSON.parse(readFileSync("wrangler.jsonc", "utf8"));
const command = new GetObjectCommand({ Bucket, Key: "documents/report.txt" });
const allowed = await getSignedUrl(s3, command, { expiresIn: 300 });
const short = await getSignedUrl(s3, command, { expiresIn: 5 });
const outside = await getSignedUrl(s3, new GetObjectCommand({
  Bucket: config.name + "-control", Key: "retained/handbook.txt"
}), { expiresIn: 300 });
const unsigned = new URL(allowed); unsigned.search = "";
writeFileSync("signed.json", JSON.stringify({ allowed, short, outside, unsigned: unsigned.href }), { mode: 0o600 });
console.log("Stored temporary request URLs in private signed.json");
JS
node --env-file=.env.s3 sign.mjs

Utilisez rapidement l’URL autorisée. Cette requête curl ne contient aucun en-tête Authorization : la signature est portée par l’URL. node -p fournit directement à curl l’URL enregistrée, sans l’afficher dans la sortie du terminal.

curl -fsS "$(node -p "require('./signed.json').allowed")" -o signed-download.txt
cmp document.txt signed-download.txt

Les octets doivent être identiques. Attendez maintenant dix secondes pour que l’URL valide cinq secondes expire, puis examinez les réponses de refus. Ces requêtes omettent volontairement --fail afin d’afficher les erreurs HTTP :

sleep 10
curl -sS -o expired.xml -w "%{http_code}\n" "$(node -p "require('./signed.json').short")"
curl -sS -o unsigned.xml -w "%{http_code}\n" "$(node -p "require('./signed.json').unsigned")"
curl -sS -o outside.xml -w "%{http_code}\n" "$(node -p "require('./signed.json').outside")"

Les requêtes expirées et hors périmètre doivent renvoyer 403. La requête non signée vers ce point de terminaison S3 de R2 renvoie 400 avec InvalidArgument et le message Authorization : les informations de signature requises sont absentes. Ce cas diffère du refus d’accès à un autre compartiment pour des identifiants valablement signés. Examinez les noms des erreurs sans afficher les documents de réponse complets, car ils peuvent contenir des détails sur les requêtes :

python3 - <<'PYXML'
from xml.etree import ElementTree
for path in ["expired.xml", "unsigned.xml", "outside.xml"]:
    root = ElementTree.parse(path).getroot()
    print(path, root.findtext("Code"))
PYXML

La réponse liée à l’expiration doit l’indiquer, tandis que le compartiment de contrôle doit renvoyer AccessDenied. Une défaillance réseau ne constitue pas un résultat d’autorisation. Si plus de cinq minutes se sont écoulées avant le test du compartiment de contrôle, régénérez les URL, répétez les requêtes autorisée et hors périmètre, puis laissez de nouveau l’URL courte expirer.

Distinguer l’autorisation d’une URL de l’accès public

Dans cette étape, vous reliez les requêtes observées au modèle d’accès. Le lien fonctionnel a autorisé un GET signé pour une seule clé. Il n’a pas activé l’URL de développement publique du compartiment, n’a pas autorisé PUT et n’a pas connecté un utilisateur à votre application.

Ouvrez les paramètres des deux compartiments appartenant au laboratoire dans le Dashboard. Laissez les URL de développement publiques désactivées et aucun domaine personnalisé configuré. Sur la page du jeton d’objet, confirmez que le jeton en lecture seule couvre uniquement le compartiment -docs. Ne révélez pas son secret à usage unique dans une capture d’écran.

Exécutez maintenant la vérification de la plateforme. Elle génère ses propres requêtes signées, exige les octets autorisés exacts et vérifie les refus pour les requêtes non signées, expirées et hors périmètre. Il s’agit d’une preuve réelle provenant de l’API distante ; le fichier signed.json n’est pas un indicateur de réussite.

Dans une application de production, déterminez si un utilisateur peut accéder au document avant d’émettre un lien signé. Une fois émis, le lien porteur peut être copié. Le laboratoire ultérieur consacré à l’autorisation applicative traite directement cette décision. Les URL S3 signées ciblent le point de terminaison S3 R2 du compte, et non une adresse publique r2.dev ou un domaine personnalisé.

Nettoyer les deux compartiments du laboratoire

Dans cette étape, vous supprimez les deux clés de fichiers explicites, puis leurs compartiments. Gardez l’identifiant de gestion actif jusqu’à ce que la plateforme confirme l’absence des deux noms de compartiment.

BUCKET=$(node -p "JSON.parse(require('fs').readFileSync('wrangler.jsonc')).r2_buckets[0].bucket_name")
CONTROL=$(node -p "JSON.parse(require('fs').readFileSync('wrangler.jsonc')).name + '-control'")
npx wrangler r2 object delete "$BUCKET/documents/report.txt" --remote --env-file=.env.management
npx wrangler r2 object delete "$CONTROL/retained/handbook.txt" --remote --env-file=.env.management
npx wrangler r2 bucket delete "$BUCKET" --env-file=.env.management
npx wrangler r2 bucket delete "$CONTROL" --env-file=.env.management
npx wrangler r2 bucket list --env-file=.env.management

Confirmez chaque nom exact lorsque l’invite vous le demande. Une liste de compartiments réussie doit omettre les deux noms. Ne supprimez jamais un autre compartiment pour vider l’inventaire global.

Révoquer l’identifiant du laboratoire et se déconnecter

Dans cette étape, vous fermez les accès laissés par cet exercice. Sur la page R2 API Tokens, révoquez uniquement le jeton d’objet nommé pour ce laboratoire. Sur la page API Tokens de votre profil, révoquez le jeton de gestion R2 distinct que vous avez créé pour ce laboratoire. Supprimer un compartiment ne révoque pas un jeton, et la déconnexion de Wrangler ne révoque pas les identifiants S3.

Après la révocation, supprimez le fichier d’identifiants local et déconnectez cette machine virtuelle :

rm .env.s3 .env.management signed.json
npx wrangler logout

Examinez l’identité structurée. Son statut différent de zéro est attendu après la déconnexion :

npx wrangler whoami --json || true

Vérifiez que loggedIn: false est présent ; conservez votre session de connexion habituelle au Dashboard. La plateforme vérifie la suppression des identifiants locaux et la déconnexion de Wrangler. Les deux révocations de jetons sont des vérifications manuelles dans le Dashboard pour ce laboratoire ; elles ne sont pas déduites de la suppression des fichiers.

Résumé

Créer un accès limité à des fichiers privés, vérifier le fonctionnement des URL présignées et leurs limites de refus, distinguer l’accès public de l’autorisation et révoquer les identifiants du laboratoire.