Distribuir conteúdo privado do S3 com CloudFront

AWSBeginner
Pratique Agora

Introdução

Sua equipe quer que os clientes leiam uma versão pelo CloudFront, mantendo a origem S3 privada. Envie a página fornecida, conecte uma distribuição e um controle de acesso à origem e conceda somente a essa distribuição permissão para ler os objetos. Teste o caminho funcional do cliente e o acesso direto à origem que deve ser negado.

Conclua primeiro AWS Foundations, operações de objetos S3 e conceitos de políticas de recursos do IAM. Esta VM independente fornece acesso AWS CLI configurado, index.html e um bucket de referência separado. Observe a distribuição e a origem no AWS View, acima, e trabalhe no Terminal, abaixo. Não é necessária uma conta AWS pessoal nem um domínio público. Este laboratório utiliza uma origem S3 comum; cache do cliente e HTTPS serão tratados separadamente.

Relação com a certificação

Certificação Tarefa do exame Prática
Solutions Architect – Associate (SAA-C03) Tarefa 1.1 Usar uma política de recursos para restringir leituras do S3 à distribuição CloudFront desejada.

Visão geral do laboratório

Diagrama conceitual: o cliente solicita conteúdo ao CloudFront, autorizado a ler a origem S3 privada; uma solicitação anônima direta é negada.

Preparar o conteúdo da origem privada

Nesta etapa, crie um bucket S3 privado e envie a página preparada.

A origem armazena o conteúdo que o CloudFront busca. Um viewer é o cliente que solicita conteúdo ao CloudFront. O acesso do cliente e o acesso à origem são permissões diferentes: uma página pode ser lida pela distribuição enquanto leituras anônimas diretas do S3 continuam negadas.

Trabalhe no diretório do projeto fornecido. Mantenha intacto o bucket de referência labex-n02-reference:

cd /home/labex/project
cat index.html
aws s3api create-bucket \
  --bucket labex-n02-content

Defina a propriedade dos objetos como Bucket owner enforced. O proprietário do bucket mantém a propriedade e as concessões por ACL são desativadas; OAC utiliza a política do bucket. Ative as quatro proteções Block Public Access para impedir concessões públicas por ACL ou política:

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}'

Envie a página fornecida. --content-type text/html descreve o objeto como documento 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

Examine o tamanho do objeto e ContentType. A solicitação CLI está autenticada como o operador configurado. Compare com uma solicitação HTTP anônima, sem credenciais 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

Espere Direct origin: HTTP 403. --output /dev/null descarta o corpo do erro; --write-out imprime o status HTTP. Este endpoint explícito de prática testa o acesso direto ao objeto S3 sem tornar o bucket público. Execute a verificação da origem privada.

Exemplo após o envio: o objeto de origem de 91 bytes aparece ao lado da referência intacta; nenhuma distribuição foi criada.

Conectar a distribuição à origem

Nesta etapa, conecte uma distribuição CloudFront ao bucket S3 e observe que somente a conexão não concede permissão na origem.

Um controle de acesso à origem (OAC) informa como o CloudFront autentica suas solicitações à origem. Selecione S3, Signature Version 4 e assinatura always. Escreva a configuração CLI comum:

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)

$(...) salva o ID de OAC retornado em OAC_ID. --query seleciona apenas o ID para a próxima configuração referenciá-lo. Mantenha este Terminal aberto durante todo o laboratório.

A configuração conecta content-origin ao endpoint comum do bucket S3, não a um endpoint de site S3. TargetOriginId seleciona a origem; DefaultRootObject associa / a index.html. O bloco legado ForwardedValues evita encaminhar cookies ou strings de consulta. Todos os TTLs são zero, impedindo que o teste de permissões reutilize um sucesso em cache. allow-all permite a solicitação HTTP do cliente utilizada aqui; HTTPS é ensinado separadamente.

O próximo here-document está sem aspas, portanto o shell expande $OAC_ID no arquivo 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)

Examine a origem conectada:

aws cloudfront get-distribution-config \
  --id "$DIST_ID" \
  --query DistributionConfig.Origins

O domínio S3 e OriginAccessControlId devem corresponder ao bucket e ao OAC. Aguarde a implantação no plano de controle antes do teste. O waiter oficial consulta repetidamente o status, sem gerar tráfego de clientes:

