Введение
Агент службы поддержки должен ненадолго предоставить доступ к частному синтетическому экспорту, не делая bucket публичным и не передавая учётные данные хранилища. Вы создадите presigned URL для скачивания, проверите его срок действия и убедитесь, что подписывающие учётные данные не дают доступа к другому bucket, принадлежащему лабораторной работе.
Сначала завершите лабораторные работы «Организация bucket для документов» и «Передача документов через Worker». Эта независимая виртуальная машина содержит закреплённые версии Node.js 22.22.0, Wrangler 4.131.1 и AWS SDK 3.888.0. Для аккаунта должны быть активированы R2 и разрешено создание ограниченного пользовательского токена объекта. Используются только два небольших синтетических файла. Ознакомьтесь с тарифами R2 и доступом по presigned URL; приобретённый домен не требуется. Не передавайте созданные ссылки или учётные данные. Перед выходом очистите оба bucket и отзовите только токены этой лабораторной работы.
Создание изолированных bucket для проверки доступа
На этом шаге вы авторизуете виртуальную машину и создадите один временный bucket. Авторизация устройства подтверждает вашу учебную учётную запись. Управление bucket R2 выполняется отдельным API-токеном, ограниченным этим аккаунтом.
Запустите Bash, чтобы использовать приведённый ниже синтаксис команд, затем перейдите в подготовленный проект и проверьте инструменты. Не закрывайте этот терминал: в нём сохранятся переменные с именами ресурсов.
bash
cd /home/labex/project/r2-lab
export PATH="$PWD/.tools/node-v22.22.0-linux-x64/bin:$PATH"
node --version
npx wrangler --version
Откройте показанный код устройства в своём браузере. Перед подтверждением проверьте учебную учётную запись, а также запрошенные области account и user для чтения:
npx wrangler login --device --browser=false --scopes account:read user:read
npx wrangler whoami --json
Убедитесь, что указано loggedIn: true. Прочитайте имя аккаунта, даже если в списке отображается только один аккаунт. Замените YOUR_ACCOUNT_ID ниже фактическим идентификатором этого аккаунта из 32 символов. Команда openssl rand -hex 6 создаёт двенадцать случайных шестнадцатеричных символов, поэтому имя ресурсов этой лабораторной работы не совпадёт с именем предыдущего запуска. Here-документ создаёт стандартный файл конфигурации; оболочка подставляет в него значения ваших переменных.
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
Для управления bucket откройте в профиле Cloudflare страницу API Tokens и создайте пользовательский токен с именем этой лабораторной работы. Выдайте разрешение Account → Workers R2 Storage → Edit и ограничьте Account Resources учебным аккаунтом, идентификатор которого вы сохранили. Установите короткий срок действия. Не добавляйте другие аккаунты или несвязанные разрешения. Это разрешение на уровне аккаунта позволяет создавать и удалять bucket; токен только для объектов на следующем шаге этого делать не сможет.
Однократно скопируйте токен в скрытый запрос виртуальной машины. umask 077 ограничивает доступ к файлу вашей учётной записью, а read -s скрывает ввод. Файл использует стандартную переменную токена Wrangler и исключён из 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
Используйте --env-file=.env.management только для команд управления R2. Обычная команда whoami продолжит проверять авторизацию устройства виртуальной машины.
Размещайте --env-file в конце каждой команды Wrangler, чтобы имя команды не попадало в список аргументов с файлами. Если после создания каждого бакета Wrangler предложит добавить привязку в конфигурацию, введите n и нажмите Enter. Нужная привязка уже указана в конфигурации.
npx wrangler r2 bucket create "$BUCKET" --env-file=.env.management
Выведите список bucket и найдите точное созданное имя. Остальные bucket принадлежат другим работам — не изменяйте их.
npx wrangler r2 bucket list --env-file=.env.management
В Dashboard откройте Storage & databases → R2 → Overview, выберите именно этот bucket и проверьте пустой список объектов. В его настройках оставьте URL для разработки и пользовательские домены отключёнными. Имя bucket в Dashboard подтверждает его идентичность; последующие проверки скачивания подтвердят сохранённые байты.
Создайте второй bucket, принадлежащий этой лабораторной работе. Он представляет отдельную границу хранилища; не подставляйте bucket из другой работы. С учётными данными управления загрузите по одному синтетическому файлу в каждый 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
Оставьте публичный доступ отключённым для обоих bucket. Следующие учётные данные получат доступ только для чтения к bucket с документом.
Ограничение подписывающих учётных данных
На этом шаге вы создадите учётные данные только для чтения, ограниченные одним bucket. Presigned URL не может предоставить больше доступа, чем есть у использованных для его подписи учётных данных. Токен управления имеет доступ к обоим bucket лабораторной работы, поэтому в этом уроке его нельзя использовать как идентификатор подписывающей стороны.
S3-совместимый API предоставляет стандартным SDK доступ к R2. Для него используется отдельная пара ключей доступа, а не токен устройства Wrangler. В разделе R2 Overview выберите Account Details → API Tokens → Manage, затем создайте User API token с именем, содержащим созданное имя ресурса этой лабораторной работы. Выберите Object Read only, ограничьте токен именно этим новым bucket и установите короткий срок действия, если форма предлагает такую возможность. Не выбирайте все bucket или доступ Admin. Не закрывайте страницу токена, пока не сохраните одноразовый секрет.
Используйте следующие запросы Bash в виртуальной машине. read -s скрывает ввод, а umask 077 делает файл с учётными данными доступным только вашей учётной записи. Эти имена являются стандартными переменными окружения AWS SDK. Вставьте Access Key ID и Secret Access Key в соответствующие запросы и нажмите Enter. Не вставляйте значение общего API-токена.
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
Создайте повторно используемый стандартный клиент SDK. SDK требует строку региона; для R2 используется auto. Чтение существующей конфигурации гарантирует, что операции CLI и SDK направлены на один и тот же аккаунт и 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
Выберите только bucket -docs; оставьте bucket -control за пределами области действия этого токена. Существующий клиент storage.mjs читает .env.s3 только при наличии --env-file=.env.s3. Не используйте разрешение Admin или область действия для всех bucket.
Перед передачей любой ссылки проверьте выбранный bucket в форме токена. Реальный запрос к control bucket на следующем шаге отдельно подтвердит, что эта граница работает.

