Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support json body matching in wafv2 byte match rule #24772

Merged
merged 10 commits into from
Aug 30, 2022
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