Introdução
Uma página de upload no navegador pode falhar mesmo quando uma URL pré-assinada funciona com curl. O navegador também verifica se o serviço de armazenamento permite a origem da página. Você configurará esse limite independente de CORS para uma página de upload sintética fornecida, mantendo o bucket privado.
Conclua primeiro Authorize Temporary File Access. Esta VM nova fornece Node.js 22.22.0, Wrangler 4.131.1 e AWS SDK 3.888.0 fixos, além de uma estrutura completa e não relacionada de página de upload. Você criará um bucket privado novo, uma credencial de objeto de curta duração e uma página descartável do Worker. O R2 já deve estar ativo; consulte preços do R2 e o comportamento do CORS. Não é necessário ter um domínio comprado. Use somente o texto sintético fornecido; remova todos os recursos e revogue os tokens do laboratório.
Conectar o bucket da aplicação
Nesta etapa, você autorizará esta VM e criará um bucket privado independente para a aplicação. A autorização do dispositivo confirma sua conta de aprendizagem. O gerenciamento de buckets do R2 usa um token de API separado, restrito a essa conta.
Inicie o Bash para usar a sintaxe de comandos mostrada abaixo. Em seguida, vá para o projeto preparado e verifique as ferramentas. Mantenha este mesmo terminal aberto para que as variáveis com os nomes dos recursos continuem disponíveis:
bash
cd /home/labex/project/r2-lab
export PATH="$PWD/.tools/node-v22.22.0-linux-x64/bin:$PATH"
node --version
npx wrangler --version
Autorize o código de dispositivo exibido no seu próprio navegador. Confirme a conta de aprendizagem e os escopos de leitura da conta e do usuário solicitados antes de conceder o consentimento:
npx wrangler login --device --browser=false --scopes account:read user:read workers_scripts:write workers_kv:write
npx wrangler whoami --json
Exija loggedIn: true. Leia o nome da conta mesmo que apenas uma conta seja listada. Substitua YOUR_ACCOUNT_ID abaixo pelo ID real de 32 caracteres dessa conta. openssl rand -hex 6 gera doze caracteres hexadecimais aleatórios para evitar que este laboratório entre em conflito com uma execução anterior. O here-document grava um arquivo de configuração padrão; o shell substitui nele as suas variáveis.
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 gerenciar o bucket, abra a página API Tokens do seu perfil da Cloudflare e crie um token personalizado com o nome deste laboratório. Conceda Account → Workers R2 Storage → Edit e restrinja Account Resources à conta de aprendizagem cujo ID você salvou. Defina uma expiração curta. Não inclua outras contas nem permissões não relacionadas. Essa permissão no nível da conta cria e exclui buckets; o token limitado a objetos da próxima etapa não pode fazer isso.
Copie o token uma única vez para este prompt oculto da VM. umask 077 restringe o arquivo ao seu usuário; read -s oculta a entrada. O arquivo usa a variável de token padrão do Wrangler e é excluído do 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 somente nos comandos de gerenciamento do R2; o whoami comum continuará verificando a autorização do dispositivo da VM.
Coloque --env-file no final de cada comando do Wrangler para que a lista de argumentos de arquivos não inclua o nome do comando. Depois de criar cada bucket, se o Wrangler perguntar se deve adicionar uma vinculação à configuração, digite n e pressione Enter. A configuração já contém a vinculação necessária.
npx wrangler r2 bucket create "$BUCKET" --env-file=.env.management
Liste seus buckets e encontre o nome exato gerado. Os outros buckets pertencem a outros trabalhos; não os altere.
npx wrangler r2 bucket list --env-file=.env.management
No Dashboard, abra Storage & databases → R2 → Overview, selecione exatamente esse bucket e verifique se a lista de objetos está vazia. Nas configurações, mantenha desativados o URL público de desenvolvimento e os domínios personalizados. O nome do bucket no Dashboard confirma a identidade; as verificações de download posteriores comprovarão os bytes armazenados.
A permissão para scripts do Worker permite a implantação. A permissão para KV permite que o Wrangler mantenha o controle das exclusões; este laboratório não cria nenhum namespace KV. O token de gerenciamento do R2 continua sendo uma credencial separada, com escopo de conta.
Preparar a página de upload e o acesso para assinatura
Nesta etapa, você implantará a página fornecida e dará ao terminal uma credencial limitada a este bucket. A página contém somente um payload sintético fixo e um campo de URL vazio; ela nunca recebe o segredo de assinatura de longa duração.
A API compatível com S3 permite que SDKs de armazenamento padrão acessem o R2. Ela usa um par de chave de acesso separado, em vez do token de dispositivo do Wrangler. Em R2 Overview, use Account Details → API Tokens → Manage e crie um User API token com o nome do recurso gerado para este laboratório. Escolha Object Read & Write, restrinja o token exatamente a este bucket novo e selecione uma expiração curta, se o formulário oferecer essa opção. Não escolha todos os buckets nem o acesso de administrador. Mantenha esta página do token disponível até armazenar o segredo de uso único.
Use os prompts Bash a seguir na VM. read -s oculta a entrada; umask 077 faz com que o arquivo de credenciais possa ser lido somente pelo seu usuário. Estes são os nomes padrão das variáveis de ambiente do AWS SDK. Cole o Access Key ID e o Secret Access Key nos respectivos prompts e pressione Enter. Não cole o valor do token de API geral.
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
Grave um cliente reutilizável do SDK padrão. O SDK exige uma string de região; o R2 usa auto. A leitura da configuração existente mantém as operações da CLI e do SDK direcionadas à mesma conta e ao mesmo 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 assinar uma soma de verificação de corpo vazio antes de o navegador fornecer o conteúdo. A URL continua assinando o objeto e a operação; a chamada abaixo inclui Content-Type explicitamente. A verificação final compara os bytes armazenados.
Publique a página fornecida:
npx wrangler deploy
Copie o URL de implantação HTTPS para ORIGIN, sem caminho nem barra final. Uma origin consiste em esquema, nome do host e porta; https://example.com é diferente de http://example.com. Salve esse valor para a verificação independente posterior:
ORIGIN=https://YOUR_WORKER.YOUR_SUBDOMAIN.workers.dev
printf "%s\n" "$ORIGIN" > origin.txt
Abra esse URL no navegador. Exija a página Synthetic export upload com o campo de URL temporário vazio. Ainda não faça upload; o bucket ainda não permite essa origem do navegador.
Permitir uma origem do navegador
Nesta etapa, você configurará o Cross-Origin Resource Sharing (CORS). Antes de enviar um PUT entre origens, o navegador pergunta ao bucket se a origem, o método e os cabeçalhos da página são permitidos. Essa solicitação OPTIONS é um preflight. O CORS controla o acesso do navegador; ele não substitui a autorização da operação de armazenamento assinada.
Grave uma política usando a origem real da página. O terminal substituirá $ORIGIN no JSON. O Wrangler usa o formato rules/allowed em minúsculas da Cloudflare, e não o formato CORSRules da AWS:
cat > cors.json <<JSON
{"rules":[{"allowed":{"origins":["$ORIGIN"],"methods":["PUT"],"headers":["content-type"]},"exposeHeaders":["ETag"],"maxAgeSeconds":60}]}
JSON
A página fornecida precisa somente de PUT e Content-Type. exposeHeaders permite que o JavaScript leia o ETag retornado, e o valor curto de maxAgeSeconds reduz o cache do preflight durante os testes.
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 a substituição da política ocorrerá somente neste bucket recém-criado. A lista resultante deve mostrar exatamente a sua origem. Nas configurações do bucket no Dashboard, inspecione a política de CORS somente para leitura e mantenha o acesso público desativado.

