diff --git a/.changelog/24772.txt b/.changelog/24772.txt new file mode 100644 index 00000000000..18d0701076d --- /dev/null +++ b/.changelog/24772.txt @@ -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 +``` \ No newline at end of file diff --git a/internal/service/wafv2/flex.go b/internal/service/wafv2/flex.go index 8396c97847a..55cd3f8ee7f 100644 --- a/internal/service/wafv2/flex.go +++ b/internal/service/wafv2/flex.go @@ -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{} } @@ -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 @@ -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) } @@ -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} } diff --git a/internal/service/wafv2/rule_group_test.go b/internal/service/wafv2/rule_group_test.go index 135d441bd31..4915bd0bcbc 100644 --- a/internal/service/wafv2/rule_group_test.go +++ b/internal/service/wafv2/rule_group_test.go @@ -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", @@ -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", @@ -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( @@ -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", @@ -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", @@ -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", @@ -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", @@ -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", @@ -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" { diff --git a/internal/service/wafv2/schemas.go b/internal/service/wafv2/schemas.go index 88c21fc7cac..295c84bf27e 100644 --- a/internal/service/wafv2/schemas.go +++ b/internal/service/wafv2/schemas.go @@ -329,6 +329,7 @@ func xssMatchStatementSchema() *schema.Schema { }, } } + func fieldToMatchBaseSchema() *schema.Resource { return &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -372,6 +373,7 @@ func fieldToMatchBaseSchema() *schema.Resource { }, }, }, + "json_body": jsonBodySchema(), "method": emptySchema(), "query_string": emptySchema(), "single_header": { @@ -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, diff --git a/internal/service/wafv2/web_acl_test.go b/internal/service/wafv2/web_acl_test.go index 2c91a0f9979..300b7f3f6a7 100644 --- a/internal/service/wafv2/web_acl_test.go +++ b/internal/service/wafv2/web_acl_test.go @@ -737,6 +737,126 @@ func TestAccWAFV2WebACL_RateBased_basic(t *testing.T) { }) } +func TestAccWAFV2WebACL_ByteMatchStatement_basic(t *testing.T) { + var v wafv2.WebACL + webACLName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_wafv2_web_acl.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); testAccPreCheckScopeRegional(t) }, + ErrorCheck: acctest.ErrorCheck(t, wafv2.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckWebACLDestroy, + Steps: []resource.TestStep{ + { + Config: testAccWebACLConfig_byteMatchStatement(webACLName, wafv2.PositionalConstraintContainsWord, "value1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckWebACLExists(resourceName, &v), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "wafv2", regexp.MustCompile(`regional/webacl/.+$`)), + resource.TestCheckResourceAttr(resourceName, "name", webACLName), + 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.all_query_arguments.#": "1", + "statement.0.byte_match_statement.0.positional_constraint": "CONTAINS_WORD", + "statement.0.byte_match_statement.0.search_string": "value1", + "statement.0.byte_match_statement.0.text_transformation.#": "1", + "statement.0.byte_match_statement.0.text_transformation.0.priority": "0", + "statement.0.byte_match_statement.0.text_transformation.0.type": "NONE", + }), + ), + }, + { + Config: testAccWebACLConfig_byteMatchStatement(webACLName, wafv2.PositionalConstraintExactly, "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckWebACLExists(resourceName, &v), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "wafv2", regexp.MustCompile(`regional/webacl/.+$`)), + resource.TestCheckResourceAttr(resourceName, "name", webACLName), + 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.all_query_arguments.#": "1", + "statement.0.byte_match_statement.0.positional_constraint": "EXACTLY", + "statement.0.byte_match_statement.0.search_string": "value2", + "statement.0.byte_match_statement.0.text_transformation.#": "1", + "statement.0.byte_match_statement.0.text_transformation.0.priority": "0", + "statement.0.byte_match_statement.0.text_transformation.0.type": "NONE", + }), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: testAccWebACLImportStateIdFunc(resourceName), + }, + }, + }) +} + +func TestAccWAFV2WebACL_ByteMatchStatement_jsonBody(t *testing.T) { + var v wafv2.WebACL + webACLName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_wafv2_web_acl.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); testAccPreCheckScopeRegional(t) }, + ErrorCheck: acctest.ErrorCheck(t, wafv2.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckWebACLDestroy, + Steps: []resource.TestStep{ + { + Config: testAccWebACLConfig_byteMatchStatementJSONBody(webACLName, wafv2.JsonMatchScopeValue, wafv2.FallbackBehaviorMatch, wafv2.OversizeHandlingNoMatch, `included_paths = ["/dogs/0/name", "/dogs/1/name"]`), + Check: resource.ComposeTestCheckFunc( + testAccCheckWebACLExists(resourceName, &v), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "wafv2", regexp.MustCompile(`regional/webacl/.+$`)), + resource.TestCheckResourceAttr(resourceName, "name", webACLName), + resource.TestCheckResourceAttr(resourceName, "rule.#", "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "rule.*", map[string]string{ + "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": "NO_MATCH", + "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.all.#": "0", + "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: testAccWebACLConfig_byteMatchStatementJSONBody(webACLName, wafv2.JsonMatchScopeAll, wafv2.FallbackBehaviorNoMatch, wafv2.OversizeHandlingContinue, "all {}"), + Check: resource.ComposeTestCheckFunc( + testAccCheckWebACLExists(resourceName, &v), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "wafv2", regexp.MustCompile(`regional/webacl/.+$`)), + resource.TestCheckResourceAttr(resourceName, "name", webACLName), + resource.TestCheckResourceAttr(resourceName, "rule.#", "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "rule.*", map[string]string{ + "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": "ALL", + "statement.0.byte_match_statement.0.field_to_match.0.json_body.0.invalid_fallback_behavior": "NO_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.all.#": "1", + "statement.0.byte_match_statement.0.field_to_match.0.json_body.0.match_pattern.0.included_paths.#": "0", + }), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: testAccWebACLImportStateIdFunc(resourceName), + }, + }, + }) +} + func TestAccWAFV2WebACL_GeoMatch_basic(t *testing.T) { var v wafv2.WebACL webACLName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -1870,6 +1990,111 @@ resource "aws_wafv2_web_acl" "test" { `, name, ruleName1, priority1, ruleName2, priority2) } +func testAccWebACLConfig_byteMatchStatement(name, positionalConstraint, searchString string) string { + return fmt.Sprintf(` +resource "aws_wafv2_web_acl" "test" { + name = "%[1]s" + description = "%[1]s" + scope = "REGIONAL" + + default_action { + allow {} + } + + rule { + name = "rule-1" + priority = 1 + + action { + count {} + } + + statement { + byte_match_statement { + field_to_match { + all_query_arguments {} + } + positional_constraint = "%[2]s" + search_string = "%[3]s" + text_transformation { + priority = 0 + 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, positionalConstraint, searchString) +} + +func testAccWebACLConfig_byteMatchStatementJSONBody(name, matchScope, invalidFallbackBehavior, oversizeHandling, matchPattern string) string { + return fmt.Sprintf(` +resource "aws_wafv2_web_acl" "test" { + name = "%[1]s" + description = "%[1]s" + scope = "REGIONAL" + + default_action { + allow {} + } + + rule { + name = "rule-1" + priority = 1 + + action { + count {} + } + + statement { + byte_match_statement { + field_to_match { + json_body { + match_scope = "%[2]s" + invalid_fallback_behavior = "%[3]s" + oversize_handling = "%[4]s" + match_pattern { + %[5]s + } + } + } + positional_constraint = "CONTAINS_WORD" + search_string = "Buddy" + text_transformation { + priority = 0 + 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, matchScope, invalidFallbackBehavior, oversizeHandling, matchPattern) +} + func testAccWebACLConfig_geoMatchStatement(name, countryCodes string) string { return fmt.Sprintf(` resource "aws_wafv2_web_acl" "test" { diff --git a/website/docs/r/wafv2_rule_group.html.markdown b/website/docs/r/wafv2_rule_group.html.markdown index 69db541d5a4..c1bf8db4c0f 100644 --- a/website/docs/r/wafv2_rule_group.html.markdown +++ b/website/docs/r/wafv2_rule_group.html.markdown @@ -492,12 +492,13 @@ The part of a web request that you want AWS WAF to inspect. Include the single ` The `field_to_match` block supports the following arguments: -~> **NOTE:** Only one of `all_query_arguments`, `body`, `cookies`, `method`, `query_string`, `single_header`, `single_query_argument`, or `uri_path` can be specified. +~> **NOTE:** Only one of `all_query_arguments`, `body`, `cookies`, `json_body`, `method`, `query_string`, `single_header`, `single_query_argument`, or `uri_path` can be specified. An empty configuration block `{}` should be used when specifying `all_query_arguments`, `body`, `method`, or `query_string` attributes. * `all_query_arguments` - (Optional) Inspect all query arguments. * `body` - (Optional) Inspect the request body, which immediately follows the request headers. * `cookies` - (Optional) Inspect the request cookies. +* `json_body` - (Optional) Inspect the request body as JSON. See [JSON Body](#json-body) for details. * `method` - (Optional) Inspect the HTTP method. The method indicates the type of operation that the request is asking the origin to perform. * `query_string` - (Optional) Inspect the query string. This is the part of a URL that appears after a `?` character, if any. * `single_header` - (Optional) Inspect a single header. See [Single Header](#single-header) below for details. @@ -525,6 +526,15 @@ The `ip_set_forwarded_ip_config` block supports the following arguments: * `header_name` - (Required) - The name of the HTTP header to use for the IP address. * `position` - (Required) - The position in the header to search for the IP address. Valid values include: `FIRST`, `LAST`, or `ANY`. If `ANY` is specified and the header contains more than 10 IP addresses, AWS WAFv2 inspects the last 10. +### JSON Body + +The `json_body` block supports the following arguments: + +* `invalid_fallback_behavior` - (Optional) What to do when JSON parsing fails. Defaults to evaluating up to the first parsing failure. Valid values are `EVALUATE_AS_STRING`, `MATCH` and `NO_MATCH`. +* `match_pattern` - (Required) The patterns to look for in the JSON body. You must specify exactly one setting: either `all` or `included_paths`. See [JsonMatchPattern](https://docs.aws.amazon.com/waf/latest/APIReference/API_JsonMatchPattern.html) for details. +* `match_scope` - (Required) The parts of the JSON to match against using the `match_pattern`. Valid values are `ALL`, `KEY` and `VALUE`. +* `oversize_handling` - (Optional) What to do if the body is larger than can be inspected. Valid values are `CONTINUE` (default), `MATCH` and `NO_MATCH`. + ### Single Header Inspect a single header. Provide the name of the header to inspect, for example, `User-Agent` or `Referer` (provided as lowercase strings). diff --git a/website/docs/r/wafv2_web_acl.html.markdown b/website/docs/r/wafv2_web_acl.html.markdown index 860be6a9889..b82b4cfa886 100644 --- a/website/docs/r/wafv2_web_acl.html.markdown +++ b/website/docs/r/wafv2_web_acl.html.markdown @@ -545,12 +545,13 @@ The part of a web request that you want AWS WAF to inspect. Include the single ` The `field_to_match` block supports the following arguments: -~> **NOTE:** Only one of `all_query_arguments`, `body`, `cookies`, `method`, `query_string`, `single_header`, `single_query_argument`, or `uri_path` can be specified. +~> **NOTE:** Only one of `all_query_arguments`, `body`, `cookies`, `json_body`, `method`, `query_string`, `single_header`, `single_query_argument`, or `uri_path` can be specified. An empty configuration block `{}` should be used when specifying `all_query_arguments`, `body`, `method`, or `query_string` attributes. * `all_query_arguments` - (Optional) Inspect all query arguments. * `body` - (Optional) Inspect the request body, which immediately follows the request headers. * `cookies` - (Optional) Inspect the request cookies. +* `json_body` - (Optional) Inspect the request body as JSON. See [JSON Body](#json-body) for details. * `method` - (Optional) Inspect the HTTP method. The method indicates the type of operation that the request is asking the origin to perform. * `query_string` - (Optional) Inspect the query string. This is the part of a URL that appears after a `?` character, if any. * `single_header` - (Optional) Inspect a single header. See [Single Header](#single-header) below for details. @@ -578,6 +579,15 @@ The `ip_set_forwarded_ip_config` block supports the following arguments: * `header_name` - (Required) - Name of the HTTP header to use for the IP address. * `position` - (Required) - Position in the header to search for the IP address. Valid values include: `FIRST`, `LAST`, or `ANY`. If `ANY` is specified and the header contains more than 10 IP addresses, AWS WAFv2 inspects the last 10. +### JSON Body + +The `json_body` block supports the following arguments: + +* `invalid_fallback_behavior` - (Optional) What to do when JSON parsing fails. Defaults to evaluating up to the first parsing failure. Valid values are `EVALUATE_AS_STRING`, `MATCH` and `NO_MATCH`. +* `match_pattern` - (Required) The patterns to look for in the JSON body. You must specify exactly one setting: either `all` or `included_paths`. See [JsonMatchPattern](https://docs.aws.amazon.com/waf/latest/APIReference/API_JsonMatchPattern.html) for details. +* `match_scope` - (Required) The parts of the JSON to match against using the `match_pattern`. Valid values are `ALL`, `KEY` and `VALUE`. +* `oversize_handling` - (Optional) What to do if the body is larger than can be inspected. Valid values are `CONTINUE` (default), `MATCH` and `NO_MATCH`. + ### Single Header Inspect a single header. Provide the name of the header to inspect, for example, `User-Agent` or `Referer` (provided as lowercase strings).