For AI agents: A markdown version of this page is available at https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/terraform-aws-alb-is-not-integrated-with-waf.md. A documentation index is available at /llms.txt.
This product is not supported for your selected Datadog site. ().

Metadata

Id: terraform-aws-alb-is-not-integrated-with-waf

Provider: AWS

Platform: Terraform

Severity: Medium

Category: Networking and Firewall

Learn More

Description

Application Load Balancers (ALBs) should always be protected by a Web Application Firewall (WAF) to defend against common web exploits and vulnerabilities, such as SQL injection and cross-site scripting (XSS) attacks. Without an associated WAF, ALBs are left exposed to the internet with no filter or mitigation in place for potentially malicious traffic targeting applications. This misconfiguration can lead to unauthorized access, data breaches, and disruptions to application availability, compromising the security and integrity of hosted applications. Ensuring that ALBs have an active WAF association is a critical best practice for reducing the risk of successful web-based attacks.

Compliant Code Examples

resource "aws_alb" "foo33" {
  internal = false
  subnets  = [aws_subnet.foo.id, aws_subnet.bar.id]
}

resource "aws_wafregional_web_acl_association" "foo_waf33" {
  resource_arn = aws_alb.foo33.arn
  web_acl_id   = aws_wafregional_web_acl.foo.id
}
# trigger validation
resource "aws_lb" "alb" {
  name               = "test-lb-tf"
  internal           = false
  load_balancer_type = "application"
  security_groups    = [aws_security_group.lb_sg.id]
  subnets            = [for subnet in aws_subnet.public : subnet.id]
}

resource "aws_wafv2_web_acl_association" "alb_waf_association" {
  resource_arn = aws_lb.alb.arn
  web_acl_arn  = aws_wafv2_web_acl.example.arn
}
# Documented false positive: this ALB IS associated with a WAF via
# aws_wafv2_web_acl_association, but the association still gets flagged when
# evaluated purely from plan-JSON.
#
# query.rego's waf_associated_lb_names matches the raw HCL interpolation
# string ("${aws_alb.<name>...}"/"${aws_lb.<name>...}") that only exists in
# HCL-parsed .tf documents. In a real "terraform plan -out=... && terraform
# show -json" run, resource_arn on the WAF association is a value computed
# from the not-yet-created load balancer, so it is unresolved
# (resource_changes[].change.after_unknown.resource_arn == true) and is
# entirely absent from planned_values -- there is nothing in plan-JSON for
# the rule to match against pre-apply, so waf_associated_lb_names can never
# be populated from an unresolved reference. Fixing this for real requires
# the plan-JSON parser (pkg/parser/json/tfplan.go) to also read
# resource_changes[].change.after_unknown / configuration.expressions[].references,
# which it currently does not.
resource "aws_lb" "fp_alb" {
  name               = "test-lb-tf"
  internal           = false
  load_balancer_type = "application"
  security_groups    = [aws_security_group.fp_lb_sg.id]
  subnets            = [for subnet in aws_subnet.fp_public : subnet.id]
}

resource "aws_wafv2_web_acl_association" "fp_alb_waf_association" {
  resource_arn = aws_lb.fp_alb.arn
  web_acl_arn  = aws_wafv2_web_acl.fp_example.arn
}

Non-Compliant Code Examples

resource "aws_alb" "foo" {
  internal = false
  subnets  = [aws_subnet.foo.id, aws_subnet.bar.id]
}

resource "aws_wafregional_web_acl_association" "foo_waf" {
  resource_arn = aws_alb.fooooo.arn
  web_acl_id   = aws_wafregional_web_acl.foo.id
}
resource "aws_lb" "alb" {
  name               = "test-lb-tf"
  internal           = false
  load_balancer_type = "application"
  security_groups    = [aws_security_group.lb_sg.id]
  subnets            = [for subnet in aws_subnet.public : subnet.id]
}

resource "aws_wafv2_web_acl_association" "alb_waf_association" {
  resource_arn = aws_lb.alba.arn
  web_acl_arn  = aws_wafv2_web_acl.example.arn
}
resource "aws_lb" "expression_type" {
  internal           = false
  load_balancer_type = var.lb_type
}

variable "lb_type" {
  type = string
}