Skip to main content
emnode
Compliance Medium severity

AWS Security Hub · CloudFront

CloudFront.9: Distributions should encrypt traffic to custom origins

Written and reviewed by Emnode · Last reviewed

What does AWS Security Hub CloudFront.9 check?

CloudFront.9 checks that a distribution encrypts traffic to its custom origins. It reports FAILED when a custom origin's `OriginProtocolPolicy` is `http-only`, or when it is `match-viewer` while the `ViewerProtocolPolicy` is `allow-all` (both of which allow a plaintext path through to the origin). S3 (non-website) origins, which CloudFront always reaches over HTTPS, are out of scope.

Why does CloudFront.9 matter?

The browser padlock only covers the viewer-to-edge leg. When CloudFront fetches from a custom origin over plain HTTP, the full request and response (URLs, headers, session cookies, authorization tokens, payloads) cross the public internet in clear text where anyone on the path can read or tamper with them. It maps to PCI DSS 4.2.1 and the NIST data-in-transit controls.

How do I fix CloudFront.9?

  1. Verify the origin has a valid HTTPS listener on port 443 with a publicly-trusted certificate first; flipping to `https-only` against an origin that can't serve TLS produces 502s.
  2. Set the custom origin's `OriginProtocolPolicy` to `https-only`, or pair `match-viewer` with a viewer policy of `redirect-to-https` or `https-only`.
  3. Apply the change with the ETag-guarded `get-distribution-config` / `update-distribution` flow.
  4. Make encrypted-to-origin the default in your distribution IaC so new origins can't ship on `http-only`.

Remediation script · bash

# 1. Lock an S3 origin: only THIS distribution may read the bucket.
cat > bucket-policy.json <<'JSON'
{
  "Version": "2012-10-17",
  "Statement": [{
    "Sid": "AllowCloudFrontServicePrincipalReadOnly",
    "Effect": "Allow",
    "Principal": { "Service": "cloudfront.amazonaws.com" },
    "Action": "s3:GetObject",
    "Resource": "arn:aws:s3:::my-downloads-bucket/*",
    "Condition": { "StringEquals": {
      "AWS:SourceArn": "arn:aws:cloudfront::111122223333:distribution/E2QWRUHAPOMQZL"
    } }
  }]
}
JSON
aws s3api put-bucket-policy --bucket my-downloads-bucket --policy file://bucket-policy.json

# 2. Raise the minimum TLS version (the field is nested in ViewerCertificate, so send the whole config back).
aws cloudfront get-distribution-config --id E2QWRUHAPOMQZL > dist.json
ETAG=$(python3 -c "import json;print(json.load(open('dist.json'))['ETag'])")
# ... edit dist.json: ViewerCertificate.MinimumProtocolVersion = TLSv1.2_2021, ViewerProtocolPolicy = redirect-to-https ...
aws cloudfront update-distribution --id E2QWRUHAPOMQZL \
  --distribution-config file://distribution-config.json --if-match "$ETAG"

# 3. Confirm the second door is shut: a raw S3 GET should now return 403.
curl -s -o /dev/null -w '%{http_code}\n' https://my-downloads-bucket.s3.amazonaws.com/file.pdf

Full walkthrough (console steps, edge cases and verification) in the lesson Protect CloudFront distributions and origins.

Is CloudFront.9 a false positive?

CloudFront only trusts publicly-chained certificates on custom origins: a self-signed cert on the origin will break `https-only` even though the policy change itself is what the control wants.

Part of the learning path Lock down access