Skip to content

Commit

Permalink
Add support for regex_match_statement to AWS WAF v2 ACL rules
Browse files Browse the repository at this point in the history
`RegexMatchStatement` is a valid kind statement, as described in AWS' API
documentation (see
https://docs.aws.amazon.com/waf/latest/APIReference/API_RegexMatchStatement.html).

This statement can be used in both `CreateRuleGroup` and `CreateWebACL`
API calls. For reference:

- https://docs.aws.amazon.com/waf/latest/APIReference/API_CreateRuleGroup.html
- https://docs.aws.amazon.com/waf/latest/APIReference/API_CreateWebACL.html

This solves hashicorp#21492.
  • Loading branch information
wilkmaia committed Jan 6, 2022
1 parent 388c74d commit ebbbbb0
Show file tree
Hide file tree
Showing 6 changed files with 227 additions and 4 deletions.
58 changes: 58 additions & 0 deletions internal/service/wafv2/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ func wafv2RootStatementSchema(level int) *schema.Schema {
"label_match_statement": wafv2LabelMatchStatementSchema(),
"not_statement": wafv2StatementSchema(level - 1),
"or_statement": wafv2StatementSchema(level - 1),
"regex_match_statement": wafv2RegexMatchStatementSchema(),
"regex_pattern_set_reference_statement": wafv2RegexPatternSetReferenceStatementSchema(),
"size_constraint_statement": wafv2SizeConstraintSchema(),
"sqli_match_statement": wafv2SqliMatchStatementSchema(),
Expand Down Expand Up @@ -97,6 +98,7 @@ func wafv2StatementSchema(level int) *schema.Schema {
"label_match_statement": wafv2LabelMatchStatementSchema(),
"not_statement": wafv2StatementSchema(level - 1),
"or_statement": wafv2StatementSchema(level - 1),
"regex_match_statement": wafv2RegexMatchStatementSchema(),
"regex_pattern_set_reference_statement": wafv2RegexPatternSetReferenceStatementSchema(),
"size_constraint_statement": wafv2SizeConstraintSchema(),
"sqli_match_statement": wafv2SqliMatchStatementSchema(),
Expand Down Expand Up @@ -124,6 +126,7 @@ func wafv2StatementSchema(level int) *schema.Schema {
"geo_match_statement": wafv2GeoMatchStatementSchema(),
"ip_set_reference_statement": wafv2IpSetReferenceStatementSchema(),
"label_match_statement": wafv2LabelMatchStatementSchema(),
"regex_match_statement": wafv2RegexMatchStatementSchema(),
"regex_pattern_set_reference_statement": wafv2RegexPatternSetReferenceStatementSchema(),
"size_constraint_statement": wafv2SizeConstraintSchema(),
"sqli_match_statement": wafv2SqliMatchStatementSchema(),
Expand Down Expand Up @@ -254,6 +257,25 @@ func wafv2LabelMatchStatementSchema() *schema.Schema {
}
}

func wafv2RegexMatchStatementSchema() *schema.Schema {
return &schema.Schema{
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"regex_string": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringLenBetween(1, 512),
},
"field_to_match": wafv2FieldToMatchSchema(),
"text_transformation": wafv2TextTransformationSchema(),
},
},
}
}

