Skip to content

CloudFront distributions should have AWS WAF enabled

CloudFront distributions sit at the outermost edge of your infrastructure, handling every HTTP request before it reaches origins. Without a WAF web ACL attached, the distribution forwards all traffic unfiltered, including SQL injection attempts, cross-site scripting payloads, and volumetric abuse. A single unprotected distribution can expose backend APIs and S3 origins to attacks that WAF rules would otherwise block at the edge with minimal latency cost.

Attaching a WAF web ACL also gives you request-level visibility through WAF logging, which is difficult to reconstruct from CloudFront access logs alone.

Retrofit consideration

Attaching a new WAF web ACL to a production distribution can block legitimate traffic if rules are too aggressive. Deploy in COUNT mode first, analyze matched requests via WAF logging, then switch to BLOCK. Expect a one-to-two-week tuning window per distribution.

Implementation

Choose the approach that matches how you manage Terraform.

Use the compliance.tf module to enforce this control by default. See get started with compliance.tf.

module "cloudfront" {
  source  = "pcidss.compliance.tf/terraform-aws-modules/cloudfront/aws"
  version = ">=6.0.0,<7.0.0"
}

module "cloudfront" {
  source  = "nistcsfv11.compliance.tf/terraform-aws-modules/cloudfront/aws"
  version = ">=6.0.0,<7.0.0"
}

If you use terraform-aws-modules/cloudfront/aws, set the right module inputs for this control. You can later migrate to the compliance.tf module with minimal changes because it is compatible by design.

module "cloudfront" {
  source  = "terraform-aws-modules/cloudfront/aws"
  version = ">=6.0.0,<7.0.0"
}

Use AWS provider resources directly. See docs for the resources involved: aws_cloudfront_distribution.

resource "aws_cloudfront_distribution" "this" {
  default_cache_behavior {
    allowed_methods           = ["GET", "HEAD"]
    cached_methods            = ["GET", "HEAD"]
    field_level_encryption_id = "abc123"

    forwarded_values {
      cookies {
        forward = "none"
      }
      query_string = false
    }

    target_origin_id       = "S3Origin"
    viewer_protocol_policy = "redirect-to-https"
  }
  default_root_object = "index.html"
  enabled             = true

  logging_config {
    bucket = "example-bucket-abc123_domain_name"
  }

  origin {
    domain_name = "example.s3.amazonaws.com"
    origin_id   = "S3Origin"
  }

  restrictions {
    geo_restriction {
      locations        = ["US", "CA", "GB"]
      restriction_type = "whitelist"
    }
  }

  viewer_certificate {
    acm_certificate_arn      = "arn:aws:acm:us-east-1:123456789012:certificate/12345678-1234-1234-1234-123456789012"
    minimum_protocol_version = "TLSv1.2_2021"
    ssl_support_method       = "sni-only"
  }

  wait_for_deployment = false
  web_acl_id          = "arn:aws:wafv2:us-east-1:123456789012:regional/webacl/example/12345678-1234-1234-1234-123456789012"
}

What this control checks

This control checks that web_acl_id on aws_cloudfront_distribution has a non-empty value. For WAFv2 (the current standard), create an aws_wafv2_web_acl resource with scope set to "CLOUDFRONT" in us-east-1, then pass its arn to web_acl_id on the distribution. For WAF Classic, pass the id of aws_waf_web_acl instead. It fails when web_acl_id is omitted or empty. Any valid web ACL reference, regardless of the rules it contains, satisfies the association check.

Common pitfalls

  • WAFv2 web ACL must be in us-east-1

    WAFv2 web ACLs scoped to CLOUDFRONT must be created in us-east-1, regardless of where the Terraform provider's default region is configured. If you create the web ACL in another region, the aws_cloudfront_distribution resource will fail to associate it. Use a provider alias (provider = aws.us_east_1) for the aws_wafv2_web_acl resource.

  • Empty web ACL passes the check but provides no protection

    The control only verifies association, not rule content. An aws_wafv2_web_acl with no rule blocks and a default_action of allow {} satisfies the check while filtering nothing. Pair this control with wafv2_web_acl_logging_enabled and manual rule review.

  • WAF Classic vs WAFv2 confusion

    Both aws_waf_web_acl (Classic) and aws_wafv2_web_acl (v2) can be attached via web_acl_id. If you are migrating from Classic to v2, removing the old Classic ACL before attaching the new v2 ACL creates a window where the distribution has no WAF. Use a two-step apply: attach the new ACL first, then remove the old resource.

  • Terraform plan shows in-place update but propagation takes minutes

    Changing web_acl_id on a live distribution triggers a CloudFront deployment that can take 5 to 15 minutes. During propagation, some edge locations may still serve traffic without the new WAF association. This is not a Terraform issue but an operational reality to account for during incident response.

Audit evidence

AWS Config evaluation results for the managed rule cloudfront-associated-with-waf showing all distributions COMPLIANT is the primary evidence. Supplement with aws cloudfront get-distribution-config --id <ID> output showing a populated WebACLId field, and aws wafv2 get-web-acl output confirming the referenced ACL exists with meaningful rules. WAF logging configuration from aws wafv2 get-logging-configuration demonstrates active inspection. Security Hub findings filtered to this control ID provide a timestamped compliance status across accounts and regions.

Framework-specific interpretation

PCI DSS v4.0: Requirement 6.4.1 calls for public-facing web applications to be continuously protected against web-based attacks, through either periodic vulnerability assessments or an automated technical solution that detects and prevents them. A WAF web ACL on every CloudFront distribution covers the automated-solution option, provided the ACL contains rules targeting SQL injection, XSS, and similar patterns. An empty ACL with a default allow action does not get you there.

Tool mappings

Use these identifiers to cross-reference this control across tools, reports, and evidence.

  • Compliance.tf Control: cloudfront_distribution_waf_enabled

  • AWS Config Managed Rule: CLOUDFRONT_ASSOCIATED_WITH_WAF

  • Checkov Check: CKV_AWS_68

  • Powerpipe Control: aws_compliance.control.cloudfront_distribution_waf_enabled

  • Prowler Check: cloudfront_distributions_using_waf

  • AWS Security Hub Control: CloudFront.6

  • KICS Query: 1419b4c6-6d5c-4534-9cf6-6a5266085333

  • Trivy Check: AWS-0011

Last reviewed: 2026-03-09