Public-facing load balancers are vulnerable to attacks, including DDoS, SQL injection, cross-site scripting (XSS), and bot attacks. These attacks can degrade the load balancer’s performance, rendering it unavailable to legitimate users and negatively impacting business operations.
AWS Web Application Firewall (WAF) is a service designed to protect resources like load balancers, Amazon CloudFront distributions, API Gateway, and other web-facing services from external threats, such as DDoS attacks, SQL injection, cross-site scripting (XSS), and bot traffic. For example, when AWS WAF integrates with an Application Load Balancer (ALB), all incoming HTTP/HTTPS traffic passes through AWS WAF before reaching the load balancer. This makes AWS WAF the first line of defense for the load balancer. It checks each HTTP/HTTPS request against a set of rules to decide whether to allow or block the traffic.
In this note, you will learn how to protect a public-facing load balancer by attaching an AWS WAF. This use case follows DevOps best practices by creating the required AWS cloud resources using Terraform (IAC) and automating the deployment using GitHub actions.
If you are interested and want to follow along, please refer to the GitHub repository: kunduso/add-aws-elb-ec2-private-subnet-terraform. Please note the branch name is add-waf
.
This note builds upon a previous use case where a load balancer was attached to three Amazon EC2 instances. That architecture adhered to secure practices by placing the EC2 instances in a private subnet and allowing access through a load balancer security group in the public subnet. A private subnet is defined as one with no direct routes to 0.0.0.0/0
via the Internet Gateway. In that use case, the EC2 instances utilized NAT gateways in their respective availability zones to access the Internet (e.g., for downloading packages). The load balancer, on the other hand, was in the public subnet, which was attached to an Internet Gateway.
However, the load balancer in that setup lacked adequate protection, which is addressed in this use case.
When AWS WAF is attached to a load balancer, it collects data related to incoming web requests directed at it, such as request metadata, IP addresses, HTTP headers, and any detected malicious activity. This data is logged and stored in Amazon CloudWatch for monitoring and analysis.
As per security best practices, Amazon CloudWatch Logs must be encrypted using a KMS (Key Management Service) key to maintain their confidentiality and integrity.
Additionally, security best practices dictate that the KMS key must be configured with a specific permission policy tailored to its intended usage. This practice ensures that only authorized users and services can access and use the key for encryption and decryption operations.
Tying all these security best practices together and building on the load balancer from the previous use case, this use case involves (1) creating a KMS key and attaching a KMS key policy, (2) creating a CloudWatch log group and encrypting it with the KMS key, (3) creating an AWS WAF with a set of custom rules, (4) attaching the AWS WAF to the load balancer, and (5) configuring the AWS WAF to send all logs to the CloudWatch log group for analysis.
Let us dive deeper into these steps.
1. Create a KMS key and a KMS key policy
This Terraform code creates an AWS KMS key and a key policy to encrypt WAF logs stored in CloudWatch Logs.
2. Create an Amazon CloudWatch log group
In this step, Terraform creates a CloudWatch Log Group to store the AWS WAF logs with a one-year retention period. The log group is configured with server-side encryption using the specified KMS key (created in the previous step), ensuring that all stored WAF logs are encrypted at rest for enhanced security.
3. Create an AWS WAF with a set of custom rules
The AWS WAF web ACL resource is configured with a
REGIONAL
scope for protecting Application Load Balancers, with a default_action
to allow traffic that doesn’t match any rules.
AWS WAF also implements multiple layers of protection through five prioritized rules: AWS-managed rules for known bad inputs (including Log4j protection), common threats, and SQL injection prevention, along with custom rules for rate limiting (2000 requests per IP) and geographic blocking.
The web ACL also enables visibility through CloudWatch metrics and request sampling, which allows for monitoring and analyzing the web traffic patterns.
4. Attach the AWS WAF to the load balancer
This Terraform code creates the association between the AWS WAF web ACL and the Application Load Balancer, essentially linking these resources so that all traffic to the ALB is first inspected by the WAF rules defined.
5. Configure AWS WAF to send all logs to the CloudWatch log group for analysis
Finally, in this step, the Terraform code configures logging for the AWS WAF web ACL, sending logs to a CloudWatch Log Group. The logging filter is set to keep all logs by default, with a specific filter to ensure that blocked requests are logged. This helps monitor and analyze security events where WAF has blocked potentially malicious traffic.
The Terraform code was deployed automatically with GitHub Actions to provision all the required AWS cloud resources. Once AWS WAF was successfully attached to the load balancer, the traffic overview was available on the AWS Console → AWS WAF → Web ACLs → Select ACL.
Several other dashboards were also available that analyzed the traffic based on whether a bot was detected, the client device type, attack types, the country of origin of the request, and more.
Each client request to the load balancer generates detailed logs that capture request metadata, threat detection, and the corresponding WAF actions. These logs are securely stored in Amazon CloudWatch for ongoing analysis. This step ensures that cloud engineering teams can create effective rules in AWS WAF over time after evaluating the data generated from client requests to ensure the underlying compute (load balancer) is protected and available to serve genuine requests. This process ensures that the load balancer remains resilient against threats, guaranteeing a seamless user experience and maintaining business continuity even during traffic spikes or attacks.
If you have any questions or feedback, please use the comments section below. This note is part of a series on elastic load balancing. To learn more, please click on the links below.
Add an application load balancer to Amazon EC2 using Terraform Add an application load balancer to Amazon EC2 instances in a private subnet Create an Amazon EC2 Auto Scaling group and load balancer using Terraform and GitHub Actions Automate Amazon Route 53 hosted zone, ACM, and Load Balancer provisioning with Terraform and GitHub Actions
If you are using an external load balancer, how was your experience after enabling AWS WAF?