Введение
Команда хочет предоставлять клиентам страницу релиза через CloudFront, сохраняя источник S3 закрытым. Загрузите подготовленную страницу, соедините распределение с управлением доступом к источнику и разрешите только этому распределению читать объекты. Проверьте работающий клиентский путь и запрещённый прямой доступ к источнику.
Сначала завершите AWS Foundations, операции с объектами S3 и изучение ресурсных политик IAM. Эта независимая VM предоставляет настроенный доступ AWS CLI, index.html и отдельный эталонный бакет. Наблюдайте за распределением и источником в AWS View сверху, выполняйте работу в Terminal снизу. Личный аккаунт AWS и публичный домен не нужны. Здесь используется один обычный источник S3; кэширование и HTTPS изучаются отдельно.
Связь с сертификацией
| Сертификация | Задача экзамена | Практика |
|---|---|---|
| Solutions Architect – Associate (SAA-C03) | Задача 1.1 | Ограничить чтение S3 выбранным распределением CloudFront с помощью ресурсной политики. |
Обзор лабораторной работы

Подготовьте содержимое закрытого источника
На этом шаге создайте закрытый бакет S3 и загрузите подготовленную страницу.
Источник (origin) хранит содержимое, которое получает CloudFront. Viewer — клиент, запрашивающий содержимое у CloudFront. Доступ клиента и доступ к источнику — разные разрешения: страницу можно читать через распределение, сохраняя запрет анонимного прямого чтения S3.
Работайте в предоставленном каталоге проекта. Сохраните эталонный бакет labex-n02-reference без изменений:
cd /home/labex/project
cat index.html
aws s3api create-bucket \
--bucket labex-n02-content
Установите владение объектами Bucket owner enforced. Владельцем остаётся владелец бакета, разрешения через ACL отключаются; OAC использует политику бакета. Включите все четыре защиты Block Public Access, предотвращающие публичные разрешения через ACL или политики:
aws s3api put-bucket-ownership-controls \
--bucket labex-n02-content \
--ownership-controls '{"Rules":[{"ObjectOwnership":"BucketOwnerEnforced"}]}'
aws s3api put-public-access-block \
--bucket labex-n02-content \
--public-access-block-configuration '{"BlockPublicAcls":true,"IgnorePublicAcls":true,"BlockPublicPolicy":true,"RestrictPublicBuckets":true}'
Загрузите предоставленную страницу. --content-type text/html описывает объект как HTML-документ:
aws s3api put-object \
--bucket labex-n02-content \
--key index.html \
--body index.html \
--content-type text/html
aws s3api head-object \
--bucket labex-n02-content \
--key index.html
Посмотрите размер объекта и ContentType. Запрос CLI аутентифицирован как настроенный оператор. Сравните его с анонимным HTTP-запросом без учётных данных AWS:
curl --noproxy '*' \
--output /dev/null \
--write-out 'Direct origin: HTTP %{http_code}\n' \
http://127.0.0.1:5000/labex-n02-content/index.html
Ожидайте Direct origin: HTTP 403. --output /dev/null отбрасывает тело ошибки, --write-out выводит статус HTTP. Этот явно заданный учебный endpoint проверяет прямой доступ к объекту S3, не делая бакет публичным. Выполните проверку закрытого источника.