aws cloudfront wait distribution-deployed \
  --id "$DIST_ID"

Agora teste o caminho do cliente. --resolve envia este nome exato da distribuição e a porta de prática ao endpoint de entrega fornecido na VM; não altera o DNS do sistema nem registra um domínio:

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"

Espere HTTP 403. Criar a distribuição e selecionar um OAC descreve uma conexão; o S3 ainda precisa de uma política que conceda acesso a essa distribuição. Não torne o bucket público para corrigir o resultado. Execute a verificação da conexão.

Exemplo antes da permissão de origem: a distribuição conectada retorna HTTP 403 à solicitação real do cliente.

Conceder acesso à distribuição desejada

Nesta etapa, permita que a distribuição CloudFront selecionada leia os objetos, mantendo negado o acesso anônimo direto à origem.

Um ARN identifica um recurso AWS e sua conta. Leia o ARN da distribuição:

DIST_ARN=$(aws cloudfront get-distribution \
  --id "$DIST_ID" \
  --query Distribution.ARN \
  --output text)

A política do bucket identifica cloudfront.amazonaws.com como principal de serviço, permite apenas s3:GetObject e limita a concessão aos objetos do bucket. A condição AWS:SourceArn restringe a solicitação do serviço à sua distribuição específica. Essa concessão difere de uma política pública com Principal: "*". Escreva-a usando o ARN obtido:

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

Solicite o objeto novamente. --fail agora faz o comando falhar diante de um status HTTP sem sucesso, e --include mostra os cabeçalhos junto com o documento real:

curl --fail --include --noproxy '*' \
  --resolve "${DIST_DOMAIN}:8082:127.0.0.1" \
  "http://${DIST_DOMAIN}:8082/index.html"

Espere HTTP 200, tipo de conteúdo text/html e a página contendo Release one. Você observou os bytes do objeto pela distribuição, além de uma resposta de configuração bem-sucedida.

Repita imediatamente o teste anônimo direto à origem:

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

Ele ainda deve retornar HTTP 403. O caminho do cliente funciona, enquanto a origem direta permanece privada. AWS View mostra a origem conectada e o último resultado do cliente. Execute a verificação do acesso.

Exemplo após a permissão: a solicitação real do cliente retorna HTTP 200 pela distribuição conectada.

Remover somente seus recursos de entrega

Nesta etapa, desative e exclua a distribuição antes de remover o OAC e o conteúdo S3. Mantenha intacto o bucket de referência.

O CloudFront utiliza um ETag como token de versão para mudanças de configuração. Obtenha a configuração e seu ETag atuais, sem adivinhar o token:

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)

Nesta configuração, Enabled da distribuição é a única propriedade desse nome com valor true. A substituição padrão com sed produz uma cópia desativada e mantém as configurações da origem:

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

Aguarde a implantação da configuração desativada. Na AWS, a propagação pode levar tempo; este exercício não mede a latência global de implantação:

aws cloudfront wait distribution-deployed \
  --id "$DIST_ID"

A atualização altera o ETag. Leia o token mais recente antes de excluir a distribuição desativada:

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"

Remova o OAC com seu próprio ETag. O ID da distribuição e o do OAC representam recursos diferentes:

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"

Remova somente o objeto e o bucket que você criou:

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

O bucket de referência deve permanecer e o de conteúdo deve estar ausente. AWS View deve mostrar nenhuma distribuição de conteúdo e somente o objeto de referência. Execute a verificação da limpeza. Uma solicitação API malsucedida não comprova a exclusão de um recurso.

Exemplo após a limpeza: a distribuição e o bucket de conteúdo foram removidos; o objeto de referência independente permanece.

Resumo

Você conectou uma distribuição a uma origem S3 privada comum, configurou solicitações OAC sempre assinadas e concedeu leitura somente à distribuição desejada. Testes HTTP reais distinguiram o acesso permitido do cliente do acesso anônimo à origem negado. Depois, utilizou ETags atuais para desativar e remover seus recursos, preservando o conteúdo independente. Em seguida, observe a reutilização do cache e invalide um objeto atualizado.