В этом примере выбраны Object Read only и только сгенерированный бакет -docs; бакет -control исключён. Ваше сгенерированное имя будет другим. TTL токена в 24 часа не совпадает со сроком действия каждой подписанной ссылки в пять минут или пять секунд. Форма показывает выбранную область, а успешные и отклонённые удалённые запросы подтверждают реальные права.
Создание и проверка временных ссылок на скачивание
На этом шаге вы подпишете GET-запросы для конкретного объекта и заданного периода действия. Подписание выполняется локально, без обращения к R2; создание URL само по себе не подтверждает наличие разрешения. R2 проверяет подпись, когда клиент использует URL. Любой, кто получит URL, сможет выполнять разрешённую операцию до истечения срока действия, поэтому не помещайте эти ссылки в журналы, сообщения или снимки экрана.
Создайте небольшую программу для подписи. expiresIn указывается в секундах. Файл сохраняет URL сроком на пять минут, URL сроком на пять секунд и намеренно выходящий за область действия URL, не выводя их на экран:
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
Сразу используйте разрешённый URL. Этот запрос curl не содержит заголовка Authorization: подпись находится в самом URL. node -p передаёт сохранённый URL непосредственно в curl, не показывая его в выводе терминала.
curl -fsS "$(node -p "require('./signed.json').allowed")" -o signed-download.txt
cmp document.txt signed-download.txt
Убедитесь, что байты полностью совпадают. Теперь подождите десять секунд, чтобы срок действия URL на пять секунд истёк, а затем проверьте ответы с отказом. В этих запросах намеренно не используется --fail, чтобы показать ошибки 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")"
Для просроченного запроса и запроса вне разрешённой области ожидается 403. Неподписанный запрос к этой конечной точке R2 S3 возвращает 400 с кодом InvalidArgument и сообщением Authorization: отсутствуют обязательные данные подписи. Это отличается от отказа в доступе к другому бакету при действительной подписи учётных данных. Проверьте имена ошибок, не выводя документы ответов целиком: они могут содержать сведения о запросах.
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
В ответе для истёкшего URL должна быть указана причина, связанная с истечением срока действия, а control bucket должен вернуть AccessDenied. Сбой сети не является результатом проверки авторизации. Если до проверки control bucket прошло больше пяти минут, заново создайте URL, повторите запросы к разрешённому и внешнему объектам, а затем снова дождитесь истечения короткого URL.
Отличие разрешения URL от публичного доступа
На этом шаге вы свяжете наблюдавшиеся запросы с моделью доступа. Успешная ссылка авторизовала один подписанный GET-запрос для одного ключа. Она не включила публичный URL для разработки bucket, не разрешила PUT и не выполнила вход пользователя в ваше приложение.
Откройте настройки обоих bucket лабораторной работы в Dashboard. Оставьте публичные URL для разработки отключёнными, а пользовательские домены — не настроенными. На странице токена объектов подтвердите, что токен только для чтения распространяется лишь на bucket -docs. Не показывайте его одноразовый секрет на снимке экрана.
Теперь запустите проверку платформы. Она создаёт собственные подписанные запросы, требует точного совпадения разрешённых байтов и проверяет отказы для неподписанного, истёкшего и выходящего за область действия запросов. Это реальные данные удалённого API; сохранённый signed.json не является признаком завершения.
В производственном приложении определяйте, может ли пользователь получить доступ к документу, до выдачи подписанной ссылки. После выдачи ссылку типа bearer можно скопировать. Следующая лабораторная работа по авторизации приложения непосредственно рассматривает это решение. Подписанные URL S3 направляются к S3 endpoint аккаунта R2, а не к публичному адресу r2.dev или пользовательскому домену.
Очистка обоих bucket лабораторной работы
На этом шаге вы удалите два явно созданных ключа объектов, а затем сами bucket. Не отключайте учётные данные управления, пока платформа не подтвердит отсутствие обоих имён bucket.
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
При появлении запроса подтвердите каждое точное имя. В успешном списке bucket не должны присутствовать ни одно из этих двух имён. Никогда не удаляйте другой bucket, чтобы глобальный список оказался пустым.
Отзыв учётных данных лабораторной работы и выход
На этом шаге вы закроете доступ, оставшийся после выполнения работы. На странице R2 API Tokens отзовите только токен объектов, названный в соответствии с этой лабораторной работой. На странице API Tokens вашего профиля отзовите отдельный токен управления R2, созданный для этой работы. Удаление bucket не отзывает токен, а выход из Wrangler не отзывает учётные данные S3.
После отзыва удалите локальные файлы с учётными данными и выполните выход этой виртуальной машины:
rm .env.s3 .env.management signed.json
npx wrangler logout
Проверьте структурированные сведения об идентификации. Ненулевой статус при выходе из системы ожидаем:
npx wrangler whoami --json || true
Убедитесь, что указано loggedIn: false; обычный вход в Dashboard оставьте активным. Платформа проверяет удаление локальных учётных данных и выход из Wrangler. Отзыв обоих токенов выполняется вручную в Dashboard и является отдельными контрольными точками этой работы; по удалению файлов он не определяется.
Резюме
Создайте ограниченный доступ к частным файлам, подтвердите успешный и отклонённый доступ по presigned URL, отличите публичный доступ от авторизации и отзовите учётные данные лабораторной работы.



