Skip to main content
emnode
Compliance Medium severity

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?

  1. Generate an RSA key pair and upload the public key to CloudFront with `create-public-key`.
  2. Wrap it in a trusted key group with `create-key-group`.
  3. 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.
  4. 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.

Part of the learning path Lock down access