AWS CloudFront


本文缩写(英文全称与中文含义)
缩写 英文全称/名称 中文含义
ACL Access Control List 访问控制列表
ACM AWS Certificate Manager AWS 证书管理服务
ALB Application Load Balancer 应用负载均衡器
API Application Programming Interface 应用程序编程接口
AWS Amazon Web Services 亚马逊云服务
CDK Cloud Development Kit AWS 云开发工具包
CDN Content Delivery Network 内容分发网络
CIDR Classless Inter-Domain Routing 无类别域间路由,用于表示网段
CLI Command-Line Interface 命令行接口
CSP Content Security Policy 内容安全策略
DASH Dynamic Adaptive Streaming over HTTP 基于 HTTP 的动态自适应流媒体
ECDSA Elliptic Curve Digital Signature Algorithm 椭圆曲线数字签名算法
ECS Elastic Container Service AWS 容器编排服务
HLS HTTP Live Streaming 基于 HTTP 的流媒体协议
HSTS HTTP Strict Transport Security HTTP 严格传输安全策略
HTTP Hypertext Transfer Protocol 超文本传输协议
HTTPS Hypertext Transfer Protocol Secure 使用 TLS 加密的 HTTP
IaC Infrastructure as Code 基础设施即代码
IAM Identity and Access Management 身份与访问管理
ID Identifier 标识符
IP Internet Protocol 网际协议;IP 地址是网络寻址标识
JSON JavaScript Object Notation 一种结构化数据格式
JWT JSON Web Token 一种携带声明的令牌格式
KMS Key Management Service 密钥管理服务
OAC Origin Access Control CloudFront 源站访问控制
OAI Origin Access Identity CloudFront 旧式源站访问身份
PDF Portable Document Format 便携式文档格式
RSA Rivest-Shamir-Adleman 一种公钥密码算法
S3 Simple Storage Service AWS 对象存储服务
SPA Single-Page Application 单页应用
SSE Server-Side Encryption 服务端加密
TLS Transport Layer Security 传输层安全协议
TTL Time To Live 存活时间;具体指缓存有效期或记录过期时间
URL Uniform Resource Locator 统一资源定位符,通常指访问地址
WAF Web Application Firewall Web 应用防火墙

按国家展示不同页面、缓存隔离及完整实验,见 CloudFront 国家内容分流。

https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Introduction.html
https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-overview.html
https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-signed-urls.html
https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-signed-cookies.html
https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-trusted-signers.html
https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html
https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html#oac-overview
https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-choosing-signed-urls-cookies.html
https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/controlling-the-cache-key.html
https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/cache-key-understand-cache-policy.html
https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/QueryStringParameters.html
https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/function-code-choose-purpose.html
https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-edge-event-request-response.html
https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/understanding-response-headers-policies.html
https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/creating-response-headers-policies.html
https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/distribution-web-awswaf.html
https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/standard-logging.html
https://docs.aws.amazon.com/cli/latest/reference/cloudfront/sign.html

1. Important Points#

CloudFront 是 CDN:
    cache static / dynamic content at edge
    reduce origin traffic
    lower latency
    support HTTPS / WAF / geo restriction / signed URL / signed cookie

常见 origin:
    S3 bucket
    ALB
    API Gateway
    custom HTTP origin

核心原则:
    S3 origin should use OAC, not public bucket
    ACM cert for CloudFront must be in us-east-1
    signed URL / signed cookie protects viewer access
    OAC protects origin access
    cache policy and origin request policy must be explicit
CloudFront signed URL != S3 presigned URL:
    CloudFront signed URL:
        viewer -> CloudFront
        signed by your app with CloudFront private key
        verified by CloudFront public key / key group

    S3 presigned URL:
        viewer -> S3
        signed by AWS credentials
        verified by S3 IAM auth

private content with S3:
    viewer must use CloudFront signed URL / cookie
    S3 bucket should only allow CloudFront distribution through OAC

2. Service Configuration#

distribution#

Item Recommendation
Origin S3 / ALB / custom origin
S3 access use OAC
Viewer protocol redirect HTTP to HTTPS
TLS cert ACM in us-east-1
Cache policy explicit policy per behavior
Origin request policy forward only required headers/cookies/query strings
WAF enable for public endpoints
Logs standard logs / real-time logs when needed
Private content trusted key group + signed URL/cookie

cache behavior#

