Skip to content

Commit

Permalink
Merge pull request #24772 from steveorourke/main
Browse files Browse the repository at this point in the history
Support json body matching in wafv2 byte match rule
  • Loading branch information
ewbankkit authored Aug 30, 2022
2 parents 1b77bd1 + e2197e1 commit 41352c2
Show file tree
Hide file tree
Showing 7 changed files with 473 additions and 3 deletions.
7 changes: 7 additions & 0 deletions .changelog/24772.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
```release-note:enhancement
resource/aws_wafv2_rule_group: Add `json_body` attribute to the `field_to_match` block
```

```release-note:enhancement
resource/aws_wafv2_web_acl: Add `json_body` attribute to the `field_to_match` block
```
83 changes: 82 additions & 1 deletion internal/service/wafv2/flex.go
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,10 @@ func expandFieldToMatch(l []interface{}) *wafv2.FieldToMatch {
f.Cookies = expandCookies(m["cookies"].([]interface{}))
}

if v, ok := m["json_body"]; ok && len(v.([]interface{})) > 0 {
f.JsonBody = expandJSONBody(v.([]interface{}))
}

if v, ok := m["method"]; ok && len(v.([]interface{})) > 0 {
f.Method = &wafv2.Method{}
}
Expand Down Expand Up @@ -491,6 +495,45 @@ func expandCookieMatchPattern(l []interface{}) *wafv2.CookieMatchPattern {
return CookieMatchPattern
}

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

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

jsonBody := &wafv2.JsonBody{
MatchScope: aws.String(m["match_scope"].(string)),
OversizeHandling: aws.String(m["oversize_handling"].(string)),
MatchPattern: expandJSONMatchPattern(m["match_pattern"].([]interface{})),
}

if v, ok := m["invalid_fallback_behavior"].(string); ok && v != "" {
jsonBody.InvalidFallbackBehavior = aws.String(v)
}

return jsonBody
}

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

m := l[0].(map[string]interface{})
jsonMatchPattern := &wafv2.JsonMatchPattern{}

if v, ok := m["all"].([]interface{}); ok && len(v) > 0 {
jsonMatchPattern.All = &wafv2.All{}
}

if v, ok := m["included_paths"]; ok && len(v.([]interface{})) > 0 {
jsonMatchPattern.IncludedPaths = flex.ExpandStringList(v.([]interface{}))
}

return jsonMatchPattern
}