Exemplo: a política permite esta origem, PUT e content-type. Seu nome de host gerado será diferente. A saída da CLI também verifica o ETag exposto e a duração do cache.
Fazer upload pelo navegador
Nesta etapa, você combinará a permissão de PUT assinada com a política de origem do navegador. O assinador ensinado anteriormente cria um URL exatamente para uploads/browser.txt; a página envia o mesmo Content-Type usado durante a assinatura.
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
Exiba este link temporário apenas pelo tempo necessário para copiá-lo para a página fornecida. Não o compartilhe, não o inclua em capturas de tela e não cole o segredo do S3 no lugar dele:
cat put-url.txt
Cole o link em Temporary PUT URL e selecione Upload synthetic text dentro de cinco minutos. Exija um resultado HTTP 2xx bem-sucedido e um ETag visível. Limpe o campo de URL após o upload. Uma URL expirada pode aparecer como uma falha genérica de CORS no navegador, porque as respostas de erro podem não conter cabeçalhos de CORS; gere um link novo se necessário.
Baixe o objeto real usando o Wrangler e compare o payload fixo:
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
A verificação da plataforma lê os bytes e o tipo de conteúdo de forma independente. A interação real com o navegador continua sendo uma evidência necessária para este laboratório; apenas o curl não aplica as regras de CORS do navegador.

