# ── WAF for CloudFront ──────────────────────────────────────
# Provides XSS/SQLi protection, known bad input filtering, and rate limiting.
# Cost: ~$5/mo base + per-request charges. Enable for staging/production.

# ── VPN IP Whitelist ────────────────────────────────────────
# Team VPN IPs bypass all WAF rules (including rate limits).
# Managed via var.vpn_whitelist_cidrs in environment tfvars.

resource "aws_wafv2_ip_set" "vpn_whitelist" {
  name               = "${local.name_prefix}-vpn-whitelist"
  scope              = "CLOUDFRONT"
  ip_address_version = "IPV4"
  addresses          = var.vpn_whitelist_cidrs
  tags               = { Name = "${local.name_prefix}-vpn-whitelist" }
}

resource "aws_wafv2_web_acl" "cloudfront" {
  name        = "${local.name_prefix}-cf-waf"
  scope       = "CLOUDFRONT"
  description = "WAF for ${local.name_prefix} CloudFront distribution"

  default_action {
    allow {}
  }

  # Allow team VPN IPs unconditionally (bypass all other rules)
  rule {
    name     = "allow-vpn"
    priority = 0

    action {
      allow {}
    }

    statement {
      ip_set_reference_statement {
        arn = aws_wafv2_ip_set.vpn_whitelist.arn
      }
    }

    visibility_config {
      sampled_requests_enabled   = true
      cloudwatch_metrics_enabled = true
      metric_name                = "${local.name_prefix}-vpn-allow"
    }
  }

  # AWS Managed: Common Rule Set (XSS, SQLi, bad inputs)
  # SizeRestrictions and NoUserAgent overridden to COUNT to avoid
  # false positives on CRM layout saves (>8KB) and CLI/script calls.
  rule {
    name     = "AWS-AWSManagedRulesCommonRuleSet"
    priority = 1

    override_action {
      none {}
    }

    statement {
      managed_rule_group_statement {
        name        = "AWSManagedRulesCommonRuleSet"
        vendor_name = "AWS"

        rule_action_override {
          name = "SizeRestrictions_BODY"
          action_to_use {
            count {}
          }
        }
        rule_action_override {
          name = "SizeRestrictions_QUERYSTRING"
          action_to_use {
            count {}
          }
        }
        rule_action_override {
          name = "NoUserAgent_HEADER"
          action_to_use {
            count {}
          }
        }
        # CRM record fields frequently contain HTML fragments (e.g. rich-text
        # fields like Champion__c = "<p>Eddy</p>"), which trigger false
        # positives on the XSS body inspection rule. COUNT instead of BLOCK
        # so WAF logs the match without rejecting the request. (#506)
        rule_action_override {
          name = "CrossSiteScripting_BODY"
          action_to_use {
            count {}
          }
        }
      }
    }

    visibility_config {
      sampled_requests_enabled   = true
      cloudwatch_metrics_enabled = true
      metric_name                = "${local.name_prefix}-common-rules"
    }
  }

  # AWS Managed: Known Bad Inputs (Log4j, etc.)
  rule {
    name     = "AWS-AWSManagedRulesKnownBadInputsRuleSet"
    priority = 2

    override_action {
      none {}
    }

    statement {
      managed_rule_group_statement {
        name        = "AWSManagedRulesKnownBadInputsRuleSet"
        vendor_name = "AWS"
      }
    }

    visibility_config {
      sampled_requests_enabled   = true
      cloudwatch_metrics_enabled = true
      metric_name                = "${local.name_prefix}-bad-inputs"
    }
  }

  # Rate limiting: 10000 requests per 5 minutes per IP (general)
  rule {
    name     = "rate-limit"
    priority = 3

    action {
      block {}
    }

    statement {
      rate_based_statement {
        limit              = 10000
        aggregate_key_type = "IP"
      }
    }

    visibility_config {
      sampled_requests_enabled   = true
      cloudwatch_metrics_enabled = true
      metric_name                = "${local.name_prefix}-rate-limit"
    }
  }

  # Stricter rate limit for token exchange endpoint (brute-force protection)
  rule {
    name     = "rate-limit-token-exchange"
    priority = 4

    action {
      block {}
    }

    statement {
      rate_based_statement {
        limit              = 100
        aggregate_key_type = "IP"

        scope_down_statement {
          byte_match_statement {
            search_string         = "/api/dev-sessions/exchange"
            positional_constraint = "STARTS_WITH"

            field_to_match {
              uri_path {}
            }

            text_transformation {
              priority = 0
              type     = "NONE"
            }
          }
        }
      }
    }

    visibility_config {
      sampled_requests_enabled   = true
      cloudwatch_metrics_enabled = true
      metric_name                = "${local.name_prefix}-rate-limit-exchange"
    }
  }

  visibility_config {
    sampled_requests_enabled   = true
    cloudwatch_metrics_enabled = true
    metric_name                = "${local.name_prefix}-waf"
  }

  tags = { Name = "${local.name_prefix}-waf" }
}

# ── WAF Logging ─────────────────────────────────────────────
# Logs blocked/counted requests to CloudWatch for incident response.
# The log group name must start with "aws-waf-logs-" per AWS requirements.

resource "aws_cloudwatch_log_group" "waf" {
  name              = "aws-waf-logs-${local.name_prefix}"
  retention_in_days = 30
  tags              = { Name = "${local.name_prefix}-waf-logs" }
}

resource "aws_wafv2_web_acl_logging_configuration" "cloudfront" {
  log_destination_configs = [aws_cloudwatch_log_group.waf.arn]
  resource_arn            = aws_wafv2_web_acl.cloudfront.arn

  logging_filter {
    default_behavior = "DROP"

    filter {
      behavior    = "KEEP"
      requirement = "MEETS_ANY"

      condition {
        action_condition {
          action = "BLOCK"
        }
      }

      condition {
        action_condition {
          action = "COUNT"
        }
      }
    }
  }
}
