Encerrar um download público não intencional

CloudflareBeginner
Pratique Agora

Introdução

Um bucket privado ainda pode expor arquivos se um Worker público os retornar para qualquer solicitante. Você reproduzirá esse defeito usando apenas duas exportações sintéticas, adicionará autorização da aplicação restrita ao leitor e comprovará que os downloads privados continuam protegidos enquanto a verificação de integridade pública permanece disponível.

Conclua primeiro as lições sobre integração de documentos do Worker e acesso temporário. Esta VM nova fornece um handler intencionalmente inseguro, arquivos sintéticos, Node.js 22.22.0, Wrangler 4.131.1 e Miniflare 4.20260730.0. Você criará um bucket privado novo e um Worker descartável. É necessário ter o R2 ativo e as permissões correspondentes na conta de aprendizagem; consulte preços. Não são necessários arquivos reais, dados reais de clientes nem domínio personalizado. Limpe a demonstração exposta e suas credenciais antes de sair.

Conectar o bucket da aplicação

Nesta etapa, você autoriza esta VM e cria 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 apresentada abaixo. Em seguida, vá para o projeto preparado e verifique as ferramentas. Mantenha este mesmo terminal aberto para conservar disponíveis as variáveis com os nomes dos 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

Autorize o código de dispositivo exibido no seu próprio navegador. Confirme a conta de aprendizagem e os escopos de leitura de conta e 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 colida 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-r08-$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 um nome relacionado a este 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. Este token de gerenciamento serve para administrar buckets, incluindo sua criação e exclusão. Neste laboratório, o Worker acessa os objetos do R2 por meio do binding DOCUMENTS.

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 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 este bucket e verifique se a lista de objetos está vazia. Nas configurações dele, 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 o controle de exclusão do Wrangler; este laboratório não cria nenhum namespace KV. O token de gerenciamento do R2 continua sendo uma credencial separada, com escopo da conta.

Crie duas credenciais novas da aplicação para os leitores sintéticos. Elas não são tokens de API da Cloudflare. Prepare os dois objetos pertencentes aos leitores e publique o handler fornecido, intencionalmente exposto:

umask 077
printf "BLUE_TOKEN=%s\nGREEN_TOKEN=%s\n" "$(openssl rand -hex 24)" "$(openssl rand -hex 24)" > .dev.vars
npx wrangler r2 object put "$BUCKET/exports/blue/report.txt" --remote --file blue.txt --content-type text/plain --env-file=.env.management
npx wrangler r2 object put "$BUCKET/exports/green/report.txt" --remote --file green.txt --content-type text/plain --env-file=.env.management
npx wrangler deploy
npx wrangler secret bulk .dev.vars

Esta implantação exposta deliberadamente contém apenas esses dois arquivos sintéticos. Não use exportações reais nem deixe o Worker em execução após o exercício.

Observar o download público não intencional

Nesta etapa, você reproduzirá a exposição por meio do Worker enquanto o bucket subjacente continua privado. Uma binding dá ao código do servidor acesso ao bucket; o R2 não decide automaticamente em quais solicitantes HTTP esse código deve confiar.

Copie o URL exato da implantação e solicite a exportação azul sem credenciais:

BASE_URL=https://YOUR_WORKER.YOUR_SUBDOMAIN.workers.dev
curl -i "$BASE_URL/exports/blue/report.txt"

Exija HTTP 200 e Synthetic blue export.. Se a nova implantação ainda estiver sendo propagada, repita a leitura por até um minuto. Essa resposta anônima bem-sucedida é o defeito que você deverá corrigir.

Inspecione o handler fornecido:

cat src/index.js

Ele lê diretamente do URL uma chave do R2 e retorna o conteúdo sem decidir se o solicitante é o proprietário desse arquivo. No Dashboard, inspecione as configurações do bucket: o URL público de desenvolvimento está desativado e não há domínios personalizados. Essas configurações não fecham a rota separada do Worker. Execute a verificação de exposição antes de substituir o handler.

As configurações reais mostram ausência de domínio personalizado e URL pública de desenvolvimento desativada. Um Worker com vínculo R2 ainda pode expor dados pela própria rota.

Configurações do bucket privado

Esta solicitação real no terminal da VM retorna HTTP 200 e o relatório azul sintético sem credenciais. O usuário, prefixo e URL do Worker são exemplos; use sua própria URL de implantação.

HTTP 200 anônimo antes da correção

Autenticar o leitor antes de selecionar a chave

Nesta etapa, você associará autenticação (qual leitor possui uma credencial válida) à autorização (qual leitor pode baixar este objeto). Um token azul válido não pode recuperar uma exportação verde. A identidade autenticada fornece o proprietário da chave, e o proprietário no URL deve corresponder antes que o R2 seja consultado.

Substitua o handler. As duas credenciais sintéticas representam leitores neste pequeno exemplo; uma aplicação real usaria sua sessão ou seu provedor de identidade e registros autoritativos de permissões. Nunca aceite um nome fornecido pelo solicitante como prova de identidade.

