Introducción
Una página de carga del navegador puede fallar aunque una URL prefirmada funcione con curl. El navegador también comprueba si el servicio de almacenamiento permite el origen de la página. En este laboratorio configurará ese límite CORS independiente para una página de carga sintética proporcionada, manteniendo privado el bucket.
Complete primero Authorize Temporary File Access. Esta VM nueva proporciona Node.js 22.22.0, Wrangler 4.131.1 y AWS SDK 3.888.0 fijados, además de la estructura completa y no relacionada de una página de carga. Creará un bucket privado nuevo, una credencial de objeto de corta duración y una página de Worker desechable. R2 debe estar activo; consulte R2 pricing y CORS behavior. No necesita un dominio comprado. Use únicamente el texto sintético proporcionado; elimine todos los recursos y revoque los tokens del laboratorio.
Conectar el bucket de la aplicación
En este paso, autorizará esta VM y creará un bucket privado independiente para la aplicación. La autorización del dispositivo confirma su cuenta de aprendizaje. La administración de buckets de R2 utiliza un token de API independiente, restringido a esa cuenta.
Inicie Bash para usar la sintaxis de comandos que se muestra a continuación. Después, vaya al proyecto preparado y compruebe sus herramientas. Mantenga abierto este mismo terminal para conservar disponibles las variables con los nombres de los recursos:
bash
cd /home/labex/project/r2-lab
export PATH="$PWD/.tools/node-v22.22.0-linux-x64/bin:$PATH"
node --version
npx wrangler --version
Autorice el código de dispositivo mostrado en su propio navegador. Confirme la cuenta de aprendizaje y los ámbitos de lectura de la cuenta y del usuario solicitados antes de conceder el consentimiento:
npx wrangler login --device --browser=false --scopes account:read user:read workers_scripts:write workers_kv:write
npx wrangler whoami --json
Exija loggedIn: true. Lea el nombre de la cuenta aunque solo aparezca una. Sustituya YOUR_ACCOUNT_ID a continuación por el ID real de 32 caracteres de esa cuenta. openssl rand -hex 6 genera doce caracteres hexadecimales aleatorios para evitar colisiones con ejecuciones anteriores de este laboratorio. El documento aquí genera un archivo de configuración estándar; el shell sustituye en él sus variables.
ACCOUNT_ID=YOUR_ACCOUNT_ID
RUN_ID=$(openssl rand -hex 6)
NAME="labex-c05-r05-$RUN_ID"
BUCKET="$NAME-docs"
cat > wrangler.jsonc <<JSON
{"name":"$NAME","account_id":"$ACCOUNT_ID","main":"src/index.js","workers_dev":true,"compatibility_date":"2026-07-30","r2_buckets":[{"binding":"DOCUMENTS","bucket_name":"$BUCKET"}]}
JSON
Para administrar el bucket, abra la página API Tokens de su perfil de Cloudflare y cree un token personalizado con el nombre de este laboratorio. Conceda Account → Workers R2 Storage → Edit y restrinja Account Resources a la cuenta de aprendizaje cuyo ID guardó. Establezca una caducidad breve. No incluya otras cuentas ni permisos no relacionados. Este permiso de nivel de cuenta permite crear y eliminar buckets; el token limitado a objetos del paso siguiente no puede hacerlo.
Copie el token una sola vez en este prompt oculto de la VM. umask 077 restringe el archivo a su usuario; read -s oculta la entrada. El archivo utiliza la variable de token estándar de Wrangler y queda excluido 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
Use --env-file=.env.management únicamente para los comandos de administración de R2; el whoami normal seguirá comprobando la autorización del dispositivo de la VM.
Coloque --env-file al final de cada comando de Wrangler para que su lista de archivos no incluya el nombre del comando. Después de crear cada bucket, si Wrangler pregunta si debe añadir un enlace a la configuración, escriba n y pulse Enter. La configuración ya contiene el enlace previsto.
npx wrangler r2 bucket create "$BUCKET" --env-file=.env.management
Muestre sus buckets y busque el nombre generado exacto. Los demás buckets pertenecen a otros trabajos; no los modifique.
npx wrangler r2 bucket list --env-file=.env.management
En Dashboard, abra Storage & databases → R2 → Overview, seleccione este bucket exacto y compruebe que la lista de objetos esté vacía. En su configuración, mantenga desactivadas la URL pública de desarrollo y los dominios personalizados. El nombre del bucket en Dashboard confirma su identidad; las comprobaciones de descarga posteriores demostrarán cuáles son los bytes almacenados.
El permiso para scripts de Worker permite realizar la implementación. El permiso de KV permite que Wrangler lleve el registro necesario para la eliminación; este laboratorio no crea ningún espacio de nombres de KV. El token de administración de R2 sigue siendo una credencial independiente, limitada a la cuenta.
Preparar la página de carga y el acceso para firmar
En este paso, implementará la página proporcionada y dará al terminal una credencial limitada a este bucket. La página contiene únicamente un contenido sintético fijo y un campo de URL vacío; nunca recibe el secreto de firma de larga duración.
La API compatible con S3 permite que los SDK de almacenamiento estándar accedan a R2. Utiliza un par de claves de acceso independiente en lugar del token de dispositivo de Wrangler. En R2 Overview, use Account Details → API Tokens → Manage y cree un User API token con el nombre del recurso generado para este laboratorio. Elija Object Read & Write, restrínjalo a este bucket nuevo exacto y seleccione una caducidad breve si el formulario ofrece esa opción. No elija todos los buckets ni el acceso de administrador. Mantenga abierta esta página del token hasta guardar el secreto de un solo uso.
Use los siguientes prompts de Bash en la VM. read -s oculta la entrada; umask 077 hace que el archivo de credenciales solo sea legible por su usuario. Estos nombres son las variables de entorno estándar del AWS SDK. Pegue el Access Key ID y el Secret Access Key en sus respectivos prompts y pulse Enter. No pegue el valor del token de API general.
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
Escriba un cliente reutilizable para el SDK estándar. El SDK requiere una cadena de región; R2 utiliza auto. Leer la configuración existente mantiene las operaciones de la CLI y del SDK dirigidas a la misma cuenta y al mismo bucket.
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",
requestChecksumCalculation: "WHEN_REQUIRED",
endpoint: `https://${config.account_id}.r2.cloudflarestorage.com`,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
}
});
JS
requestChecksumCalculation: "WHEN_REQUIRED" evita firmar una suma de comprobación de un cuerpo vacío antes de que el navegador aporte el contenido. La URL sigue firmando el objeto y la operación; la llamada de firma siguiente incluye Content-Type explícitamente. La comprobación final compara los bytes almacenados.
Publique la página proporcionada:
npx wrangler deploy
Copie la URL HTTPS de la implementación en ORIGIN, sin ruta ni barra final. Un origin consta del esquema, el nombre de host y el puerto; https://example.com es distinto de http://example.com. Guárdelo para la comprobación independiente posterior:
ORIGIN=https://YOUR_WORKER.YOUR_SUBDOMAIN.workers.dev
printf "%s\n" "$ORIGIN" > origin.txt
Abra esa URL en el navegador. Debe aparecer la página Synthetic export upload con el campo de URL temporal vacío. Todavía no cargue ningún archivo; el bucket aún no permite este origen del navegador.
Permitir un origen del navegador
En este paso, configurará Cross-Origin Resource Sharing (CORS). Antes de enviar un PUT entre orígenes, el navegador pregunta al bucket si se permiten el origen, el método y las cabeceras de la página. Esa solicitud OPTIONS es un preflight. CORS controla el acceso del navegador; no sustituye la autorización de almacenamiento de la operación firmada.
Escriba una política usando el origen real de la página. El terminal sustituirá $ORIGIN en el JSON. Wrangler utiliza el formato rules/allowed en minúsculas de Cloudflare, en lugar del formato CORSRules de AWS:
cat > cors.json <<JSON
{"rules":[{"allowed":{"origins":["$ORIGIN"],"methods":["PUT"],"headers":["content-type"]},"exposeHeaders":["ETag"],"maxAgeSeconds":60}]}
JSON
La página proporcionada solo necesita PUT y Content-Type. exposeHeaders permite que JavaScript lea el ETag devuelto, y el valor breve de maxAgeSeconds reduce la caché de preflight durante las pruebas.
npx wrangler r2 bucket cors set "$BUCKET" --file cors.json --env-file=.env.management
npx wrangler r2 bucket cors list "$BUCKET" --env-file=.env.management
Confirme que sobrescribe la política únicamente para este bucket recién creado. La lista resultante debe mostrar su origen exacto. En la configuración del bucket en Dashboard, inspeccione la política CORS en modo de solo lectura y mantenga desactivado el acceso público.

