Skip to main content
emnode / learn
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