cat > src/index.js <<'JS'
async function matches(actual, secret) {
  if (!secret) return false;
  const expected = `Bearer ${secret}`;
  const a = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(actual));
  const b = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(expected));
  return crypto.subtle.timingSafeEqual(a, b);
}
export default {
  async fetch(request, env) {
    const path = new URL(request.url).pathname;
    if (path === "/health" && request.method === "GET") return new Response("ok");
    const actual = request.headers.get("Authorization") || "";
    const owner = await matches(actual, env.BLUE_TOKEN) ? "blue" : await matches(actual, env.GREEN_TOKEN) ? "green" : null;
    if (!owner) return new Response("Unauthorized", { status: 401 });
    if (request.method !== "GET") return new Response("Method not allowed", { status: 405 });
    const route = /^\/exports\/(blue|green)\/([a-z0-9-]+\.txt)$/.exec(path);
    if (!route) return new Response("Not found", { status: 404 });
    if (route[1] !== owner) return new Response("Forbidden", { status: 403 });
    // The authenticated owner and validated path determine the storage key.
    const key = `exports/${owner}/${route[2]}`;
    const object = await env.DOCUMENTS.get(key);
    if (!object) return new Response("Not found", { status: 404 });
    const headers = new Headers({ "Cache-Control": "private, no-store" });
    object.writeHttpMetadata(headers);
    headers.set("ETag", object.httpEtag);
    return new Response(object.body, { headers });
  }
};
JS

Um segredo ausente não pode coincidir acidentalmente com um solicitante. A comparação dos digests usa a operação de igualdade segura contra ataques de temporização disponível no runtime. A verificação de integridade permanece fora da rota protegida, e as respostas privadas não são candidatas ao cache compartilhado. Um key na string de consulta não pode substituir o caminho de armazenamento autenticado.

Implante a correção:

npx wrangler deploy

A verificação da plataforma primeiro executa um runtime local separado com credenciais sintéticas e bytes de objetos aleatórios. Uma verificação local bem-sucedida é útil antes de avaliar a correção implantada.

Comprovar o isolamento dos leitores e a verificação de integridade preservada

Nesta etapa, você testará os caminhos permitidos e negados. Carregue as credenciais sintéticas sem exibi-las e baixe o arquivo de cada proprietário:

set -a
source .dev.vars
set +a
curl -fsS -H "Authorization: Bearer $BLUE_TOKEN" "$BASE_URL/exports/blue/report.txt" -o blue-download.txt
cmp blue.txt blue-download.txt
curl -fsS -H "Authorization: Bearer $GREEN_TOKEN" "$BASE_URL/exports/green/report.txt" -o green-download.txt
cmp green.txt green-download.txt

Exija bytes exatamente iguais. Agora teste o acesso anônimo, um leitor acessando o caminho do outro proprietário, uma chave ausente do próprio proprietário e a verificação de integridade pública:

curl -i "$BASE_URL/exports/blue/report.txt"
curl -i -H "Authorization: Bearer $BLUE_TOKEN" "$BASE_URL/exports/green/report.txt"
curl -i -H "Authorization: Bearer $BLUE_TOKEN" "$BASE_URL/exports/blue/missing.txt"
curl -i "$BASE_URL/health"

Exija, respectivamente, 401 Unauthorized, 403 Forbidden, 404 Not found e 200 ok. O status e o corpo devem ser compatíveis. A verificação da plataforma repete remotamente o isolamento dos leitores e confirma que a conta selecionada é proprietária do Worker e de sua binding privada do R2.

A mesma URL de exemplo agora retorna HTTP 401 Unauthorized à solicitação anônima no terminal da VM. São resultados HTTP do terminal; as comparações de bytes autenticadas e verificações independentes comprovam o isolamento.

HTTP 401 anônimo após a correção

Remover a aplicação e o bucket remotos

Nesta etapa, você excluirá somente o Worker e os objetos deste laboratório enquanto ainda estiver autorizado. O bucket privado não desaparece quando o Worker é excluído.

npx wrangler delete

Confirme o nome exato do Worker gerado. Exclua explicitamente o único objeto enviado e, em seguida, exclua o bucket:

BUCKET=$(node -p "JSON.parse(require('fs').readFileSync('wrangler.jsonc')).r2_buckets[0].bucket_name")
npx wrangler r2 object delete "$BUCKET/exports/blue/report.txt" --remote --env-file=.env.management
npx wrangler r2 object delete "$BUCKET/exports/green/report.txt" --remote --env-file=.env.management
npx wrangler r2 bucket delete "$BUCKET" --env-file=.env.management

Somente as chaves de relatório azul e verde foram criadas. Exclua essas chaves exatas e preserve os recursos não relacionados da conta.

Atualize as listas de Workers e buckets no Dashboard e execute a verificação de limpeza da plataforma. Falhas de autenticação ou de rede são inconclusivas, não significam que a exclusão foi bem-sucedida.

Encerrar as credenciais restantes

Nesta etapa, revogue o token de gerenciamento deste laboratório na página API Tokens do seu perfil, remova o segredo local da aplicação e encerre a autorização da VM. Faça isso somente depois que a verificação de limpeza anterior for aprovada.

rm .env.management .dev.vars
unset BLUE_TOKEN GREEN_TOKEN
npx wrangler logout
npx wrangler whoami --json || true

Exija loggedIn: false. A revogação do token de gerenciamento é uma verificação manual separada no Dashboard; excluir apenas o arquivo local não o revoga. Mantenha intactos o login comum do Dashboard e os tokens de outros laboratórios.

Resumo

Encerre a exposição de um arquivo por um Worker sintético, associe a identidade do leitor à propriedade do objeto, mantenha o acesso à verificação de integridade e confirme a limpeza do bucket privado.