O navegador real retornou HTTP 200 e um ETag legível; o campo da URL temporária foi limpo. A página mostra \n literalmente, mas envia uma quebra de linha real. O download independente confirma o conteúdo exato de 26 bytes.
Separar a política de origem da permissão da assinatura
Nesta etapa, você inspecionará um preflight permitido e outro não permitido sem criar outro objeto. Essas solicitações curl perguntam o que o navegador tem permissão para fazer; o próprio curl não aplica essa resposta.
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 Access-Control-Allow-Origin com o mesmo valor exato da origem da página. Agora faça a solicitação a partir de uma origem não relacionada:
curl -i -X OPTIONS -H "Origin: https://outside.example" -H "Access-Control-Request-Method: PUT" -H "Access-Control-Request-Headers: content-type" "$OBJECT_URL"
A resposta não deve conceder acesso a essa origem, nem a *. O status exato de uma resposta de negação pode variar; é a ausência da concessão que impede o acesso do navegador. Por fim, tente fazer uma leitura sem assinatura:
curl -sS -o unsigned.xml -w "%{http_code}\n" "$OBJECT_URL"
Exija 400 com Code XML InvalidArgument e Message Authorization, como observado para uma solicitação sem assinatura a este endpoint S3 do R2. Inspecione ambos os campos; um erro qualquer ou falha de rede não prova rejeição da assinatura.
python3 - <<'PYXML'
from xml.etree import ElementTree
root = ElementTree.parse("unsigned.xml").getroot()
print("Code:", root.findtext("Code"))
print("Message:", root.findtext("Message"))
PYXML
Ativar o CORS não tornou o bucket público nem removeu as verificações de assinatura. Um cliente que não seja um navegador e tenha uma assinatura válida ainda pode usar a operação concedida, independentemente de um cabeçalho Origin; strings de origem não são credenciais de identidade.
Remover os recursos e as credenciais do upload
Nesta etapa, você removerá o Worker da interface, o objeto exato e o bucket. Confirme a ausência dos recursos antes de revogar as credenciais.
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 somente os nomes gerados para este laboratório. Atualize a lista de Workers e a lista de buckets no Dashboard. Execute a verificação de limpeza da plataforma enquanto a autorização de gerenciamento ainda estiver ativa. A configuração de CORS do bucket é removida junto com o bucket.
Revogar a credencial do laboratório e sair
Nesta etapa, você encerrará o acesso deixado por este exercício. Na página R2 API Tokens, revogue somente o token de objeto nomeado para este laboratório. Na página API Tokens do seu perfil, revogue o token de gerenciamento do R2 separado que você criou para este laboratório. Excluir um bucket não revoga um token, e sair do Wrangler não revoga as credenciais do S3.
Depois da revogação, remova o arquivo de credenciais local e faça logout desta VM:
rm .env.s3 .env.management put-url.txt
npx wrangler logout
Inspecione a identidade estruturada. O status diferente de zero é esperado quando você está desconectado:
npx wrangler whoami --json || true
Exija loggedIn: false; mantenha o login comum no Dashboard. A plataforma verifica a remoção das credenciais locais e o logout do Wrangler. As duas revogações de token são checkpoints manuais no Dashboard neste laboratório; elas não são inferidas pela exclusão dos arquivos.
Resumo
Configure o CORS para uma origem exata, faça upload de bytes sintéticos com uma URL pré-assinada, diferencie a permissão do navegador da autorização e faça a limpeza.



