Skip to main content

Overview

AWS WAF (Web Application Firewall) helps protect your web applications from common web exploits and bots that could affect availability, compromise security, or consume excessive resources.

Core Concepts

WAF Components

Rule Types

    Creating a Web ACL

    Basic Setup

    # Create IP set
    aws wafv2 create-ip-set \
      --name blocked-ips \
      --scope REGIONAL \
      --ip-address-version IPV4 \
      --addresses 192.0.2.0/24 203.0.113.0/24
    
    # Create Web ACL
    aws wafv2 create-web-acl \
      --name my-web-acl \
      --scope REGIONAL \
      --default-action Allow={} \
      --rules file://rules.json \
      --visibility-config SampledRequestsEnabled=true,CloudWatchMetricsEnabled=true,MetricName=MyWebACL
    
    # Associate with ALB
    aws wafv2 associate-web-acl \
      --web-acl-arn arn:aws:wafv2:us-east-1:123456789012:regional/webacl/my-web-acl/... \
      --resource-arn arn:aws:elasticloadbalancing:us-east-1:123456789012:loadbalancer/app/my-alb/...
    

    Terraform Configuration

    # IP Set for blocking
    resource "aws_wafv2_ip_set" "blocked_ips" {
      name               = "blocked-ips"
      description        = "IP addresses to block"
      scope              = "REGIONAL"
      ip_address_version = "IPV4"
      addresses          = [
        "192.0.2.0/24",
        "203.0.113.0/24"
      ]
    
      tags = {
        Environment = "production"
      }
    }
    
    # Regex pattern set for SQL injection
    resource "aws_wafv2_regex_pattern_set" "sql_patterns" {
      name  = "sql-injection-patterns"
      scope = "REGIONAL"
    
      regular_expression {
        regex_string = "(?i)(union.*select|insert.*into|delete.*from)"
      }
    
      regular_expression {
        regex_string = "(?i)(exec.*xp_|execute.*sp_)"
      }
    }
    
    # Web ACL
    resource "aws_wafv2_web_acl" "main" {
      name  = "production-web-acl"
      scope = "REGIONAL"
    
      default_action {
        allow {}
      }
    
      # Rule 1: Rate limiting
      rule {
        name     = "rate-limit-rule"
        priority = 0
    
        action {
          block {}
        }
    
        statement {
          rate_based_statement {
            limit              = 2000
            aggregate_key_type = "IP"
          }
        }
    
        visibility_config {
          cloudwatch_metrics_enabled = true
          metric_name                = "RateLimitRule"
          sampled_requests_enabled   = true
        }
      }
    
      # Rule 2: Block specific IPs
      rule {
        name     = "block-bad-ips"
        priority = 1
    
        action {
          block {
            custom_response {
              response_code = 403
            }
          }
        }
    
        statement {
          ip_set_reference_statement {
            arn = aws_wafv2_ip_set.blocked_ips.arn
          }
        }
    
        visibility_config {
          cloudwatch_metrics_enabled = true
          metric_name                = "BlockBadIPs"
          sampled_requests_enabled   = true
        }
      }
    
      # Rule 3: Geo blocking
      rule {
        name     = "geo-block"
        priority = 2
    
        action {
          block {}
        }
    
        statement {
          geo_match_statement {
            country_codes = ["KP", "IR"]  # North Korea, Iran
          }
        }
    
        visibility_config {
          cloudwatch_metrics_enabled = true
          metric_name                = "GeoBlock"
          sampled_requests_enabled   = true
        }
      }
    
      # Rule 4: AWS Managed Rules - Core Rule Set
      rule {
        name     = "aws-managed-core-rule-set"
        priority = 3
    
        override_action {
          none {}
        }
    
        statement {
          managed_rule_group_statement {
            vendor_name = "AWS"
            name        = "AWSManagedRulesCommonRuleSet"
    
            # Exclude specific rules if needed
            rule_action_override {
              action_to_use {
                count {}
              }
              name = "SizeRestrictions_BODY"
            }
          }
        }
    
        visibility_config {
          cloudwatch_metrics_enabled = true
          metric_name                = "AWSManagedRulesCommonRuleSet"
          sampled_requests_enabled   = true
        }
      }
    
      # Rule 5: SQL Injection protection
      rule {
        name     = "sql-injection-protection"
        priority = 4
    
        action {
          block {}
        }
    
        statement {
          or_statement {
            statement {
              sqli_match_statement {
                field_to_match {
                  query_string {}
                }
                text_transformation {
                  priority = 0
                  type     = "URL_DECODE"
                }
                text_transformation {
                  priority = 1
                  type     = "HTML_ENTITY_DECODE"
                }
              }
            }
    
            statement {
              sqli_match_statement {
                field_to_match {
                  body {
                    oversize_handling = "CONTINUE"
                  }
                }
                text_transformation {
                  priority = 0
                  type     = "URL_DECODE"
                }
              }
            }
          }
        }
    
        visibility_config {
          cloudwatch_metrics_enabled = true
          metric_name                = "SQLInjectionProtection"
          sampled_requests_enabled   = true
        }
      }
    
      # Rule 6: XSS protection
      rule {
        name     = "xss-protection"
        priority = 5
    
        action {
          block {}
        }
    
        statement {
          xss_match_statement {
            field_to_match {
              body {
                oversize_handling = "CONTINUE"
              }
            }
            text_transformation {
              priority = 0
              type     = "URL_DECODE"
            }
            text_transformation {
              priority = 1
              type     = "HTML_ENTITY_DECODE"
            }
          }
        }
    
        visibility_config {
          cloudwatch_metrics_enabled = true
          metric_name                = "XSSProtection"
          sampled_requests_enabled   = true
        }
      }
    
      # Rule 7: Bot Control
      rule {
        name     = "bot-control"
        priority = 6
    
        override_action {
          none {}
        }
    
        statement {
          managed_rule_group_statement {
            vendor_name = "AWS"
            name        = "AWSManagedRulesBotControlRuleSet"
            
            managed_rule_group_configs {
              aws_managed_rules_bot_control_rule_set {
                inspection_level = "COMMON"  # or "TARGETED"
              }
            }
          }
        }
    
        visibility_config {
          cloudwatch_metrics_enabled = true
          metric_name                = "BotControl"
          sampled_requests_enabled   = true
        }
      }
    
      visibility_config {
        cloudwatch_metrics_enabled = true
        metric_name                = "ProductionWebACL"
        sampled_requests_enabled   = true
      }
    
      tags = {
        Environment = "production"
        ManagedBy   = "terraform"
      }
    }
    
    # Associate with ALB
    resource "aws_wafv2_web_acl_association" "alb" {
      resource_arn = aws_lb.main.arn
      web_acl_arn  = aws_wafv2_web_acl.main.arn
    }
    
    # Associate with CloudFront (requires global scope)
    resource "aws_wafv2_web_acl" "cloudfront" {
      provider = aws.us-east-1  # CloudFront requires us-east-1
      name     = "cloudfront-web-acl"
      scope    = "CLOUDFRONT"
    
      default_action {
        allow {}
      }
    
      # Similar rules as above...
    
      visibility_config {
        cloudwatch_metrics_enabled = true
        metric_name                = "CloudFrontWebACL"
        sampled_requests_enabled   = true
      }
    }
    

    Advanced Features

    Bot Control

    Bot Control Use Cases:
      E-commerce:
        - Prevent inventory hoarding
        - Stop price scraping
        - Block scalper bots
        
      APIs:
        - Rate limit automated requests
        - Verify API consumers
        - Prevent abuse
        
      Content Sites:
        - Allow search engine crawlers
        - Block content scrapers
        - Protect copyrighted material
        
      Authentication:
        - Prevent credential stuffing
        - Block brute force attempts
        - Protect login endpoints
    

    CAPTCHA Challenge

    # CAPTCHA configuration
    resource "aws_wafv2_web_acl" "with_captcha" {
      name  = "captcha-enabled-acl"
      scope = "REGIONAL"
    
      default_action {
        allow {}
      }
    
      rule {
        name     = "captcha-for-suspicious-requests"
        priority = 0
    
        action {
          captcha {
            custom_request_handling {
              insert_header {
                name  = "x-waf-captcha"
                value = "challenged"
              }
            }
          }
        }
    
        statement {
          rate_based_statement {
            limit              = 100
            aggregate_key_type = "IP"
          }
        }
    
        captcha_config {
          immunity_time_property {
            immunity_time = 300  # 5 minutes immunity after solving
          }
        }
    
        visibility_config {
          cloudwatch_metrics_enabled = true
          metric_name                = "CAPTCHARule"
          sampled_requests_enabled   = true
        }
      }
    
      visibility_config {
        cloudwatch_metrics_enabled = true
        metric_name                = "CAPTCHAWebACL"
        sampled_requests_enabled   = true
      }
    }
    

    Custom Response

    resource "aws_wafv2_web_acl" "custom_response" {
      name  = "custom-response-acl"
      scope = "REGIONAL"
    
      default_action {
        allow {}
      }
    
      rule {
        name     = "block-with-custom-response"
        priority = 0
    
        action {
          block {
            custom_response {
              response_code = 403
              
              custom_response_body_key = "blocked_message"
              
              response_header {
                name  = "x-block-reason"
                value = "rate-limit-exceeded"
              }
            }
          }
        }
    
        statement {
          rate_based_statement {
            limit              = 2000
            aggregate_key_type = "IP"
          }
        }
    
        visibility_config {
          cloudwatch_metrics_enabled = true
          metric_name                = "CustomResponseRule"
          sampled_requests_enabled   = true
        }
      }
    
      custom_response_body {
        key          = "blocked_message"
        content      = jsonencode({
          error   = "Rate limit exceeded"
          message = "Too many requests. Please try again later."
          code    = 403
        })
        content_type = "APPLICATION_JSON"
      }
    
      visibility_config {
        cloudwatch_metrics_enabled = true
        metric_name                = "CustomResponseWebACL"
        sampled_requests_enabled   = true
      }
    }
    

    Logging and Monitoring

    Enable Logging

    # S3 bucket for WAF logs
    resource "aws_s3_bucket" "waf_logs" {
      bucket = "my-waf-logs-${data.aws_caller_identity.current.account_id}"
    }
    
    resource "aws_s3_bucket_public_access_block" "waf_logs" {
      bucket = aws_s3_bucket.waf_logs.id
    
      block_public_acls       = true
      block_public_policy     = true
      ignore_public_acls      = true
      restrict_public_buckets = true
    }
    
    # Kinesis Data Firehose for WAF logs
    resource "aws_kinesis_firehose_delivery_stream" "waf_logs" {
      name        = "aws-waf-logs-delivery-stream"
      destination = "extended_s3"
    
      extended_s3_configuration {
        role_arn   = aws_iam_role.firehose.arn
        bucket_arn = aws_s3_bucket.waf_logs.arn
        prefix     = "waf-logs/"
    
        compression_format = "GZIP"
    
        cloudwatch_logging_options {
          enabled         = true
          log_group_name  = aws_cloudwatch_log_group.waf_logs.name
          log_stream_name = "S3Delivery"
        }
      }
    }
    
    # Enable WAF logging
    resource "aws_wafv2_web_acl_logging_configuration" "main" {
      resource_arn            = aws_wafv2_web_acl.main.arn
      log_destination_configs = [aws_kinesis_firehose_delivery_stream.waf_logs.arn]
    
      redacted_fields {
        single_header {
          name = "authorization"
        }
      }
    
      redacted_fields {
        single_header {
          name = "cookie"
        }
      }
    
      logging_filter {
        default_behavior = "KEEP"
    
        filter {
          behavior = "KEEP"
    
          condition {
            action_condition {
              action = "BLOCK"
            }
          }
    
          requirement = "MEETS_ANY"
        }
      }
    }
    
    # CloudWatch Log Group
    resource "aws_cloudwatch_log_group" "waf_logs" {
      name              = "/aws/waf/logs"
      retention_in_days = 30
    }
    

    CloudWatch Metrics and Alarms

    # Alarm for high block rate
    resource "aws_cloudwatch_metric_alarm" "waf_high_block_rate" {
      alarm_name          = "waf-high-block-rate"
      comparison_operator = "GreaterThanThreshold"
      evaluation_periods  = "2"
      metric_name         = "BlockedRequests"
      namespace           = "AWS/WAFV2"
      period              = "300"
      statistic           = "Sum"
      threshold           = "1000"
      alarm_description   = "This metric monitors WAF blocked requests"
      alarm_actions       = [aws_sns_topic.security_alerts.arn]
    
      dimensions = {
        WebACL = aws_wafv2_web_acl.main.name
        Region = data.aws_region.current.name
        Rule   = "ALL"
      }
    }
    
    # Alarm for rate limit rule triggers
    resource "aws_cloudwatch_metric_alarm" "rate_limit_triggers" {
      alarm_name          = "waf-rate-limit-triggers"
      comparison_operator = "GreaterThanThreshold"
      evaluation_periods  = "1"
      metric_name         = "BlockedRequests"
      namespace           = "AWS/WAFV2"
      period              = "60"
      statistic           = "Sum"
      threshold           = "100"
      alarm_description   = "Rate limiting is being triggered frequently"
      alarm_actions       = [aws_sns_topic.security_alerts.arn]
    
      dimensions = {
        WebACL = aws_wafv2_web_acl.main.name
        Region = data.aws_region.current.name
        Rule   = "rate-limit-rule"
      }
    }
    

    Best Practices

    Security Checklist

    WAF Implementation Checklist:
      Initial Setup:
        ☐ Enable AWS Managed Rules Core Rule Set
        ☐ Configure rate limiting
        ☐ Set up geo-blocking if needed
        ☐ Enable logging to S3/CloudWatch
        ☐ Create CloudWatch alarms
        
      Testing Phase:
        ☐ Test all rules in Count mode first
        ☐ Review sampled requests
        ☐ Analyze false positives
        ☐ Tune rule sensitivity
        ☐ Document exceptions
        
      Production:
        ☐ Switch rules to Block mode
        ☐ Enable bot control
        ☐ Configure custom responses
        ☐ Set up CAPTCHA for suspicious traffic
        ☐ Integrate with Security Hub
        
      Ongoing:
        ☐ Weekly review of blocked requests
        ☐ Monthly rule effectiveness review
        ☐ Update IP sets as needed
        ☐ Monitor for new threat patterns
    

    Cost Optimization

    Pricing Structure:
      Web ACL:
        - $5.00 per month per Web ACL
        
      Rules:
        - $1.00 per month per rule
        
      Requests:
        - $0.60 per million requests
        
      Bot Control:
        - COMMON: $10.00 per million requests
        - TARGETED: $16.00 per million requests
        
      CAPTCHA:
        - $0.40 per 1,000 challenge attempts
        
    Optimization Tips:
      - Consolidate rules where possible
      - Use rule groups efficiently
      - Start with Common bot control level
      - Monitor request patterns
      - Use count mode for testing
      - Review unused rules monthly
    

    Exam Tips