'When using Terraform with AWS, how can I set a rate limit on a specific URI path (or regex of a URI path) on an ALB

I am trying to rate limit requests to the forgot password change URL using WAFv2 rules attached to an ALB on Cloudfront.

What I think I need to do is..

Create two resources aws_wafv2_web_acl.afv2_rate_limit and another called aws_wafv2_regex_pattern_set.wafv2_password_url

Example of rate: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/wafv2_web_acl

Example of regex: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/wafv2_regex_pattern_set

Combine these into a rule group, call it aws_wafv2_rule_group.wafv2_rule_group_pw_rate_group

Example of group: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/wafv2_rule

I've created the rate limit and the regex, but I am failing to create the rule group. I put this rule in to refer to the rate limit

    rule {
        name = "rate_limit"
        priority = 1
        action {
            block {}
        }
        statement {
            and_statement {
                statement {
                    rule_group_reference_statement {  # !!FIXME!! doesn't work
                        arn = aws_wafv2_web_acl.wafv2_rate_limit.arn
                    }
                }
           }
        }
        visibility_config {
            cloudwatch_metrics_enabled = false
            metric_name                = "password_url"
            sampled_requests_enabled   = false
        }
    }

I get the error on the rule_group_reference_statement line:

Blocks of type "rule_group_reference_statement" are not expected here.

I can attach the rule group to the ALB.

of course, the first question is whether this is even the right way to go about it?!

thanks for any thoughts.



Solution 1:[1]

working!

resource "aws_wafv2_web_acl" "wafv2_alb_pw5pm_acl" {
    name        = "wafv2_alb_pw5pm-acl"
    description = "prevent brute forcing password setting or changing"
    scope       = "REGIONAL"       # if using this, no need to set provider

    default_action {
        allow {}    # pass traffic until the rules trigger a block
    }

    rule {
        name     = "rate_limit_pw5pm"
        priority = 1

        action {
            block {}
        }
        statement {
            rate_based_statement {
                #limit              = 300    # 5 per sec = 300 per min
                limit              = 100     # smallest value for testing
                aggregate_key_type = "IP"

                scope_down_statement {
                    regex_pattern_set_reference_statement {
                        arn = aws_wafv2_regex_pattern_set.wafv2_password_uri.arn
                        text_transformation {
                            priority = 1
                            type     = "NONE"
                        }
                        field_to_match {
                            uri_path {}
                        }
                    }
                }
            }

        }
        visibility_config {
            cloudwatch_metrics_enabled = true
            metric_name                = "wafv2_alb_pw5pm_acl_rule_vis"
            sampled_requests_enabled   = false
        }
    }

    visibility_config {
        cloudwatch_metrics_enabled = true
        metric_name                = "wafv2_alb_pw5pm_acl_vis"
        sampled_requests_enabled   = false
    }

    tags = {
        managedby   = "terraform"
    }
}


resource "aws_wafv2_web_acl_association" "web_acl_association_my_lb" {
    resource_arn = aws_lb.xxxxxx.arn
    web_acl_arn  = aws_wafv2_web_acl.wafv2_alb_pw5pm_acl.arn
}

Solution 2:[2]

You can't nest a rule_group_reference_statement, for example for use inside a and_statement, not_statement or or_statement. It can only be referenced as a top-level statement within a rule.

Solution 3:[3]

yes you can. basically you need to declare an aws_wafv2_regex_pattern_set, in this example I use the URI "/api/*" but it can be a fixed one too.

resource "aws_wafv2_regex_pattern_set" "regex_pattern_api" {
  name  = "regex-path-api"
  scope = "REGIONAL"

  regular_expression {
    regex_string = "/api/.+"
  }
}

and the here is an example on how to use it on a waf declaration:

resource "aws_wafv2_web_acl" "waf" {
  name  = "waf"
  scope = "REGIONAL"

  default_action {
    allow {}
  }

  rule {
    name     = "RateLimit"
    priority = 1

    action {
      block {}
    }

    statement {

      rate_based_statement {
        aggregate_key_type = "IP"
        limit              = 100

        scope_down_statement {
          regex_pattern_set_reference_statement {
            arn = aws_wafv2_regex_pattern_set.regex_pattern_api.arn

            field_to_match {
              uri_path {}
            }
            text_transformation {
              priority = 1
              type     = "NONE"
            }
          }
        }
      }
    }

    visibility_config {
      cloudwatch_metrics_enabled = true
      metric_name                = "RateLimit"
      sampled_requests_enabled   = true
    }
  }

  visibility_config {
    cloudwatch_metrics_enabled = false
    metric_name                = "waf"
    sampled_requests_enabled   = false
  }
}

The cool part of this is that it is a rate limit that narrows the filter based on client IP using scope_down_statement

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Community
Solution 2 Asri Badlah
Solution 3