Ejemplo: la política permite este origen, PUT y content-type. Tu nombre de host generado será distinto. La salida de la CLI también comprueba ETag expuesto y la duración de la caché.
Cargar mediante el navegador
En este paso, combinará el permiso PUT firmado con la política de origen del navegador. El firmante explicado anteriormente crea una URL exactamente para uploads/browser.txt; la página envía el mismo Content-Type utilizado al firmar.
cat > sign-upload.mjs <<'JS'
import { PutObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import { writeFileSync } from "node:fs";
import { s3, Bucket } from "./storage.mjs";
const url = await getSignedUrl(s3, new PutObjectCommand({
Bucket, Key: "uploads/browser.txt", ContentType: "text/plain"
}), { expiresIn: 300, signableHeaders: new Set(["content-type"]) });
writeFileSync("put-url.txt", url, { mode: 0o600 });
console.log("Prepared a five-minute PUT URL");
JS
node --env-file=.env.s3 sign-upload.mjs
Muestre este enlace temporal solo durante el tiempo necesario para copiarlo en la página proporcionada. No lo comparta, no lo incluya en capturas de pantalla ni pegue el secreto S3 en su lugar:
cat put-url.txt
Pegue el enlace en Temporary PUT URL y seleccione Upload synthetic text en un plazo de cinco minutos. Exija un resultado HTTP 2xx correcto y un ETag visible. Después de la carga, borre el contenido del campo de URL. Una URL caducada puede aparecer como un error CORS genérico del navegador porque las respuestas de error pueden no incluir cabeceras CORS; si es necesario, genere un enlace nuevo.
Descargue el objeto real mediante Wrangler y compare el contenido fijo:
npx wrangler r2 object get "$BUCKET/uploads/browser.txt" --remote --file browser-download.txt --env-file=.env.management
printf "Synthetic browser export.\n" > expected-browser.txt
cmp expected-browser.txt browser-download.txt
La comprobación de la plataforma lee de forma independiente los bytes y el tipo de contenido. La interacción real con el navegador sigue siendo necesaria como evidencia para este laboratorio; curl por sí solo no aplica CORS del navegador.

El navegador real devolvió HTTP 200 y un ETag legible; el campo de URL temporal está vacío. La página muestra \n literalmente, pero carga un salto de línea real. La descarga independiente confirma el contenido exacto de 26 bytes.
Separar la política de origen del permiso de firma
En este paso, inspeccionará un preflight permitido y otro no permitido sin crear otro objeto. Estas solicitudes curl preguntan qué puede hacer el navegador; curl no aplica esa respuesta por sí mismo.
ACCOUNT_ID=$(node -p "JSON.parse(require('fs').readFileSync('wrangler.jsonc')).account_id")
OBJECT_URL="https://$ACCOUNT_ID.r2.cloudflarestorage.com/$BUCKET/uploads/browser.txt"
curl -i -X OPTIONS -H "Origin: $ORIGIN" -H "Access-Control-Request-Method: PUT" -H "Access-Control-Request-Headers: content-type" "$OBJECT_URL"
Exija que Access-Control-Allow-Origin coincida con el origen exacto de la página. Ahora realice la solicitud desde un origen no relacionado:
curl -i -X OPTIONS -H "Origin: https://outside.example" -H "Access-Control-Request-Method: PUT" -H "Access-Control-Request-Headers: content-type" "$OBJECT_URL"
La respuesta no debe conceder acceso a ese origen, ni a *. El estado exacto de una respuesta de denegación puede variar; lo importante para impedir el acceso del navegador es que falte la concesión. Por último, intente realizar una lectura sin firmar:
curl -sS -o unsigned.xml -w "%{http_code}\n" "$OBJECT_URL"
Exija 400 con Code XML InvalidArgument y Message Authorization, como se observó para una solicitud sin firma a este endpoint S3 de R2. Inspeccione ambos campos; un error cualquiera o de red no demuestra rechazo de firma.
python3 - <<'PYXML'
from xml.etree import ElementTree
root = ElementTree.parse("unsigned.xml").getroot()
print("Code:", root.findtext("Code"))
print("Message:", root.findtext("Message"))
PYXML
Activar CORS no hizo público el bucket ni eliminó las comprobaciones de firma. Un cliente que no sea un navegador y tenga una firma válida aún puede utilizar la operación concedida, independientemente de la cabecera Origin; las cadenas de origen no son credenciales de identidad.
Eliminar los recursos y las credenciales de carga
En este paso, eliminará el Worker de la interfaz, el objeto exacto y el bucket. Confirme que los recursos ya no existen antes de revocar las credenciales.
npx wrangler delete
npx wrangler r2 object delete "$BUCKET/uploads/browser.txt" --remote --env-file=.env.management
npx wrangler r2 bucket delete "$BUCKET" --env-file=.env.management
npx wrangler r2 bucket list --env-file=.env.management
Confirme únicamente los nombres generados para este laboratorio. Actualice la lista de Workers y la lista de buckets en Dashboard. Ejecute la comprobación de limpieza de la plataforma mientras la autorización de administración siga activa. La configuración CORS del bucket se elimina junto con el bucket.
Revocar la credencial del laboratorio y cerrar sesión
En este paso, cerrará el acceso que dejó este ejercicio. En la página R2 API Tokens, revoque únicamente el token de objetos cuyo nombre corresponde a este laboratorio. En la página API Tokens de su perfil, revoque el token de administración de R2 independiente que creó para este laboratorio. Eliminar un bucket no revoca un token, y cerrar sesión en Wrangler no revoca las credenciales de S3.
Después de la revocación, elimine el archivo local de credenciales y cierre la sesión de esta VM:
rm .env.s3 .env.management put-url.txt
npx wrangler logout
Inspeccione la identidad estructurada. Su estado distinto de cero es esperado cuando ha cerrado sesión:
npx wrangler whoami --json || true
Exija loggedIn: false; mantenga abierta su sesión normal de Dashboard. La plataforma comprueba la eliminación de las credenciales locales y el cierre de sesión de Wrangler. Las dos revocaciones de tokens son puntos de comprobación manuales en Dashboard en esta versión candidata; no se deducen de la eliminación de archivos.
Resumen
Configure CORS para un origen exacto, cargue bytes sintéticos con una URL prefirmada, distinga los permisos del navegador de la autorización y elimine los recursos.



