Back to blog

Security

Protecting your ALB with WAF & Cloudfront

Learn how to protect your ALB with AWS WAF and CloudFront.

January 1, 2020 Platform Engineering 4 min read

Protecting your Application Load Balancer (ALB) from unwanted traffic is an important part of securing internet-facing applications. While products such as Cloudflare offer feature-rich edge protection, you can also build a strong native AWS solution with CloudFront and AWS WAF.

This post focuses on locking an ALB down so it only accepts the traffic that should be reaching it, while still allowing CloudFront to sit in front as the public edge.

Solution Overview

The solution has three main pieces:

  1. A CloudFront distribution in front of the ALB
  2. A CloudFront Web ACL for edge filtering and rate limiting
  3. An ALB Web ACL that only allows requests with the expected origin token header

Together they create a layered protection model:

  • CloudFront handles public edge traffic
  • WAF filters traffic both at the edge and at the origin
  • the ALB only accepts traffic that matches the expected path into the application

CloudFront Configuration

The first step is to generate an origin token:

resource "random_string" "origin_token" {
  length  = 30
  special = false
}

Then configure CloudFront to send that value to the ALB as a custom header:

resource "aws_cloudfront_distribution" "distribution" {
  origin {
    domain_name = aws_lb.example.dns_name
    origin_id   = "alb"

    custom_header {
      name  = "X-Origin-Token"
      value = random_string.origin_token.result
    }
  }

  enabled = true
  aliases = ["yourdomain.example.com"]

  default_cache_behavior {
    allowed_methods  = ["GET", "HEAD", "OPTIONS", "PUT", "POST", "PATCH", "DELETE"]
    cached_methods   = ["GET", "HEAD"]
    target_origin_id = "alb"

    forwarded_values {
      query_string = true
      headers      = ["X-Origin-Token"]

      cookies {
        forward = "all"
      }
    }

    viewer_protocol_policy = "redirect-to-https"
  }
}

Configuring AWS WAF

CloudFront Web ACL

The CloudFront Web ACL is where you can apply managed rule sets, reputation filtering, and rate limiting:

module "cloudfront_waf" {
  source = "coresolutions-ltd/wafv2/aws"

  name_prefix    = "CloudFront"
  default_action = "allow"
  scope          = "CLOUDFRONT"
  rate_limit     = 1000
  managed_rules  = [
    "AWSManagedRulesCommonRuleSet",
    "AWSManagedRulesAmazonIpReputationList",
    "AWSManagedRulesAdminProtectionRuleSet",
    "AWSManagedRulesKnownBadInputsRuleSet"
  ]
}

ALB Web ACL

The ALB Web ACL should be stricter. A useful pattern is to block by default and only allow traffic that includes the expected origin token:

module "alb_waf" {
  source = "coresolutions-ltd/wafv2/aws"

  name_prefix    = "ALB"
  default_action = "block"
  scope          = "REGIONAL"
  origin_token   = random_string.origin_token.result
}

resource "aws_wafv2_web_acl_association" "waf_association" {
  resource_arn = aws_lb.example.arn
  web_acl_arn  = module.alb_waf.waf_arn
}

Configuring the ALB Security Group

As another layer of defence, restrict the ALB security group to CloudFront IP ranges:

data "aws_ip_ranges" "cloudfront" {
  services = ["CLOUDFRONT"]
}

resource "aws_security_group" "alb_sg" {
  name        = "alb_sg"
  description = "Allow traffic from CloudFront"
  vpc_id      = aws_vpc.main.id

  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = data.aws_ip_ranges.cloudfront.cidr_blocks
  }
}

Because CloudFront IP ranges change over time, you may also want automation to keep those rules up to date.

Testing the Configuration

To verify the setup:

  1. Access the application through CloudFront and confirm it works.
  2. Attempt direct access to the ALB and confirm it is blocked.
  3. Review WAF logs and metrics to confirm non-matching requests are being denied.

Conclusion

CloudFront and AWS WAF provide a strong AWS-native way to protect an ALB. By combining edge filtering, origin token validation, and security group restrictions, you can make it much harder for unwanted traffic to reach the application directly while keeping the architecture relatively clean.