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

  1. Rule Management

    • Start with AWS managed rules
    • Use rate-based rules for DDoS protection
    • Implement custom rules based on needs
    • Regular rule updates
  2. Security

    • Monitor WAF logs
    • Use proper rule priorities
    • Implement proper blocking actions
    • Regular security reviews
  3. Performance

    • Monitor rule performance
    • Use appropriate rate limits
    • Optimize rule ordering
    • Regular performance reviews
  4. 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

  1. Initialize Terraform:
terraform init
  1. Plan the deployment:
terraform plan
  1. Apply the configuration:
terraform apply

Clean Up

Remove all resources when done:

terraform destroy

Common Use Cases

  1. 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
  }
}
  1. 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.