Configuring AWS WAF with Terraform
A comprehensive guide to setting up AWS Web Application Firewall (WAF) using Terraform Infrastructure as Code
Configuring AWS WAF with Terraform
AWS WAF is a web application firewall that helps protect your web applications from common web exploits. This guide shows how to set up WAF using Terraform.
Prerequisites
- AWS CLI configured
- Terraform installed
- Web application or API to protect
- Basic understanding of web security
Project Structure
aws-waf-terraform/
├── main.tf
├── variables.tf
├── outputs.tf
└── terraform.tfvars
Basic WAF Configuration
# main.tf
provider "aws" {
region = var.aws_region
}
# WAF Web ACL
resource "aws_wafv2_web_acl" "main" {
name = "${var.project_name}-web-acl"
description = "WAF Web ACL for ${var.project_name}"
scope = "REGIONAL" # or CLOUDFRONT
default_action {
allow {}
}
# IP Rate Limiting Rule
rule {
name = "IPRateLimit"
priority = 1
override_action {
none {}
}
statement {
rate_based_statement {
limit = 2000
aggregate_key_type = "IP"
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "IPRateLimit"
sampled_requests_enabled = true
}
}
# AWS Managed Rules
rule {
name = "AWSManagedRulesCommonRuleSet"
priority = 2
override_action {
none {}
}
statement {
managed_rule_group_statement {
name = "AWSManagedRulesCommonRuleSet"
vendor_name = "AWS"
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "AWSManagedRulesCommonRuleSetMetric"
sampled_requests_enabled = true
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "${var.project_name}WAFWebACL"
sampled_requests_enabled = true
}
tags = {
Environment = var.environment
}
}
# WAF IP Set
resource "aws_wafv2_ip_set" "blocked_ips" {
name = "${var.project_name}-blocked-ips"
description = "Blocked IP addresses"
scope = "REGIONAL"
ip_address_version = "IPV4"
addresses = var.blocked_ip_ranges
tags = {
Environment = var.environment
}
}
Custom Rule Configuration
# WAF Web ACL with Custom Rules
resource "aws_wafv2_web_acl" "custom" {
name = "${var.project_name}-custom-web-acl"
description = "WAF Web ACL with custom rules"
scope = "REGIONAL"
default_action {
allow {}
}
# Block Specific IP Addresses
rule {
name = "BlockIPSet"
priority = 1
override_action {
none {}
}
statement {
ip_set_reference_statement {
arn = aws_wafv2_ip_set.blocked_ips.arn
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "BlockIPSet"
sampled_requests_enabled = true
}
}
# Block SQL Injection
rule {
name = "BlockSQLInjection"
priority = 2
override_action {
none {}
}
statement {
sql_injection_match_statement {
field_to_match {
body {}
}
text_transformation {
priority = 1
type = "URL_DECODE"
}
text_transformation {
priority = 2
type = "HTML_ENTITY_DECODE"
}
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "BlockSQLInjection"
sampled_requests_enabled = true
}
}
# Block Cross-Site Scripting
rule {
name = "BlockXSS"
priority = 3
override_action {
none {}
}
statement {
xss_match_statement {
field_to_match {
body {}
}
text_transformation {
priority = 1
type = "URL_DECODE"
}
text_transformation {
priority = 2
type = "HTML_ENTITY_DECODE"
}
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "BlockXSS"
sampled_requests_enabled = true
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "${var.project_name}CustomWAFWebACL"
sampled_requests_enabled = true
}
}
Variables Configuration
# variables.tf
variable "aws_region" {
description = "AWS region"
type = string
default = "us-west-2"
}
variable "project_name" {
description = "Project name"
type = string
}
variable "environment" {
description = "Environment name"
type = string
default = "dev"
}
variable "blocked_ip_ranges" {
description = "List of IP ranges to block"
type = list(string)
default = []
}
Best Practices
-
Rule Management
- Start with AWS managed rules
- Use rate-based rules for DDoS protection
- Implement custom rules based on needs
- Regular rule updates
-
Security
- Monitor WAF logs
- Use proper rule priorities
- Implement proper blocking actions
- Regular security reviews
-
Performance
- Monitor rule performance
- Use appropriate rate limits
- Optimize rule ordering
- Regular performance reviews
-
Cost Optimization
- Monitor WAF usage
- Use appropriate rule sets
- Clean up unused rules
- Consider pricing tiers
Logging Configuration
# S3 Bucket for WAF Logs
resource "aws_s3_bucket" "waf_logs" {
bucket = "${var.project_name}-waf-logs"
tags = {
Environment = var.environment
}
}
# WAF Logging Configuration
resource "aws_wafv2_web_acl_logging_configuration" "main" {
log_destination_configs = [aws_s3_bucket.waf_logs.arn]
resource_arn = aws_wafv2_web_acl.main.arn
logging_filter {
default_behavior = "DROP"
filter {
behavior = "KEEP"
condition {
action_condition {
action = "BLOCK"
}
}
requirement = "MEETS_ANY"
}
}
}
Association with Resources
# Associate with API Gateway
resource "aws_wafv2_web_acl_association" "api_gateway" {
resource_arn = var.api_gateway_arn
web_acl_arn = aws_wafv2_web_acl.main.arn
}
# Associate with Application Load Balancer
resource "aws_wafv2_web_acl_association" "alb" {
resource_arn = var.alb_arn
web_acl_arn = aws_wafv2_web_acl.main.arn
}
Deployment Steps
- Initialize Terraform:
terraform init
- Plan the deployment:
terraform plan
- Apply the configuration:
terraform apply
Clean Up
Remove all resources when done:
terraform destroy
Common Use Cases
- Application Protection
resource "aws_wafv2_web_acl" "app_protection" {
name = "${var.project_name}-app-protection"
description = "Protection for web application"
scope = "REGIONAL"
default_action {
allow {}
}
# Rate Limiting
rule {
name = "RateLimit"
priority = 1
action {
block {}
}
statement {
rate_based_statement {
limit = 2000
aggregate_key_type = "IP"
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "RateLimit"
sampled_requests_enabled = true
}
}
# Geo Blocking
rule {
name = "GeoBlock"
priority = 2
action {
block {}
}
statement {
geo_match_statement {
country_codes = ["CN", "RU"]
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "GeoBlock"
sampled_requests_enabled = true
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "AppProtection"
sampled_requests_enabled = true
}
}
- API Protection
resource "aws_wafv2_web_acl" "api_protection" {
name = "${var.project_name}-api-protection"
description = "Protection for API endpoints"
scope = "REGIONAL"
default_action {
allow {}
}
# API Key Validation
rule {
name = "ValidateAPIKey"
priority = 1
action {
block {}
}
statement {
byte_match_statement {
positional_constraint = "EXACTLY"
search_string = "invalid-api-key"
field_to_match {
single_header {
name = "x-api-key"
}
}
text_transformation {
priority = 1
type = "NONE"
}
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "ValidateAPIKey"
sampled_requests_enabled = true
}
}
}
Monitoring and Alerts
# CloudWatch Alarm for Blocked Requests
resource "aws_cloudwatch_metric_alarm" "blocked_requests" {
alarm_name = "${var.project_name}-blocked-requests"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = "1"
metric_name = "BlockedRequests"
namespace = "AWS/WAFV2"
period = "300"
statistic = "Sum"
threshold = "100"
alarm_description = "This metric monitors blocked WAF requests"
alarm_actions = [var.sns_topic_arn]
dimensions = {
WebACL = aws_wafv2_web_acl.main.name
Region = var.aws_region
Rule = "ALL"
}
}
Conclusion
This setup provides a comprehensive foundation for deploying WAF using Terraform. Remember to:
- Plan your security strategy carefully
- Implement proper rule sets
- Monitor WAF metrics and logs
- Keep your configurations versioned
- Test thoroughly before production deployment
The complete code can be customized based on your specific requirements and use cases.