func expandSingleHeader(l []interface{}) *wafv2.SingleHeader {
if len(l) == 0 || l[0] == nil {
return nil
Expand Down Expand Up @@ -978,6 +1021,10 @@ func flattenFieldToMatch(f *wafv2.FieldToMatch) interface{} {
m["cookies"] = flattenCookies(f.Cookies)
}

if f.JsonBody != nil {
m["json_body"] = flattenJSONBody(f.JsonBody)
}

if f.Method != nil {
m["method"] = make([]map[string]interface{}, 1)
}
Expand Down Expand Up @@ -1048,11 +1095,45 @@ func flattenCookiesMatchPattern(c *wafv2.CookieMatchPattern) interface{} {
}

m := map[string]interface{}{
"all": c.All,
"included_cookies": aws.StringValueSlice(c.IncludedCookies),
"excluded_cookies": aws.StringValueSlice(c.ExcludedCookies),
}

if c.All != nil {
m["all"] = make([]map[string]interface{}, 1)
}

return []interface{}{m}
}

func flattenJSONBody(b *wafv2.JsonBody) interface{} {
if b == nil {
return []interface{}{}
}

m := map[string]interface{}{
"invalid_fallback_behavior": aws.StringValue(b.InvalidFallbackBehavior),
"match_pattern": flattenJSONMatchPattern(b.MatchPattern),
"match_scope": aws.StringValue(b.MatchScope),
"oversize_handling": aws.StringValue(b.OversizeHandling),
}

return []interface{}{m}
}

func flattenJSONMatchPattern(p *wafv2.JsonMatchPattern) []interface{} {
if p == nil {
return []interface{}{}
}

m := map[string]interface{}{
"included_paths": flex.FlattenStringList(p.IncludedPaths),
}

if p.All != nil {
m["all"] = make([]map[string]interface{}, 1)
}

return []interface{}{m}
}

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 @@ -380,6 +380,7 @@ func TestAccWAFV2RuleGroup_ByteMatchStatement_fieldToMatch(t *testing.T) {
"statement.0.byte_match_statement.0.field_to_match.0.all_query_arguments.#": "1",
"statement.0.byte_match_statement.0.field_to_match.0.body.#": "0",
"statement.0.byte_match_statement.0.field_to_match.0.cookies.#": "0",
"statement.0.byte_match_statement.0.field_to_match.0.json_body.#": "0",
"statement.0.byte_match_statement.0.field_to_match.0.method.#": "0",
"statement.0.byte_match_statement.0.field_to_match.0.query_string.#": "0",
"statement.0.byte_match_statement.0.field_to_match.0.single_header.#": "0",
Expand All @@ -401,6 +402,7 @@ func TestAccWAFV2RuleGroup_ByteMatchStatement_fieldToMatch(t *testing.T) {
"statement.0.byte_match_statement.0.field_to_match.0.all_query_arguments.#": "0",
"statement.0.byte_match_statement.0.field_to_match.0.body.#": "1",
"statement.0.byte_match_statement.0.field_to_match.0.cookies.#": "0",
"statement.0.byte_match_statement.0.field_to_match.0.json_body.#": "0",
"statement.0.byte_match_statement.0.field_to_match.0.method.#": "0",
"statement.0.byte_match_statement.0.field_to_match.0.query_string.#": "0",
"statement.0.byte_match_statement.0.field_to_match.0.single_header.#": "0",
Expand Down Expand Up @@ -435,6 +437,27 @@ func TestAccWAFV2RuleGroup_ByteMatchStatement_fieldToMatch(t *testing.T) {
}),
),
},
{
Config: testAccRuleGroupConfig_byteMatchStatementFieldToMatchJSONBody(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.byte_match_statement.#": "1",
"statement.0.byte_match_statement.0.field_to_match.#": "1",
"statement.0.byte_match_statement.0.field_to_match.0.json_body.#": "1",
"statement.0.byte_match_statement.0.field_to_match.0.json_body.0.match_scope": "VALUE",
"statement.0.byte_match_statement.0.field_to_match.0.json_body.0.invalid_fallback_behavior": "MATCH",
"statement.0.byte_match_statement.0.field_to_match.0.json_body.0.oversize_handling": "CONTINUE",
"statement.0.byte_match_statement.0.field_to_match.0.json_body.0.match_pattern.#": "1",
"statement.0.byte_match_statement.0.field_to_match.0.json_body.0.match_pattern.0.included_paths.#": "2",
"statement.0.byte_match_statement.0.field_to_match.0.json_body.0.match_pattern.0.included_paths.0": "/dogs/0/name",
"statement.0.byte_match_statement.0.field_to_match.0.json_body.0.match_pattern.0.included_paths.1": "/dogs/1/name",
}),
),
},
{
Config: testAccRuleGroupConfig_byteMatchStatementFieldToMatchMethod(ruleGroupName),
Check: resource.ComposeTestCheckFunc(
Expand All @@ -448,6 +471,7 @@ func TestAccWAFV2RuleGroup_ByteMatchStatement_fieldToMatch(t *testing.T) {
"statement.0.byte_match_statement.0.field_to_match.0.all_query_arguments.#": "0",
"statement.0.byte_match_statement.0.field_to_match.0.body.#": "0",
"statement.0.byte_match_statement.0.field_to_match.0.cookies.#": "0",
"statement.0.byte_match_statement.0.field_to_match.0.json_body.#": "0",
"statement.0.byte_match_statement.0.field_to_match.0.method.#": "1",
"statement.0.byte_match_statement.0.field_to_match.0.query_string.#": "0",
"statement.0.byte_match_statement.0.field_to_match.0.single_header.#": "0",
Expand All @@ -469,6 +493,7 @@ func TestAccWAFV2RuleGroup_ByteMatchStatement_fieldToMatch(t *testing.T) {
"statement.0.byte_match_statement.0.field_to_match.0.all_query_arguments.#": "0",
"statement.0.byte_match_statement.0.field_to_match.0.body.#": "0",
"statement.0.byte_match_statement.0.field_to_match.0.cookies.#": "0",
"statement.0.byte_match_statement.0.field_to_match.0.json_body.#": "0",
"statement.0.byte_match_statement.0.field_to_match.0.method.#": "0",
"statement.0.byte_match_statement.0.field_to_match.0.query_string.#": "1",
"statement.0.byte_match_statement.0.field_to_match.0.single_header.#": "0",
Expand All @@ -490,6 +515,7 @@ func TestAccWAFV2RuleGroup_ByteMatchStatement_fieldToMatch(t *testing.T) {
"statement.0.byte_match_statement.0.field_to_match.0.all_query_arguments.#": "0",
"statement.0.byte_match_statement.0.field_to_match.0.body.#": "0",
"statement.0.byte_match_statement.0.field_to_match.0.cookies.#": "0",
"statement.0.byte_match_statement.0.field_to_match.0.json_body.#": "0",
"statement.0.byte_match_statement.0.field_to_match.0.method.#": "0",
"statement.0.byte_match_statement.0.field_to_match.0.query_string.#": "0",
"statement.0.byte_match_statement.0.field_to_match.0.single_header.#": "1",
Expand All @@ -512,6 +538,7 @@ func TestAccWAFV2RuleGroup_ByteMatchStatement_fieldToMatch(t *testing.T) {
"statement.0.byte_match_statement.0.field_to_match.0.all_query_arguments.#": "0",
"statement.0.byte_match_statement.0.field_to_match.0.body.#": "0",
"statement.0.byte_match_statement.0.field_to_match.0.cookies.#": "0",
"statement.0.byte_match_statement.0.field_to_match.0.json_body.#": "0",
"statement.0.byte_match_statement.0.field_to_match.0.method.#": "0",
"statement.0.byte_match_statement.0.field_to_match.0.query_string.#": "0",
"statement.0.byte_match_statement.0.field_to_match.0.single_header.#": "0",
Expand All @@ -534,6 +561,7 @@ func TestAccWAFV2RuleGroup_ByteMatchStatement_fieldToMatch(t *testing.T) {
"statement.0.byte_match_statement.0.field_to_match.0.all_query_arguments.#": "0",
"statement.0.byte_match_statement.0.field_to_match.0.body.#": "0",
"statement.0.byte_match_statement.0.field_to_match.0.cookies.#": "0",
"statement.0.byte_match_statement.0.field_to_match.0.json_body.#": "0",
"statement.0.byte_match_statement.0.field_to_match.0.method.#": "0",
"statement.0.byte_match_statement.0.field_to_match.0.query_string.#": "0",
"statement.0.byte_match_statement.0.field_to_match.0.single_header.#": "0",
Expand Down Expand Up @@ -2467,6 +2495,60 @@ resource "aws_wafv2_rule_group" "test" {
`, name)
}

func testAccRuleGroupConfig_byteMatchStatementFieldToMatchJSONBody(name string) string {
return fmt.Sprintf(`
resource "aws_wafv2_rule_group" "test" {
capacity = 20
name = "%s"
scope = "REGIONAL"
rule {
name = "rule-1"
priority = 1
action {
allow {}
}
statement {
byte_match_statement {
positional_constraint = "CONTAINS"
search_string = "Clifford"
field_to_match {
json_body {
match_scope = "VALUE"
invalid_fallback_behavior = "MATCH"
oversize_handling = "CONTINUE"
match_pattern {
included_paths = ["/dogs/0/name", "/dogs/1/name"]
}
}
}
text_transformation {
priority = 1
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_byteMatchStatementFieldToMatchMethod(name string) string {
return fmt.Sprintf(`
resource "aws_wafv2_rule_group" "test" {
Expand Down
55 changes: 55 additions & 0 deletions internal/service/wafv2/schemas.go
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,7 @@ func xssMatchStatementSchema() *schema.Schema {
},
}
}

func fieldToMatchBaseSchema() *schema.Resource {
return &schema.Resource{
Schema: map[string]*schema.Schema{
Expand Down Expand Up @@ -372,6 +373,7 @@ func fieldToMatchBaseSchema() *schema.Resource {
},
},
},
"json_body": jsonBodySchema(),
"method": emptySchema(),
"query_string": emptySchema(),
"single_header": {
Expand Down Expand Up @@ -426,6 +428,59 @@ func fieldToMatchSchema() *schema.Schema {
}
}

func jsonBodySchema() *schema.Schema {
return &schema.Schema{
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"invalid_fallback_behavior": {
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringInSlice(wafv2.BodyParsingFallbackBehavior_Values(), false),
},
"match_pattern": jsonMatchPattern(),
"match_scope": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice(wafv2.JsonMatchScope_Values(), false),
},
"oversize_handling": {
Type: schema.TypeString,
Optional: true,
Default: wafv2.OversizeHandlingContinue,
ValidateFunc: validation.StringInSlice(wafv2.OversizeHandling_Values(), false),
},
},
},
}
}

func jsonMatchPattern() *schema.Schema {
return &schema.Schema{
Type: schema.TypeList,
Required: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"all": emptySchema(),
"included_paths": {
Type: schema.TypeList,
Optional: true,
MinItems: 1,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validation.All(
validation.StringLenBetween(1, 512),
validation.StringMatch(regexp.MustCompile(`(/)|(/(([^~])|(~[01]))+)`), "must be a valid JSON pointer")),
},
},
},
},
}
}

func forwardedIPConfig() *schema.Schema {
return &schema.Schema{
Type: schema.TypeList,
Expand Down
Loading

0 comments on commit 41352c2

Please sign in to comment.