Соедините распределение с источником
На этом шаге соедините распределение CloudFront с бакетом S3 и убедитесь, что соединение само по себе не предоставляет разрешение на источник.
Управление доступом к источнику (OAC) определяет, как CloudFront аутентифицирует запросы к источнику. Выберите S3, Signature Version 4 и подпись always. Запишите обычную конфигурацию CLI:
cat > oac.json <<'JSON'
{
"Name": "labex-n02-oac",
"Description": "Read the private release origin",
"SigningProtocol": "sigv4",
"SigningBehavior": "always",
"OriginAccessControlOriginType": "s3"
}
JSON
OAC_ID=$(aws cloudfront create-origin-access-control \
--origin-access-control-config file://oac.json \
--query OriginAccessControl.Id \
--output text)
$(...) сохраняет возвращённый ID OAC в OAC_ID. --query выбирает только ID для ссылки в следующей конфигурации. Сохраняйте этот Terminal открытым в течение лабораторной работы.
Конфигурация соединяет content-origin с обычным endpoint бакета S3, а не endpoint сайта S3. TargetOriginId выбирает источник; DefaultRootObject сопоставляет / с index.html. Устаревший блок ForwardedValues исключает пересылку cookie и строк запроса. Все TTL равны нулю, чтобы проверка разрешений не использовала успешный ответ из кэша. allow-all разрешает используемый здесь клиентский HTTP-запрос; HTTPS изучается отдельно.
Следующий here-document указан без кавычек, поэтому оболочка подставляет $OAC_ID в JSON-файл:
cat > distribution.json <<JSON
{
"CallerReference": "labex-n02-release",
"Comment": "labex-n02:private-content",
"Enabled": true,
"DefaultRootObject": "index.html",
"Origins": {
"Quantity": 1,
"Items": [{
"Id": "content-origin",
"DomainName": "labex-n02-content.s3.amazonaws.com",
"S3OriginConfig": {"OriginAccessIdentity": ""},
"OriginAccessControlId": "$OAC_ID"
}]
},
"DefaultCacheBehavior": {
"TargetOriginId": "content-origin",
"ViewerProtocolPolicy": "allow-all",
"TrustedSigners": {"Enabled": false, "Quantity": 0},
"ForwardedValues": {"QueryString": false, "Cookies": {"Forward": "none"}},
"MinTTL": 0,
"DefaultTTL": 0,
"MaxTTL": 0
}
}
JSON
DIST_ID=$(aws cloudfront create-distribution \
--distribution-config file://distribution.json \
--query Distribution.Id \
--output text)
DIST_DOMAIN=$(aws cloudfront get-distribution \
--id "$DIST_ID" \
--query Distribution.DomainName \
--output text)
Посмотрите подключённый источник:
aws cloudfront get-distribution-config \
--id "$DIST_ID" \
--query DistributionConfig.Origins
Домен S3 и OriginAccessControlId должны соответствовать вашему бакету и OAC. Перед тестом дождитесь развёртывания распределения в плоскости управления. Официальный waiter повторно читает статус и не создаёт клиентский трафик:
aws cloudfront wait distribution-deployed \
--id "$DIST_ID"
Проверьте клиентский путь. --resolve направляет это точное имя распределения и учебный порт к подготовленному endpoint доставки в VM; он не меняет системный DNS и не регистрирует домен:
curl --noproxy '*' \
--resolve "${DIST_DOMAIN}:8082:127.0.0.1" \
--output /dev/null \
--write-out 'Viewer before permission: HTTP %{http_code}\n' \
"http://${DIST_DOMAIN}:8082/index.html"
Ожидайте HTTP 403. Создание распределения и выбор OAC описывают соединение; S3 всё ещё нужна политика, разрешающая этому распределению доступ. Не делайте бакет публичным для исправления результата. Выполните проверку соединения.