func wafv2RegexPatternSetReferenceStatementSchema() *schema.Schema {
return &schema.Schema{
Type: schema.TypeList,
Expand Down Expand Up @@ -910,6 +932,10 @@ func expandWafv2Statement(m map[string]interface{}) *wafv2.Statement {
statement.OrStatement = expandWafv2OrStatement(v.([]interface{}))
}

if v, ok := m["regex_match_statement"]; ok {
statement.RegexMatchStatement = expandWafv2RegexMatchStatement(v.([]interface{}))
}

if v, ok := m["regex_pattern_set_reference_statement"]; ok {
statement.RegexPatternSetReferenceStatement = expandWafv2RegexPatternSetReferenceStatement(v.([]interface{}))
}
Expand Down Expand Up @@ -1156,6 +1182,20 @@ func expandWafv2OrStatement(l []interface{}) *wafv2.OrStatement {
}
}

func expandWafv2RegexMatchStatement(l []interface{}) *wafv2.RegexMatchStatement {
if len(l) == 0 || l[0] == nil {
return nil
}

m := l[0].(map[string]interface{})

return &wafv2.RegexMatchStatement{
RegexString: aws.String(m["regex_string"].(string)),
FieldToMatch: expandWafv2FieldToMatch(m["field_to_match"].([]interface{})),
TextTransformations: expandWafv2TextTransformations(m["text_transformation"].(*schema.Set).List()),
}
}

func expandWafv2RegexPatternSetReferenceStatement(l []interface{}) *wafv2.RegexPatternSetReferenceStatement {
if len(l) == 0 || l[0] == nil {
return nil
Expand Down Expand Up @@ -1426,6 +1466,10 @@ func flattenWafv2Statement(s *wafv2.Statement) map[string]interface{} {
m["or_statement"] = flattenWafv2OrStatement(s.OrStatement)
}

if s.RegexMatchStatement != nil {
m["regex_match_statement"] = flattenWafv2RegexMatchStatement(s.RegexMatchStatement)
}

if s.RegexPatternSetReferenceStatement != nil {
m["regex_pattern_set_reference_statement"] = flattenWafv2RegexPatternSetReferenceStatement(s.RegexPatternSetReferenceStatement)
}
Expand Down Expand Up @@ -1635,6 +1679,20 @@ func flattenWafv2OrStatement(a *wafv2.OrStatement) interface{} {
return []interface{}{m}
}

func flattenWafv2RegexMatchStatement(r *wafv2.RegexMatchStatement) interface{} {
if r == nil {
return []interface{}{}
}

m := map[string]interface{}{
"regex_string": aws.StringValue(r.RegexString),
"field_to_match": flattenWafv2FieldToMatch(r.FieldToMatch),
"text_transformation": flattenWafv2TextTransformations(r.TextTransformations),
}

return []interface{}{m}
}

func flattenWafv2RegexPatternSetReferenceStatement(r *wafv2.RegexPatternSetReferenceStatement) interface{} {
if r == nil {
return []interface{}{}
Expand Down
82 changes: 82 additions & 0 deletions internal/service/wafv2/rule_group_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1130,6 +1130,42 @@ func TestAccWAFV2RuleGroup_minimal(t *testing.T) {
})
}

func TestAccWAFV2RuleGroup_regexMatchStatement(t *testing.T) {
var v wafv2.RuleGroup
ruleGroupName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
resourceName := "aws_wafv2_rule_group.test"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(t); testAccPreCheckScopeRegional(t) },
ErrorCheck: acctest.ErrorCheck(t, wafv2.EndpointsID),
Providers: acctest.Providers,
CheckDestroy: testAccCheckRuleGroupDestroy,
Steps: []resource.TestStep{
{
Config: testAccRuleGroupConfig_RegexMatchStatement(ruleGroupName),
Check: resource.ComposeTestCheckFunc(
testAccCheckRuleGroupExists(resourceName, &v),
acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "wafv2", regexp.MustCompile(`regional/rulegroup/.+$`)),
resource.TestCheckResourceAttr(resourceName, "rule.#", "1"),
resource.TestCheckTypeSetElemNestedAttrs(resourceName, "rule.*", map[string]string{
"statement.#": "1",
"statement.0.regex_match_statement.#": "1",
"statement.0.regex_match_statement.0.regex_string": "one",
"statement.0.regex_match_statement.0.field_to_match.#": "1",
"statement.0.regex_match_statement.0.text_transformation.#": "1",
}),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
ImportStateIdFunc: testAccRuleGroupImportStateIdFunc(resourceName),
},
},
})
}

