Introdução
Um agente de suporte precisa compartilhar brevemente uma exportação privada sintética sem tornar o bucket público nem distribuir credenciais de armazenamento. Você emitirá uma URL de download pré-assinada, observará sua expiração e comprovará que a credencial usada para assiná-la não consegue acessar outro bucket pertencente ao laboratório.
Conclua primeiro os laboratórios Organize a Document Bucket e Stream Documents Through a Worker. Esta VM independente já possui Node.js 22.22.0, Wrangler 4.131.1 e AWS SDK 3.888.0 fixados. A conta precisa ter o R2 ativo e permissão para criar um token de objeto de usuário com escopo restrito. Serão usados apenas dois arquivos sintéticos pequenos. Consulte preços do R2 e acesso pré-assinado; não é necessário ter um domínio comprado. Não compartilhe os links nem as credenciais gerados. Antes de sair, limpe os dois buckets e revogue somente os tokens deste laboratório.
Criar buckets isolados para testes de acesso
Nesta etapa, você autoriza esta VM e cria um bucket descartável. A autorização do dispositivo confirma sua conta de aprendizado. O gerenciamento de buckets do R2 usa um token de API separado, restrito a essa conta.
Inicie o Bash para usar a sintaxe dos comandos 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 aprendizado e os escopos de leitura solicitados para a conta e o usuário antes de conceder o consentimento:
npx wrangler login --device --browser=false --scopes account:read user:read
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, evitando conflitos com uma execução anterior deste laboratório. O here-document grava um arquivo de configuração padrão; o shell substitui nele os valores das suas variáveis.
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
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 aprendizado 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 comando comum whoami continuará verificando a autorização do dispositivo na 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 desativadas a URL de desenvolvimento pública e os domínios personalizados. O nome do bucket no Dashboard confirma a identidade; os downloads posteriores comprovarão os bytes armazenados.
Crie um segundo bucket pertencente a este laboratório. Ele representa um limite de armazenamento não relacionado; não substitua esse bucket por um bucket de outro exercício. Usando a credencial de gerenciamento, envie um arquivo sintético para cada bucket:
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
Mantenha o acesso público desativado nos dois buckets. Na próxima etapa, você permitirá que a credencial seguinte leia somente o bucket de documentos.
Restringir a credencial de assinatura
Nesta etapa, você criará uma credencial somente leitura restrita a um bucket. Uma URL pré-assinada não pode conceder mais acesso do que a credencial usada para assiná-la. O token de gerenciamento pode administrar os dois buckets do laboratório; portanto, ele não deve ser usado como identidade de assinatura nesta liçã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 only, restrinja o token exatamente a este novo bucket e selecione uma expiração curta, se o formulário oferecer essa opção. Não escolha todos os buckets nem o acesso Admin. Mantenha esta página do token aberta até armazenar o segredo que só será exibido uma vez.
Use os prompts Bash a seguir na VM. read -s oculta a entrada; umask 077 faz com que somente o seu usuário possa ler o arquivo de credenciais. Esses nomes são as variáveis de ambiente padrão 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. Ler a 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",
endpoint: `https://${config.account_id}.r2.cloudflarestorage.com`,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
}
});
JS
Selecione somente o bucket -docs; mantenha o bucket -control fora do escopo de recursos desse token. O cliente existente storage.mjs lê .env.s3 somente quando --env-file=.env.s3 está presente. Não use a permissão Admin nem o escopo para todos os buckets.
Antes de compartilhar qualquer URL, confirme o bucket selecionado no formulário do token. A solicitação real ao bucket de controle na próxima etapa comprovará de forma independente que esse limite funciona.

