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
CLOUDFRONTmust be created inus-east-1, regardless of where the Terraform provider's default region is configured. If you create the web ACL in another region, theaws_cloudfront_distributionresource will fail to associate it. Use a provider alias (provider = aws.us_east_1) for theaws_wafv2_web_aclresource.Empty web ACL passes the check but provides no protection
The control only verifies association, not rule content. An
aws_wafv2_web_aclwith noruleblocks and adefault_actionofallow {}satisfies the check while filtering nothing. Pair this control withwafv2_web_acl_logging_enabledand manual rule review.WAF Classic vs WAFv2 confusion
Both
aws_waf_web_acl(Classic) andaws_wafv2_web_acl(v2) can be attached viaweb_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_idon 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.
Related controls
Tool mappings
Use these identifiers to cross-reference this control across tools, reports, and evidence.
Compliance.tf Control:
cloudfront_distribution_waf_enabledAWS Config Managed Rule:
CLOUDFRONT_ASSOCIATED_WITH_WAFCheckov Check:
CKV_AWS_68Powerpipe Control:
aws_compliance.control.cloudfront_distribution_waf_enabledProwler Check:
cloudfront_distributions_using_wafAWS Security Hub Control:
CloudFront.6KICS Query:
1419b4c6-6d5c-4534-9cf6-6a5266085333Trivy Check:
AWS-0011
Last reviewed: 2026-03-09