func TestAccWAFV2RuleGroup_regexPatternSetReferenceStatement(t *testing.T) {
var v wafv2.RuleGroup
ruleGroupName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
Expand Down Expand Up @@ -3143,6 +3179,52 @@ resource "aws_wafv2_rule_group" "test" {
`, name)
}

func testAccRuleGroupConfig_RegexMatchStatement(name string) string {
return fmt.Sprintf(`
resource "aws_wafv2_rule_group" "test" {
capacity = 50
name = "%s"
scope = "REGIONAL"
rule {
name = "rule-1"
priority = 1
action {
allow {}
}
statement {
regex_match_statement {
regex_string = "one"
field_to_match {
body {}
}
text_transformation {
priority = 2
type = "NONE"
}
}
}
visibility_config {
cloudwatch_metrics_enabled = false
metric_name = "friendly-rule-metric-name"
sampled_requests_enabled = false
}
}
visibility_config {
cloudwatch_metrics_enabled = false
metric_name = "friendly-metric-name"
sampled_requests_enabled = false
}
}
`, name)
}

func testAccRuleGroupConfig_RegexPatternSetReferenceStatement(name string) string {
return fmt.Sprintf(`
resource "aws_wafv2_regex_pattern_set" "test" {
Expand Down
10 changes: 10 additions & 0 deletions internal/service/wafv2/web_acl.go
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,7 @@ func wafv2WebACLRootStatementSchema(level int) *schema.Schema {
"not_statement": wafv2StatementSchema(level),
"or_statement": wafv2StatementSchema(level),
"rate_based_statement": wafv2RateBasedStatementSchema(level),
"regex_match_statement": wafv2RegexMatchStatementSchema(),
"regex_pattern_set_reference_statement": wafv2RegexPatternSetReferenceStatementSchema(),
"rule_group_reference_statement": wafv2RuleGroupReferenceStatementSchema(),
"size_constraint_statement": wafv2SizeConstraintSchema(),
Expand Down Expand Up @@ -471,6 +472,7 @@ func wafv2ScopeDownStatementSchema(level int) *schema.Schema {
"ip_set_reference_statement": wafv2IpSetReferenceStatementSchema(),
"not_statement": wafv2StatementSchema(level),
"or_statement": wafv2StatementSchema(level),
"regex_match_statement": wafv2RegexMatchStatementSchema(),
"regex_pattern_set_reference_statement": wafv2RegexPatternSetReferenceStatementSchema(),
"size_constraint_statement": wafv2SizeConstraintSchema(),
"sqli_match_statement": wafv2SqliMatchStatementSchema(),
Expand Down Expand Up @@ -627,6 +629,10 @@ func expandWafv2WebACLStatement(m map[string]interface{}) *wafv2.Statement {
statement.RateBasedStatement = expandWafv2RateBasedStatement(v.([]interface{}))
}

if v, ok := m["regex_match_statement"]; ok {
statement.RegexMatchStatement = expandWafv2RegexMatchStatement(v.([]interface{}))
}

if v, ok := m["regex_pattern_set_reference_statement"]; ok {
statement.RegexPatternSetReferenceStatement = expandWafv2RegexPatternSetReferenceStatement(v.([]interface{}))
}
Expand Down Expand Up @@ -783,6 +789,10 @@ func flattenWafv2WebACLStatement(s *wafv2.Statement) map[string]interface{} {
m["rate_based_statement"] = flattenWafv2RateBasedStatement(s.RateBasedStatement)
}

if s.RegexMatchStatement != nil {
m["regex_match_statement"] = flattenWafv2RegexMatchStatement(s.RegexMatchStatement)
}

if s.RegexPatternSetReferenceStatement != nil {
m["regex_pattern_set_reference_statement"] = flattenWafv2RegexPatternSetReferenceStatement(s.RegexPatternSetReferenceStatement)
}
Expand Down
40 changes: 36 additions & 4 deletions internal/service/wafv2/web_acl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1459,9 +1459,10 @@ func TestAccWAFV2WebACL_RateBased_maxNested(t *testing.T) {
"statement.0.rate_based_statement.0.scope_down_statement.0.not_statement.#": "1",
"statement.0.rate_based_statement.0.scope_down_statement.0.not_statement.0.statement.#": "1",
"statement.0.rate_based_statement.0.scope_down_statement.0.not_statement.0.statement.0.or_statement.#": "1",
"statement.0.rate_based_statement.0.scope_down_statement.0.not_statement.0.statement.0.or_statement.0.statement.#": "2",
"statement.0.rate_based_statement.0.scope_down_statement.0.not_statement.0.statement.0.or_statement.0.statement.#": "3",
"statement.0.rate_based_statement.0.scope_down_statement.0.not_statement.0.statement.0.or_statement.0.statement.0.regex_pattern_set_reference_statement.#": "1",
"statement.0.rate_based_statement.0.scope_down_statement.0.not_statement.0.statement.0.or_statement.0.statement.1.ip_set_reference_statement.#": "1",
"statement.0.rate_based_statement.0.scope_down_statement.0.not_statement.0.statement.0.or_statement.0.statement.1.regex_match_statement.#": "1",
"statement.0.rate_based_statement.0.scope_down_statement.0.not_statement.0.statement.0.or_statement.0.statement.2.ip_set_reference_statement.#": "1",
}),
),
},
Expand Down Expand Up @@ -1500,9 +1501,10 @@ func TestAccWAFV2WebACL_Operators_maxNested(t *testing.T) {
"statement.0.and_statement.0.statement.0.not_statement.#": "1",
"statement.0.and_statement.0.statement.0.not_statement.0.statement.#": "1",
"statement.0.and_statement.0.statement.0.not_statement.0.statement.0.or_statement.#": "1",
"statement.0.and_statement.0.statement.0.not_statement.0.statement.0.or_statement.0.statement.#": "2",
"statement.0.and_statement.0.statement.0.not_statement.0.statement.0.or_statement.0.statement.#": "3",
"statement.0.and_statement.0.statement.0.not_statement.0.statement.0.or_statement.0.statement.0.regex_pattern_set_reference_statement.#": "1",
"statement.0.and_statement.0.statement.0.not_statement.0.statement.0.or_statement.0.statement.1.ip_set_reference_statement.#": "1",
"statement.0.and_statement.0.statement.0.not_statement.0.statement.0.or_statement.0.statement.1.regex_match_statement.#": "1",
"statement.0.and_statement.0.statement.0.not_statement.0.statement.0.or_statement.0.statement.2.ip_set_reference_statement.#": "1",
"statement.0.and_statement.0.statement.1.geo_match_statement.#": "1",
"statement.0.and_statement.0.statement.1.geo_match_statement.0.country_codes.0": "NL",
}),
Expand Down Expand Up @@ -2837,6 +2839,21 @@ resource "aws_wafv2_web_acl" "test" {
}
}
statement {
regex_match_statement {
regex_string = "two"
field_to_match {
uri_path {}
}
text_transformation {
type = "LOWERCASE"
priority = 1
}
}
}
statement {
ip_set_reference_statement {
arn = aws_wafv2_ip_set.test.arn
Expand Down Expand Up @@ -2921,6 +2938,21 @@ resource "aws_wafv2_web_acl" "test" {
}
}
statement {
regex_match_statement {
regex_string = "two"
field_to_match {
uri_path {}
}
text_transformation {
type = "LOWERCASE"
priority = 1
}
}
}
statement {
ip_set_reference_statement {
arn = aws_wafv2_ip_set.test.arn
Expand Down
30 changes: 30 additions & 0 deletions website/docs/r/wafv2_rule_group.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,25 @@ resource "aws_wafv2_rule_group" "example" {
statement {
or_statement {
statement {
regex_match_statement {
regex_string = "two"
field_to_match {
single_header {
name = "user-agent"
}
}
text_transformation {
priority = 6
type = "NONE"
}
}
}
statement {
sqli_match_statement {
Expand Down Expand Up @@ -383,6 +402,7 @@ The `statement` block supports the following arguments:
* `ip_set_reference_statement` - (Optional) A rule statement used to detect web requests coming from particular IP addresses or address ranges. See [IP Set Reference Statement](#ip-set-reference-statement) below for details.
* `not_statement` - (Optional) A logical rule statement used to negate the results of another rule statement. See [NOT Statement](#not-statement) below for details.
* `or_statement` - (Optional) A logical rule statement used to combine other rule statements with OR logic. See [OR Statement](#or-statement) below for details.
* `regex_match_statement` - (Optional) A rule statement used to search web request components for a match against a single regular expression. See [Regex Match Statement](#regex-match-statement) below for details.
* `regex_pattern_set_reference_statement` - (Optional) A rule statement used to search web request components for matches with regular expressions. See [Regex Pattern Set Reference Statement](#regex-pattern-set-reference-statement) below for details.
* `size_constraint_statement` - (Optional) A rule statement that compares a number of bytes against the size of a request component, using a comparison operator, such as greater than (>) or less than (<). See [Size Constraint Statement](#size-constraint-statement) below for more details.
* `sqli_match_statement` - (Optional) An SQL injection match condition identifies the part of web requests, such as the URI or the query string, that you want AWS WAF to inspect. See [SQL Injection Match Statement](#sql-injection-match-statement) below for details.
Expand Down Expand Up @@ -446,6 +466,16 @@ The `or_statement` block supports the following arguments:

* `statement` - (Required) The statements to combine with `OR` logic. You can use any statements that can be nested. See [Statement](#statement) above for details.

### Regex Match Statement

A rule statement used to search web request components for a match against a single regular expression.

The `regex_match_statement` block supports the following arguments:

* `regex_string` - (Required) The string representing the regular expression. Minimum of `1` and maximum of `512` characters.
* `field_to_match` - (Required) The part of a web request that you want AWS WAF to inspect. See [Field to Match](#field-to-match) below for details.
* `text_transformation` - (Required) Text transformations eliminate some of the unusual formatting that attackers use in web requests in an effort to bypass detection. See [Text Transformation](#text-transformation) below for details.

### Regex Pattern Set Reference Statement

A rule statement used to search web request components for matches with regular expressions. To use this, create a `aws_wafv2_regex_pattern_set` that specifies the expressions that you want to detect, then use the `ARN` of that set in this statement. A web request matches the pattern set rule statement if the request component matches any of the patterns in the set.
Expand Down
Loading

0 comments on commit ebbbbb0

Please sign in to comment.