cache behavior controls:
    path pattern
    origin
    viewer protocol policy
    allowed methods
    cache policy
    origin request policy
    response headers policy
    trusted key groups

private content:
    enable Restrict viewer access
    choose Trusted key groups
    attach key group

cache policy and cache key#

CloudFront cache hit 的核心是 cache key。

default cache key:
    distribution domain
    URL path

cache policy can add:
    query strings
    headers
    cookies
    compression variant

TTL settings:
    minimum TTL
    default TTL
    maximum TTL
原则:
    only include values that change the response body.

    如果 origin 对同一个 path 返回完全相同的文件,
    不要把 query string / cookie / user-specific header 放进 cache key。

    如果 origin 会根据某个值返回不同内容,
    才把这个值放进 cache key。
Request Value Cache Policy Choice When To Use
Query strings: None best for static objects / private downloads URL query 不影响文件内容
Query strings: allowlist image resize / language / version parameter 只有指定参数会改变响应
Query strings: All rare 每个 query variation 都是独立缓存对象
Headers: None default for static content header 不影响响应
Headers: allowlist Accept-Language, device variant, negotiated content origin 根据 header 返回不同内容
Cookies: None default for cacheable static content cookie 不影响响应
Cookies: allowlist A/B variant or explicit content variant origin 根据指定 cookie 返回不同内容
bad cache key examples:
    /audio.mp3?token=random-1
    /audio.mp3?token=random-2

    if token is included in cache key:
        two different cache entries
        lower CacheHitRate
        more origin requests

good cache key:
    /audio.mp3

    token/auth is validated outside the cache key
    same object can be served from edge cache

signed URL and cache key#

CloudFront signed URL parameters:
    Expires
    Signature
    Key-Pair-Id
    Policy

这些参数表达 viewer authorization,
不是 object content variant。

private static object recommendation:
    S3 stays private behind OAC
    CloudFront behavior requires trusted key group
    cache policy query strings = None
        or allowlist only real content-variant query strings
    origin request policy forwards only what origin really needs
do not:
    use Query strings: All for static private downloads
    forward all viewer cookies by default
    include Authorization / session / random token in cache key unless response differs per value
    use Expires / Signature / Key-Pair-Id / Policy as your own application query parameter names

do:
    separate authorization from content variation
    keep cache key small
    monitor CacheHitRate after changing cache policy
{
  "Name": "private-static-downloads",
  "DefaultTTL": 86400,
  "MaxTTL": 31536000,
  "MinTTL": 0,
  "ParametersInCacheKeyAndForwardedToOrigin": {
    "EnableAcceptEncodingGzip": true,
    "EnableAcceptEncodingBrotli": true,
    "HeadersConfig": {
      "HeaderBehavior": "none"
    },
    "CookiesConfig": {
      "CookieBehavior": "none"
    },
    "QueryStringsConfig": {
      "QueryStringBehavior": "none"
    }
  }
}
when query string really changes content:
    /image.jpg?width=300
    /image.jpg?width=600

cache policy:
    Query strings = Include specified query strings
    include:
        width

result:
    width=300 and width=600 are two valid cache objects
    other tracking/auth query strings do not split the cache

3. Private Content Concepts#

signer#

signer:
    entity allowed to create signed URL / signed cookie

recommended:
    trusted key group

legacy:
    trusted AWS account / CloudFront key pair
    not recommended for new setup

public key / private key#

private key:
    kept by application
    used to sign URL / cookie
    must be stored securely
    rotate regularly

public key:
    uploaded to CloudFront
    used by CloudFront to verify signature
    belongs to a key group

supported key types:
    RSA 2048
    ECDSA 256

key group#

key group:
    contains one or more CloudFront public keys
    attached to cache behavior
    CloudFront uses public keys in key group to verify signatures

why key group:
    recommended by AWS
    no root account key pair management
    supports key rotation
    can be reused by multiple distributions / behaviors

Key-Pair-Id#

Key-Pair-Id in signed URL:
    query parameter name:
        Key-Pair-Id

    trusted key group mode:
        value = CloudFront Public Key ID
        not Key Group ID

    legacy trusted signer mode:
        value = legacy CloudFront key pair ID / access key ID

example signed URL query:
    Expires=1790599200
    Signature=<signature>
    Key-Pair-Id=K1234567890ABC