Разрешите доступ выбранному распределению
На этом шаге разрешите выбранному распределению CloudFront читать объекты, сохранив запрет анонимного прямого доступа к источнику.
ARN идентифицирует ресурс AWS и его аккаунт. Получите ARN распределения:
DIST_ARN=$(aws cloudfront get-distribution \
--id "$DIST_ID" \
--query Distribution.ARN \
--output text)
Политика бакета указывает cloudfront.amazonaws.com как сервисного субъекта (principal), разрешает только s3:GetObject и ограничивает разрешение объектами вашего бакета. Условие AWS:SourceArn ограничивает запрос сервиса конкретным распределением. Это отличается от публичной политики с Principal: "*". Запишите политику с полученным ARN:
cat > bucket-policy.json <<JSON
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {"Service": "cloudfront.amazonaws.com"},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::labex-n02-content/*",
"Condition": {"StringEquals": {"AWS:SourceArn": "$DIST_ARN"}}
}]
}
JSON
aws s3api put-bucket-policy \
--bucket labex-n02-content \
--policy file://bucket-policy.json
Снова запросите объект. Теперь --fail завершает команду ошибкой при неуспешном HTTP-статусе, а --include показывает заголовки вместе с реальным документом:
curl --fail --include --noproxy '*' \
--resolve "${DIST_DOMAIN}:8082:127.0.0.1" \
"http://${DIST_DOMAIN}:8082/index.html"
Ожидайте HTTP 200, тип содержимого text/html и страницу с Release one. Вы получили реальные байты объекта через распределение, а не только успешный ответ конфигурации.
Сразу повторите анонимную прямую проверку источника:
curl --noproxy '*' \
--output /dev/null \
--write-out 'Direct origin after viewer success: HTTP %{http_code}\n' \
http://127.0.0.1:5000/labex-n02-content/index.html
Она должна по-прежнему возвращать HTTP 403. Клиентский путь работает, а прямой источник остаётся закрытым. AWS View показывает подключённый источник и последний клиентский результат. Выполните проверку доступа.

Удалите только свои ресурсы доставки
На этом шаге отключите и удалите распределение перед удалением OAC и содержимого S3. Сохраните эталонный бакет без изменений.
CloudFront использует ETag как токен версии для изменения конфигурации. Получите текущую конфигурацию и её ETag, не угадывая токен:
aws cloudfront get-distribution-config \
--id "$DIST_ID" \
--query DistributionConfig \
--output json > distribution-current.json
DIST_ETAG=$(aws cloudfront get-distribution-config \
--id "$DIST_ID" \
--query ETag \
--output text)
В этой конфигурации Enabled распределения — единственное одноимённое свойство со значением true. Следующая стандартная замена sed создаёт отключённую копию, сохраняя настройки источника:
sed 's/"Enabled": true/"Enabled": false/' distribution-current.json > distribution-disabled.json
aws cloudfront update-distribution \
--id "$DIST_ID" \
--if-match "$DIST_ETAG" \
--distribution-config file://distribution-disabled.json
Дождитесь развёртывания отключённой конфигурации. В AWS распространение изменений может занять время; упражнение не измеряет глобальную задержку развёртывания:
aws cloudfront wait distribution-deployed \
--id "$DIST_ID"
Обновление меняет ETag. Перед удалением отключённого распределения прочитайте новейший токен:
DIST_ETAG=$(aws cloudfront get-distribution-config \
--id "$DIST_ID" \
--query ETag \
--output text)
aws cloudfront delete-distribution \
--id "$DIST_ID" \
--if-match "$DIST_ETAG"
Удалите OAC с его собственным ETag. ID распределения и ID OAC относятся к разным ресурсам:
OAC_ETAG=$(aws cloudfront get-origin-access-control \
--id "$OAC_ID" \
--query ETag \
--output text)
aws cloudfront delete-origin-access-control \
--id "$OAC_ID" \
--if-match "$OAC_ETAG"
Удалите только созданные вами объект и бакет:
aws s3api delete-object \
--bucket labex-n02-content \
--key index.html
aws s3api delete-bucket \
--bucket labex-n02-content
aws s3api list-buckets \
--query Buckets[].Name
Эталонный бакет должен остаться, а бакет содержимого — отсутствовать. AWS View должен показывать отсутствие распределения содержимого и только эталонный объект. Выполните проверку очистки. Неуспешный API-запрос не доказывает удаление ресурса.

Резюме
Вы соединили распределение с обычным закрытым источником S3, настроили всегда подписываемые запросы OAC и разрешили чтение только выбранному распределению. Реальные HTTP-тесты различили разрешённый клиентский доступ и запрещённый анонимный доступ к источнику. Затем вы использовали актуальные ETag для отключения и удаления своих ресурсов, сохранив независимое содержимое. Далее наблюдайте повторное использование кэша и инвалидируйте обновлённый объект.



