AWS Security Hub · CloudFront
CloudFront.17: Use trusted key groups for signed URLs/cookies
Written and reviewed by Emnode · Last reviewed
What does AWS Security Hub CloudFront.17 check?
CloudFront.17 checks that a distribution serving signed URLs or signed cookies uses trusted key groups rather than the legacy trusted-signer model. It reports FAILED when a cache behaviour still lists `TrustedSigners`, or has signed-URL behaviour configured with no signer at all.
Why does CloudFront.17 matter?
The trusted-signer model forces signing-key creation and rotation through the AWS account root user, the one credential that can't be scoped by IAM and should stay locked behind hardware MFA and almost never used. That means teams either expose root to rotate keys or never rotate them and run on a stale, possibly orphaned key. Root usage is also the most scrutinised event in any account, so this is a recurring audit and SOC 2 friction point.
How do I fix CloudFront.17?
- Generate an RSA key pair and upload the public key to CloudFront with `create-public-key`.
- Wrap it in a trusted key group with `create-key-group`.
- Repoint the application's URL-signing code at the new private key and public-key ID first; URLs signed with the new key validate immediately.
- Switch the cache behaviour from `TrustedSigners` to `TrustedKeyGroups` via the ETag-guarded update; existing signed URLs keep working throughout.
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.17 a false positive?
CloudFront.17 does not evaluate multi-tenant distributions (`connectionMode=tenant-only`), so those won't generate findings even when signed-URL behaviour is in use.
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.9 Distributions should encrypt traffic to custom origins
- 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