Mode When To Use Notes
Signed URL one file / download link / short-lived access URL contains signature query parameters
Signed Cookie many files under a path / website area / video assets browser sends three CloudFront cookies
signed URL:
    good for:
        private file download
        one video file
        one image/object

signed cookie:
    good for:
        /paid/*
        HLS video segments
        many objects in one protected area

priority:
    if both signed URL and signed cookie exist for same request,
    CloudFront evaluates the signed URL.

canned policy vs custom policy#

Policy Supports When To Use
Canned resource + expiration simple expiration
Custom resource + start time + expiration + IP range stricter access control
canned policy:
    simpler
    URL has Expires / Signature / Key-Pair-Id

custom policy:
    supports:
        DateGreaterThan
        DateLessThan
        IpAddress
        wildcard resource
    URL has Policy / Signature / Key-Pair-Id

5. Complete Key Group Flow#

variables#

export AWS_PAGER=""
export DIST_ID="E1234567890ABC"
export ACCOUNT_ID="123456789012"
export KEY_NAME="prod-order-private-content-2026-05"
export KEY_GROUP_NAME="prod-order-private-content"
export CF_DOMAIN="d111111abcdef8.cloudfront.net"

generate RSA key pair#

mkdir -p cloudfront-signing
cd cloudfront-signing

openssl genrsa -out cf-private-key.pem 2048
openssl rsa -pubout -in cf-private-key.pem -out cf-public-key.pem

chmod 0400 cf-private-key.pem
chmod 0644 cf-public-key.pem
files:
    cf-private-key.pem:
        application uses it to sign URL/cookie
        never upload to CloudFront
        never commit to git

    cf-public-key.pem:
        upload to CloudFront
        can be stored in IaC repo if allowed by policy

generate ECDSA key pair#

openssl ecparam -name prime256v1 -genkey -noout -out cf-private-key.pem
openssl ec -in cf-private-key.pem -pubout -out cf-public-key.pem

chmod 0400 cf-private-key.pem
chmod 0644 cf-public-key.pem
choose one:
    RSA 2048:
        widely used
        compatible with older examples

    ECDSA 256:
        smaller signature
        shorter URL
        newer support

validate key pair#

echo "hello" > message.txt

openssl dgst -sha256 -sign cf-private-key.pem -out message.sig message.txt
openssl dgst -sha256 -verify cf-public-key.pem -signature message.sig message.txt
expected:
    Verified OK

create CloudFront public key#

jq -n --arg caller_reference "$(date +%s)-${KEY_NAME}" --arg name "${KEY_NAME}" --rawfile encoded_key cf-public-key.pem '{
    CallerReference: $caller_reference,
    Name: $name,
    EncodedKey: $encoded_key,
    Comment: "public key for CloudFront signed URLs/cookies"
  }' > public-key-config.json
aws cloudfront create-public-key --public-key-config file://public-key-config.json --query 'PublicKey.Id' --output text
export PUBLIC_KEY_ID="K1234567890ABC"
PUBLIC_KEY_ID:
    this is the value used as Key-Pair-Id when signing URL/cookie

create key group#

jq -n --arg name "${KEY_GROUP_NAME}" --arg public_key_id "${PUBLIC_KEY_ID}" '{
    Name: $name,
    Comment: "trusted key group for private content",
    Items: [$public_key_id]
  }' > key-group-config.json
aws cloudfront create-key-group --key-group-config file://key-group-config.json --query 'KeyGroup.Id' --output text
export KEY_GROUP_ID="KG1234567890ABC"
KEY_GROUP_ID:
    attach this to cache behavior TrustedKeyGroups

PUBLIC_KEY_ID:
    pass this as Key-Pair-Id to signing code / aws cloudfront sign

attach key group to cache behavior#

console path:
    CloudFront
    -> Distributions
    -> <distribution>
    -> Behaviors
    -> <path pattern>
    -> Edit
    -> Restrict viewer access: Yes
    -> Trusted authorization type: Trusted key groups
    -> Trusted key groups: <KEY_GROUP_ID>
CLI / IaC:
    update distribution config
    set cache behavior:
        TrustedKeyGroups.Enabled = true
        TrustedKeyGroups.Quantity = 1
        TrustedKeyGroups.Items = [KEY_GROUP_ID]
{
  "TrustedKeyGroups": {
    "Enabled": true,
    "Quantity": 1,
    "Items": [
      "KG1234567890ABC"
    ]
  }
}
warning:
    CloudFront distribution update needs ETag / If-Match
    safer to manage this with Terraform / CloudFormation / CDK
    for manual setup, use console first

6. Sign URL#

AWS CLI canned policy#

aws cloudfront sign --url "https://${CF_DOMAIN}/private/video.mp4" --key-pair-id "${PUBLIC_KEY_ID}" --private-key file://cf-private-key.pem --date-less-than "2026-05-29T12:00:00Z"
output:
    https://d111111abcdef8.cloudfront.net/private/video.mp4?Expires=...&Signature=...&Key-Pair-Id=K1234567890ABC

AWS CLI custom policy#

aws cloudfront sign --url "https://${CF_DOMAIN}/private/video.mp4" --key-pair-id "${PUBLIC_KEY_ID}" --private-key file://cf-private-key.pem --date-greater-than "2026-05-29T10:00:00Z" --date-less-than "2026-05-29T12:00:00Z" --ip-address "203.0.113.0/24"
custom policy use cases:
    not valid before start time
    expires at end time
    only allowed from specific IP / CIDR

test#

SIGNED_URL="$(aws cloudfront sign --url "https://${CF_DOMAIN}/private/video.mp4" --key-pair-id "${PUBLIC_KEY_ID}" --private-key file://cf-private-key.pem --date-less-than "2026-05-29T12:00:00Z")"

curl -I "${SIGNED_URL}"
expected:
    200 / 206 / 304 if object exists and policy valid

common failure:
    403 InvalidKey:
        wrong Key-Pair-Id
        public key not in trusted key group
        key group not attached to matching cache behavior

    403 AccessDenied:
        signed URL expired
        signature mismatch
        URL changed after signing
        origin access denied

7. Sign URL In Java#

dependency#

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>software.amazon.awssdk</groupId>
      <artifactId>bom</artifactId>
      <version>${aws.sdk.version}</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

<dependency>
  <groupId>software.amazon.awssdk</groupId>
  <artifactId>cloudfront</artifactId>
</dependency>

code#

import software.amazon.awssdk.services.cloudfront.CloudFrontUtilities;
import software.amazon.awssdk.services.cloudfront.model.CannedSignerRequest;
import software.amazon.awssdk.services.cloudfront.url.SignedUrl;

import java.nio.file.Path;
import java.time.Instant;

public class CloudFrontSignedUrlExample {
    public static void main(String[] args) {
        String resourceUrl = "https://d111111abcdef8.cloudfront.net/private/video.mp4";
        String publicKeyId = "K1234567890ABC";
        Path privateKeyPath = Path.of("cf-private-key.pem");

        CloudFrontUtilities utilities = CloudFrontUtilities.create();

        CannedSignerRequest request = CannedSignerRequest.builder()
                .resourceUrl(resourceUrl)
                .privateKey(privateKeyPath)
                .keyPairId(publicKeyId)
                .expirationDate(Instant.parse("2026-05-29T12:00:00Z"))
                .build();

        SignedUrl signedUrl = utilities.getSignedUrlWithCannedPolicy(request);

        System.out.println(signedUrl.url());
    }
}
publicKeyId:
    same as CloudFront Public Key ID
    appears in URL as Key-Pair-Id

8. Signed Cookies#

cookies#

canned policy cookies:
    CloudFront-Expires
    CloudFront-Signature
    CloudFront-Key-Pair-Id

custom policy cookies:
    CloudFront-Policy
    CloudFront-Signature
    CloudFront-Key-Pair-Id
use signed cookies when:
    user needs access to many files
    URL should not change
    HLS/DASH video has many segment files
recommended:
    Secure
    HttpOnly
    SameSite=Lax or SameSite=None when cross-site is required
    Domain=<your domain>
    Path=/private/
Set-Cookie: CloudFront-Expires=1790599200; Path=/private/; Secure; HttpOnly; SameSite=Lax
Set-Cookie: CloudFront-Signature=<signature>; Path=/private/; Secure; HttpOnly; SameSite=Lax
Set-Cookie: CloudFront-Key-Pair-Id=K1234567890ABC; Path=/private/; Secure; HttpOnly; SameSite=Lax

9. Origin Auth vs Viewer Auth#

two different permission surfaces:
    origin auth:
        CloudFront -> S3
        protects S3 origin from direct public access
        use OAC / OAI + S3 bucket policy

    viewer auth:
        user/browser -> CloudFront
        protects CloudFront URL from public access
        use signed URL / signed cookie / auth at app or edge
origin auth 的作用:
    users cannot bypass CloudFront and read S3 object directly
    S3 bucket can stay private
    S3 Block Public Access can stay enabled
    bucket policy allows CloudFront distribution as origin reader

origin auth 不做什么:
    it does not restrict who can access CloudFront distribution URL
    it does not require signed URL/cookie by itself
    it does not authenticate viewer identity

结果:
    如果 CloudFront viewer side 是公开的,
    任何人拿到 CloudFront URL 仍然可以访问内容。
Layer Traffic Control Protects
Origin auth CloudFront -> S3 OAC / OAI + S3 bucket policy S3 cannot be accessed directly
Viewer auth Viewer -> CloudFront signed URL / signed cookie / application auth CloudFront URL cannot be used by everyone
common mistake:
    "I enabled OAC, so CloudFront content is private."

correct:
    OAC makes S3 private from direct access.
    signed URL / signed cookie makes CloudFront viewer access private.
recommended private S3 content design:
    S3:
        Block Public Access enabled
        bucket policy allows only CloudFront distribution through OAC

    CloudFront origin:
        use OAC
        regular S3 bucket origin, not S3 website endpoint

    CloudFront viewer:
        attach trusted key group to behavior
        require signed URL / signed cookie
        keep URL/cookie TTL short

minimal S3 bucket policy for OAC#

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowCloudFrontOACRead",
      "Effect": "Allow",
      "Principal": {
        "Service": "cloudfront.amazonaws.com"
      },
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::my-private-bucket/*",
      "Condition": {
        "StringEquals": {
          "AWS:SourceArn": "arn:aws:cloudfront::123456789012:distribution/E1234567890ABC"
        }
      }
    }
  ]
}
这个 policy 表达的是:
    only this CloudFront distribution can read objects from this bucket
    users cannot access S3 object directly
    viewer still needs signed URL/cookie if CloudFront URL should not be public

replace:
    my-private-bucket:
        your S3 bucket name

    123456789012:
        your AWS account ID

    E1234567890ABC:
        your CloudFront distribution ID

10. Viewer Auth#

viewer auth 保护的是:
    who can access CloudFront URL
    who can watch/download the file through CloudFront
    whether a copied CloudFront URL still works for another user

viewer auth 不保护:
    S3 direct access
    origin bucket policy
    application authorization after origin receives request

需要同时理解:
    OAC/OAI protects origin access
    viewer auth protects viewer access
Method Best For Notes
CloudFront signed URL one object / one download link / short TTL URL carries signature query parameters
CloudFront signed cookies many files under one path / video segments / website area browser sends CloudFront cookies
CloudFront Function token check simple header/cookie/query token validation at viewer request lightweight JavaScript, can return 401/403 early
Lambda@Edge token check more complex auth logic at edge heavier than CloudFront Function, can run on viewer request

signed URL#

use signed URL when:
    user receives one file link
    each link has short expiration
    link is generated by application after authorization
    URL sharing risk is acceptable within TTL / policy

common examples:
    private report download
    one invoice PDF
    one video file

important:
    signed URL is bearer access.
    Whoever has the valid signed URL can use it until policy expires.

signed cookies#

use signed cookies when:
    user needs many files
    user enters a private site area
    HLS/DASH video has many segments
    URL should stay clean without signature query string

common examples:
    /private/*
    /paid-course/*
    /video/hls/*

important:
    cookie Domain / Path must match requested CloudFront domain/path
    use Secure / HttpOnly / SameSite

CloudFront Function token check#

use CloudFront Function when:
    validation is simple
    check cookie/header/query string
    return 401/403 before cache/origin
    no network call is needed

good for:
    static shared token
    lightweight JWT shape check
    allowlist / denylist by header or path

not good for:
    calling auth server
    complex crypto / large dependency
    per-request database lookup
function handler(event) {
  var request = event.request;
  var headers = request.headers;

  if (!headers["x-download-token"] || headers["x-download-token"].value !== "expected-token") {
    return {
      statusCode: 403,
      statusDescription: "Forbidden"
    };
  }

  return request;
}

Lambda@Edge token check#

use Lambda@Edge when:
    logic is too complex for CloudFront Function
    request needs richer processing
    viewer request should be rejected before origin

注意:
    Lambda@Edge has runtime / region / deployment constraints
    viewer request function runs before CloudFront checks cache/origin
    do not put slow auth flow in front of every static object unless latency is acceptable
export const handler = async (event) => {
  const request = event.Records[0].cf.request;
  const headers = request.headers;
  const token = headers.authorization?.[0]?.value;

  if (token !== "Bearer expected-token") {
    return {
      status: "403",
      statusDescription: "Forbidden",
      body: "Forbidden"
    };
  }

  return request;
};

decision#

choose:
    one object:
        signed URL

    many objects / video segments:
        signed cookies

    simple custom token at edge:
        CloudFront Function

    complex custom auth at edge:
        Lambda@Edge

    private S3 origin:
        still use OAC/OAI separately

11. S3 Origin With OAC#

goal:
    users cannot access S3 object directly
    users can only access object through CloudFront
    CloudFront viewer access is controlled by signed URL/cookie

bucket policy#

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowCloudFrontServicePrincipalReadOnly",
      "Effect": "Allow",
      "Principal": {
        "Service": "cloudfront.amazonaws.com"
      },
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::my-private-bucket/*",
      "Condition": {
        "StringEquals": {
          "AWS:SourceArn": "arn:aws:cloudfront::123456789012:distribution/E1234567890ABC"
        }
      }
    }
  ]
}
OAC notes:
    use regular S3 bucket origin
    not S3 static website endpoint
    keep S3 Block Public Access enabled
    if using SSE-KMS, KMS key policy must allow CloudFront distribution

12. Key Rotation#

safe rotation:
    1. generate new private/public key pair
    2. create new CloudFront public key
    3. add new public key to existing key group
    4. deploy app with new private key and new PUBLIC_KEY_ID
    5. wait until old signed URLs/cookies expire
    6. remove old public key from key group
    7. delete old public key
    8. delete old private key from secret store
do not:
    replace key immediately while old URLs are still valid
    delete old public key before old signed URLs expire
    store private key in git

13. Security Best Practices#

CloudFront security layers:
    viewer security:
        HTTPS only
        signed URL / signed cookie / custom token auth when content is private
        WAF for public endpoints
        geo restriction when business requires it

    response security:
        response headers policy
        HSTS
        CSP
        X-Content-Type-Options
        X-Frame-Options
        Referrer-Policy

    origin security:
        OAC/OAI for S3 origin
        origin request policy minimized
        custom origin should verify CloudFront-only secret header when applicable

    operations:
        standard logs / real-time logs
        CloudTrail for distribution changes
        alarms on 4xx/5xx and WAF blocks

HTTPS only behavior sample#

CloudFront 与源站是两道访问边界:只保护 CDN 不会自动封闭源站直连;按 S3 或 custom origin 类型配置源站访问控制。

{
  "ViewerProtocolPolicy": "redirect-to-https",
  "AllowedMethods": {
    "Quantity": 2,
    "Items": ["GET", "HEAD"]
  },
  "CachedMethods": {
    "Quantity": 2,
    "Items": ["GET", "HEAD"]
  },
  "Compress": true
}
notes:
    redirect-to-https:
        HTTP viewer request is redirected to HTTPS

    GET/HEAD only:
        static private content should not allow write methods

    for API behavior:
        allowed methods may include POST/PUT/PATCH/DELETE
        attach WAF and auth separately

response headers policy sample#

{
  "Name": "prod-order-security-headers",
  "Comment": "security headers for CloudFront viewer responses",
  "SecurityHeadersConfig": {
    "StrictTransportSecurity": {
      "AccessControlMaxAgeSec": 31536000,
      "IncludeSubdomains": true,
      "Preload": true,
      "Override": true
    },
    "ContentTypeOptions": {
      "Override": true
    },
    "FrameOptions": {
      "FrameOption": "DENY",
      "Override": true
    },
    "ReferrerPolicy": {
      "ReferrerPolicy": "strict-origin-when-cross-origin",
      "Override": true
    },
    "ContentSecurityPolicy": {
      "ContentSecurityPolicy": "default-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'self'",
      "Override": true
    }
  }
}
aws cloudfront create-response-headers-policy --response-headers-policy-config file://security-headers-policy.json
attach:
    attach ResponseHeadersPolicyId to cache behavior

注意:
    CSP must match your real frontend assets
    start strict for private static download
    tune carefully for SPA / third-party scripts

WAF web ACL sample#

Resources:
  CloudFrontWebAcl:
    Type: AWS::WAFv2::WebACL
    Properties:
      Name: prod-order-cloudfront
      Scope: CLOUDFRONT
      DefaultAction:
        Allow: {}
      VisibilityConfig:
        CloudWatchMetricsEnabled: true
        MetricName: prod-order-cloudfront
        SampledRequestsEnabled: true
      Rules:
        - Name: AWSManagedRulesCommonRuleSet
          Priority: 10
          OverrideAction:
            None: {}
          Statement:
            ManagedRuleGroupStatement:
              VendorName: AWS
              Name: AWSManagedRulesCommonRuleSet
          VisibilityConfig:
            CloudWatchMetricsEnabled: true
            MetricName: common
            SampledRequestsEnabled: true
        - Name: AWSManagedRulesKnownBadInputsRuleSet
          Priority: 20
          OverrideAction:
            None: {}
          Statement:
            ManagedRuleGroupStatement:
              VendorName: AWS
              Name: AWSManagedRulesKnownBadInputsRuleSet
          VisibilityConfig:
            CloudWatchMetricsEnabled: true
            MetricName: known-bad-inputs
            SampledRequestsEnabled: true
notes:
    CloudFront WAF uses Scope=CLOUDFRONT
    create/manage it in us-east-1 for CloudFront
    start with AWS managed rule groups
    monitor sampled requests before adding aggressive custom blocks

custom origin secret header sample#

{
  "OriginCustomHeaders": {
    "Quantity": 1,
    "Items": [
      {
        "HeaderName": "x-origin-secret",
        "HeaderValue": "change-this-secret"
      }
    ]
  }
}
use case:
    custom origin is ALB / Nginx / application
    origin rejects requests without x-origin-secret
    users cannot call origin directly without the secret header

注意:
    this is not for S3 OAC
    rotate the secret
    do not log the header value
    restrict origin network path where possible

logging sample#

{
  "Enabled": true,
  "IncludeCookies": false,
  "Bucket": "cloudfront-logs-prod.s3.amazonaws.com",
  "Prefix": "order-private-content/"
}
security log checklist:
    standard logs enabled
    real-time logs enabled for high-risk private content when needed
    WAF logs enabled
    S3 log bucket encrypted
    log bucket write policy controlled
    retention defined

14. Monitoring#

Area Metric / Log What To Watch
Availability 5xxErrorRate, 4xxErrorRate origin errors / access denied spike
Traffic Requests, BytesDownloaded, BytesUploaded traffic trend
Latency OriginLatency, total download time from logs origin latency
Cache CacheHitRate cache efficiency
Signed URL standard logs sc-status=403, x-edge-detailed-result-type invalid signature / expired URL
Origin S3 access logs / CloudTrail / CloudWatch origin permission issue
dashboard:
    requests
    4xx / 5xx
    cache hit rate
    origin latency
    top paths
    top 403 detailed result type
    bytes downloaded

investigate TotalErrorRate alarm#

TotalErrorRate = 4xxErrorRate + 5xxErrorRate.

CloudWatch metric can tell whether errors are 4xx or 5xx.
CloudFront access logs are required to identify URL, status code, edge result type, and detailed error reason.

CloudFront metrics are in us-east-1 because CloudFront is a global service.

Check whether the spike is mostly 4xx or 5xx:

aws cloudwatch get-metric-statistics --region us-east-1 --namespace AWS/CloudFront --metric-name 4xxErrorRate --dimensions Name=DistributionId,Value=E123456EXAMPLE Name=Region,Value=Global --start-time "$(date -u -v-30M +%Y-%m-%dT%H:%M:%SZ)" --end-time "$(date -u +%Y-%m-%dT%H:%M:%SZ)" --period 300 --statistics Average

aws cloudwatch get-metric-statistics --region us-east-1 --namespace AWS/CloudFront --metric-name 5xxErrorRate --dimensions Name=DistributionId,Value=E123456EXAMPLE Name=Region,Value=Global --start-time "$(date -u -v-30M +%Y-%m-%dT%H:%M:%SZ)" --end-time "$(date -u +%Y-%m-%dT%H:%M:%SZ)" --period 300 --statistics Average

Key CloudFront log fields for error analysis:

Field Use
sc-status HTTP status returned to viewer, such as 403, 404, 502, 503, 504
x-edge-result-type High-level CloudFront result, such as Error, Miss, Hit, LimitExceeded
x-edge-detailed-result-type Detailed CloudFront error reason
cs-host Requested host
cs-uri-stem / cs-uri-query Requested path and query
cs-method Viewer method
c-ip Viewer IP
x-edge-request-id Request ID for correlation

Athena query for S3 standard logs:

SELECT
  date,
  sc_status,
  x_edge_result_type,
  x_edge_detailed_result_type,
  cs_method,
  cs_host,
  cs_uri_stem,
  COUNT(*) AS error_count
FROM cloudfront_logs
WHERE distribution_id = 'E123456EXAMPLE'
  AND date >= current_date - interval '1' day
  AND sc_status >= 400
GROUP BY
  date,
  sc_status,
  x_edge_result_type,
  x_edge_detailed_result_type,
  cs_method,
  cs_host,
  cs_uri_stem
ORDER BY error_count DESC
LIMIT 100;

CloudWatch Logs Insights query if standard logs v2 are delivered to CloudWatch Logs:

fields @timestamp, scStatus, xEdgeResultType, xEdgeDetailedResultType, csMethod, csHost, csUriStem, csUriQuery, cIp
| filter scStatus >= 400
| stats count(*) as errors by scStatus, xEdgeResultType, xEdgeDetailedResultType, csMethod, csHost, csUriStem
| sort errors desc
| limit 50
Interpretation:
    4xx spike:
        usually 403 / 404 / WAF block / signed URL or cookie issue / OAC or origin permission / wrong path.

    5xx spike:
        usually origin connection failure / origin timeout / ALB or ECS 5xx / TLS issue / Lambda@Edge or CloudFront Function issue.

    no logs enabled:
        historical per-URL error details are usually unavailable.
        enable standard logs v2 for investigation and real-time logs for urgent live debugging.

15. Troubleshooting#

403 InvalidKey:
    Key-Pair-Id is not CloudFront Public Key ID
    public key is not in key group
    key group is not attached to behavior
    request path matched another behavior

403 SignatureDoesNotMatch / malformed signature:
    private key does not match public key
    URL modified after signing
    query string added after signing
    wrong policy JSON / base64 encoding

403 AccessDenied:
    signed URL expired
    not yet valid
    IP condition mismatch
    S3 bucket policy / OAC wrong
    object does not exist but origin hides it as access denied

works with unsigned URL but not signed URL:
    behavior may not require trusted key group
    signed URL key ID mismatch

works with signed URL but direct S3 also works:
    S3 bucket is public
    OAC / bucket policy not locked down

direct S3 access denied but CloudFront URL works without signed URL:
    this is expected when only origin auth is enabled
    OAC protects S3 direct access
    CloudFront viewer access is still public
    attach trusted key group if viewer access must be private

CloudFront Function / Lambda@Edge returns 403:
    token missing
    token in wrong header/cookie/query parameter
    function associated with wrong cache behavior
    cached response behavior not understood

16. Production Checklist#

distribution:
    HTTPS only
    WAF enabled when public
    response headers policy attached
    cache policy reviewed
    origin request policy minimized
    logs enabled

private content:
    trusted key group used
    Key-Pair-Id documented as Public Key ID
    private key in Secrets Manager / secure secret store
    public key in CloudFront
    key group attached to correct behavior
    signed URL TTL short
    custom policy used when IP/start-time restriction needed

S3 origin:
    OAC enabled
    S3 Block Public Access enabled
    bucket policy restricts SourceArn to distribution
    SSE-KMS key policy reviewed if used

custom origin:
    direct origin access restricted
    secret origin header configured if appropriate
    origin does not trust viewer-controlled headers blindly

viewer response:
    HSTS enabled
    X-Content-Type-Options enabled
    X-Frame-Options or CSP frame-ancestors configured
    CSP reviewed for actual frontend assets

origin auth vs viewer auth:
    OAC/OAI only protects origin access
    signed URL/cookie protects viewer access
    CloudFront Function / Lambda@Edge token check protects viewer access when custom auth is required
    direct S3 access test returns 403
    unsigned CloudFront URL test returns 403 when private content is required

rotation:
    key rotation runbook exists
    old public key kept until old signed URLs expire
    app deploy and key group update order documented

搜索文档

输入关键词,搜索所有文档。

↑ ↓ 选择 · Enter 打开 · Esc 关闭