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?
- 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.
- Set the custom origin's `OriginProtocolPolicy` to `https-only`, or pair `match-viewer` with a viewer policy of `redirect-to-https` or `https-only`.
- Apply the change with the ETag-guarded `get-distribution-config` / `update-distribution` flow.
- 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.
More CloudFront controls
- CloudFront.1 No default root object, exposing the distribution listing
- CloudFront.3 Distributions should require encryption in transit
- CloudFront.5 Distributions should have logging enabled
- CloudFront.6 Distributions should have WAF enabled
- CloudFront.10 No deprecated SSL protocols to custom origins
- CloudFront.12 A distribution points at a non-existent S3 origin (takeover risk)
- CloudFront.13 Distributions should use origin access control
- CloudFront.15 Distributions should use recommended TLS policy
- CloudFront.16 OAC for Lambda function URL origins
- CloudFront.17 Use trusted key groups for signed URLs/cookies