Este exemplo seleciona Object Read only e apenas o bucket -docs gerado; o bucket -control é excluído. O nome gerado será diferente. O TTL do token de 24 horas é separado da validade de cinco minutos ou cinco segundos de cada URL assinada. O formulário mostra o escopo escolhido; as solicitações remotas aceitas e rejeitadas comprovam as permissões reais.
Assinar e testar downloads temporários
Nesta etapa, você assinará solicitações GET para um objeto específico durante um período determinado. A assinatura é executada localmente, sem contato com o R2; criar uma URL não comprova que você tem permissão. O R2 avalia a assinatura quando um cliente usa a URL. Qualquer pessoa que possua a URL pode usar a operação concedida até a expiração. Portanto, não inclua esses links em logs, mensagens ou capturas de tela.
Grave um pequeno programa de assinatura. expiresIn é medido em segundos. O arquivo armazenará uma URL válida por cinco minutos, uma URL válida por cinco segundos e uma URL deliberadamente fora do escopo, sem exibi-las:
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
Use rapidamente a URL permitida. Esta solicitação curl não contém um cabeçalho Authorization: a própria URL transporta a assinatura. node -p fornece diretamente ao curl a URL armazenada, sem exibi-la na saída do terminal.
curl -fsS "$(node -p "require('./signed.json').allowed")" -o signed-download.txt
cmp document.txt signed-download.txt
Exija bytes idênticos. Agora aguarde dez segundos para que a URL de cinco segundos expire e inspecione as respostas de negação. Essas solicitações omitem --fail intencionalmente para exibir os erros 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")"
Exija 403 para as solicitações expiradas e fora do escopo permitido. A solicitação sem assinatura a este endpoint S3 do R2 retorna 400 com InvalidArgument e a mensagem Authorization: faltam as informações de assinatura obrigatórias. Isso difere da negação de acesso a outro bucket para uma credencial com assinatura válida. Inspecione os nomes dos erros sem exibir os documentos de resposta completos, que podem conter detalhes da solicitação:
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
A resposta da URL expirada deve identificar a expiração, enquanto o bucket de controle deve retornar AccessDenied. Uma falha de rede não é um resultado de autorização. Se você ultrapassou cinco minutos antes de testar o bucket de controle, gere novamente as URLs, repita as solicitações permitida e fora do escopo e deixe a URL curta expirar novamente.
Diferenciar permissão de URL e acesso público
Nesta etapa, você relacionará as solicitações observadas ao modelo de acesso. O link bem-sucedido autorizou uma solicitação GET assinada para uma chave específica. Ele não ativou a URL de desenvolvimento pública do bucket, não autorizou PUT nem conectou um usuário à sua aplicação.
Abra as configurações dos dois buckets pertencentes ao laboratório no Dashboard. Mantenha as URLs de desenvolvimento públicas desativadas e não configure domínios personalizados. Na página do token de objeto, confirme que o token somente leitura cobre apenas o bucket -docs. Não revele o segredo de uso único em uma captura de tela.
Execute agora a verificação da plataforma. Ela gera suas próprias solicitações assinadas, exige os bytes permitidos exatos e verifica as negações para solicitações sem assinatura, expiradas e fora do escopo. Esta é uma evidência real da API remota; o arquivo signed.json salvo não é um marcador de conclusão.
Em uma aplicação de produção, decida se um usuário pode acessar o documento antes de emitir um link assinado. Depois de emitido, o link de portador pode ser copiado. O laboratório posterior sobre autorização da aplicação aborda diretamente essa decisão. As URLs S3 assinadas têm como destino o endpoint S3 do R2 da conta, não um endereço público r2.dev nem um domínio personalizado.
Limpar os dois buckets do laboratório
Nesta etapa, você removerá as duas chaves de objetos explícitas e, depois, os buckets correspondentes. Mantenha a credencial de gerenciamento ativa até a plataforma confirmar que os dois nomes de bucket estão ausentes.
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
Confirme cada nome exato quando solicitado. Uma listagem de buckets bem-sucedida não deve conter nenhum dos dois nomes. Nunca exclua outro bucket para deixar o inventário global vazio.
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 com o nome deste laboratório. Na página API Tokens do seu perfil, revogue o token de gerenciamento do R2 criado separadamente para este laboratório. Excluir um bucket não revoga um token, e sair do Wrangler não revoga credenciais S3.
Depois de revogar as credenciais, remova o arquivo local de credenciais e desconecte esta VM:
rm .env.s3 .env.management signed.json
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 e mantenha seu login comum no Dashboard. A plataforma verifica a remoção das credenciais locais e o logout do Wrangler. As duas revogações de tokens são verificações manuais no Dashboard neste laboratório; elas não são inferidas pela exclusão dos arquivos.
Resumo
Crie acesso restrito a arquivos privados, comprove o sucesso e os limites de negação de URLs pré-assinadas, diferencie acesso público de autorização e revogue as credenciais do laboratório.



