diff --git a/.changelog/25845.txt b/.changelog/25845.txt new file mode 100644 index 00000000000..10f7605f71b --- /dev/null +++ b/.changelog/25845.txt @@ -0,0 +1,7 @@ +```release-note:enhancement +resource/aws_wafv2_rule_group: Add `cookies` attribute to the `field_to_match` block +``` + +```release-note:enhancement +resource/aws_wafv2_web_acl: Add `cookies` attribute to the `field_to_match` block +``` diff --git a/internal/service/wafv2/flex.go b/internal/service/wafv2/flex.go index 95b3ebf54b0..8396c97847a 100644 --- a/internal/service/wafv2/flex.go +++ b/internal/service/wafv2/flex.go @@ -395,6 +395,10 @@ func expandFieldToMatch(l []interface{}) *wafv2.FieldToMatch { f.Body = &wafv2.Body{} } + if v, ok := m["cookies"]; ok && len(v.([]interface{})) > 0 { + f.Cookies = expandCookies(m["cookies"].([]interface{})) + } + if v, ok := m["method"]; ok && len(v.([]interface{})) > 0 { f.Method = &wafv2.Method{} } @@ -445,6 +449,48 @@ func expandIPSetForwardedIPConfig(l []interface{}) *wafv2.IPSetForwardedIPConfig } } +func expandCookies(l []interface{}) *wafv2.Cookies { + if len(l) == 0 || l[0] == nil { + return nil + } + + m := l[0].(map[string]interface{}) + + cookies := &wafv2.Cookies{ + MatchScope: aws.String(m["match_scope"].(string)), + OversizeHandling: aws.String(m["oversize_handling"].(string)), + } + + if v, ok := m["match_pattern"]; ok && len(v.([]interface{})) > 0 { + cookies.MatchPattern = expandCookieMatchPattern(v.([]interface{})) + } + + return cookies +} + +func expandCookieMatchPattern(l []interface{}) *wafv2.CookieMatchPattern { + if len(l) == 0 || l[0] == nil { + return nil + } + + m := l[0].(map[string]interface{}) + CookieMatchPattern := &wafv2.CookieMatchPattern{} + + if v, ok := m["included_cookies"]; ok && len(v.([]interface{})) > 0 { + CookieMatchPattern.IncludedCookies = flex.ExpandStringList(v.([]interface{})) + } + + if v, ok := m["excluded_cookies"]; ok && len(v.([]interface{})) > 0 { + CookieMatchPattern.ExcludedCookies = flex.ExpandStringList(v.([]interface{})) + } + + if v, ok := m["all"].([]interface{}); ok && len(v) > 0 { + CookieMatchPattern.All = &wafv2.All{} + } + + return CookieMatchPattern +} + func expandSingleHeader(l []interface{}) *wafv2.SingleHeader { if len(l) == 0 || l[0] == nil { return nil @@ -928,6 +974,10 @@ func flattenFieldToMatch(f *wafv2.FieldToMatch) interface{} { m["body"] = make([]map[string]interface{}, 1) } + if f.Cookies != nil { + m["cookies"] = flattenCookies(f.Cookies) + } + if f.Method != nil { m["method"] = make([]map[string]interface{}, 1) } @@ -978,6 +1028,34 @@ func flattenIPSetForwardedIPConfig(i *wafv2.IPSetForwardedIPConfig) interface{} return []interface{}{m} } +func flattenCookies(c *wafv2.Cookies) interface{} { + if c == nil { + return []interface{}{} + } + + m := map[string]interface{}{ + "match_scope": aws.StringValue(c.MatchScope), + "oversize_handling": aws.StringValue(c.OversizeHandling), + "match_pattern": flattenCookiesMatchPattern(c.MatchPattern), + } + + return []interface{}{m} +} + +func flattenCookiesMatchPattern(c *wafv2.CookieMatchPattern) interface{} { + if c == nil { + return []interface{}{} + } + + m := map[string]interface{}{ + "all": c.All, + "included_cookies": aws.StringValueSlice(c.IncludedCookies), + "excluded_cookies": aws.StringValueSlice(c.ExcludedCookies), + } + + return []interface{}{m} +} + func flattenSingleHeader(s *wafv2.SingleHeader) interface{} { if s == nil { return []interface{}{} diff --git a/internal/service/wafv2/rule_group_test.go b/internal/service/wafv2/rule_group_test.go index ac18f1ce855..135d441bd31 100644 --- a/internal/service/wafv2/rule_group_test.go +++ b/internal/service/wafv2/rule_group_test.go @@ -379,6 +379,7 @@ func TestAccWAFV2RuleGroup_ByteMatchStatement_fieldToMatch(t *testing.T) { "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.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.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", @@ -399,6 +400,7 @@ func TestAccWAFV2RuleGroup_ByteMatchStatement_fieldToMatch(t *testing.T) { "statement.0.byte_match_statement.0.field_to_match.#": "1", "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.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", @@ -407,6 +409,32 @@ func TestAccWAFV2RuleGroup_ByteMatchStatement_fieldToMatch(t *testing.T) { }), ), }, + { + Config: testAccRuleGroupConfig_byteMatchStatementFieldToMatchCookies(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.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.#": "1", + "statement.0.byte_match_statement.0.field_to_match.0.cookies.0.match_scope": "ALL", + "statement.0.byte_match_statement.0.field_to_match.0.cookies.0.oversize_handling": "NO_MATCH", + "statement.0.byte_match_statement.0.field_to_match.0.cookies.0.match_pattern.#": "1", + "statement.0.byte_match_statement.0.field_to_match.0.cookies.0.match_pattern.0.included_cookies.0": "test", + "statement.0.byte_match_statement.0.field_to_match.0.cookies.0.match_pattern.0.included_cookies.1": "cookie_test", + "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", + "statement.0.byte_match_statement.0.field_to_match.0.single_query_argument.#": "0", + "statement.0.byte_match_statement.0.field_to_match.0.uri_path.#": "0", + }), + ), + }, { Config: testAccRuleGroupConfig_byteMatchStatementFieldToMatchMethod(ruleGroupName), Check: resource.ComposeTestCheckFunc( @@ -419,6 +447,7 @@ func TestAccWAFV2RuleGroup_ByteMatchStatement_fieldToMatch(t *testing.T) { "statement.0.byte_match_statement.0.field_to_match.#": "1", "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.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", @@ -439,6 +468,7 @@ func TestAccWAFV2RuleGroup_ByteMatchStatement_fieldToMatch(t *testing.T) { "statement.0.byte_match_statement.0.field_to_match.#": "1", "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.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", @@ -459,6 +489,7 @@ func TestAccWAFV2RuleGroup_ByteMatchStatement_fieldToMatch(t *testing.T) { "statement.0.byte_match_statement.0.field_to_match.#": "1", "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.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", @@ -480,6 +511,7 @@ func TestAccWAFV2RuleGroup_ByteMatchStatement_fieldToMatch(t *testing.T) { "statement.0.byte_match_statement.0.field_to_match.#": "1", "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.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", @@ -501,6 +533,7 @@ func TestAccWAFV2RuleGroup_ByteMatchStatement_fieldToMatch(t *testing.T) { "statement.0.byte_match_statement.0.field_to_match.#": "1", "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.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", @@ -2528,6 +2561,59 @@ resource "aws_wafv2_rule_group" "test" { `, name) } +func testAccRuleGroupConfig_byteMatchStatementFieldToMatchCookies(name string) string { + return fmt.Sprintf(` +resource "aws_wafv2_rule_group" "test" { + capacity = 15 + name = "%s" + scope = "REGIONAL" + + rule { + name = "rule-1" + priority = 1 + + action { + allow {} + } + + statement { + byte_match_statement { + positional_constraint = "CONTAINS" + search_string = "word" + + field_to_match { + cookies { + match_pattern { + included_cookies = ["test", "cookie_test"] + } + match_scope = "ALL" + oversize_handling = "NO_MATCH" + } + } + + 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_byteMatchStatementFieldToMatchSingleHeader(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 72ea6d290df..88c21fc7cac 100644 --- a/internal/service/wafv2/schemas.go +++ b/internal/service/wafv2/schemas.go @@ -334,8 +334,46 @@ func fieldToMatchBaseSchema() *schema.Resource { Schema: map[string]*schema.Schema{ "all_query_arguments": emptySchema(), "body": emptySchema(), - "method": emptySchema(), - "query_string": emptySchema(), + "cookies": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "match_scope": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice(wafv2.MapMatchScope_Values(), false), + }, + "oversize_handling": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice(wafv2.OversizeHandling_Values(), false), + }, + "match_pattern": { + Type: schema.TypeList, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "all": emptySchema(), + "included_cookies": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "excluded_cookies": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + }, + }, + }, + }, + }, + "method": emptySchema(), + "query_string": emptySchema(), "single_header": { Type: schema.TypeList, Optional: true, diff --git a/internal/service/wafv2/web_acl_test.go b/internal/service/wafv2/web_acl_test.go index 262d9156f33..2c91a0f9979 100644 --- a/internal/service/wafv2/web_acl_test.go +++ b/internal/service/wafv2/web_acl_test.go @@ -98,12 +98,19 @@ func TestAccWAFV2WebACL_Update_rule(t *testing.T) { "visibility_config.0.metric_name": fmt.Sprintf("%s-metric-name-1", webACLName), "visibility_config.0.sampled_requests_enabled": "false", "statement.#": "1", - "statement.0.size_constraint_statement.#": "1", - "statement.0.size_constraint_statement.0.comparison_operator": "LT", - "statement.0.size_constraint_statement.0.field_to_match.#": "1", - "statement.0.size_constraint_statement.0.field_to_match.0.query_string.#": "1", - "statement.0.size_constraint_statement.0.size": "50", - "statement.0.size_constraint_statement.0.text_transformation.#": "2", + "statement.0.size_constraint_statement.#": "1", + "statement.0.size_constraint_statement.0.comparison_operator": "LT", + "statement.0.size_constraint_statement.0.field_to_match.#": "1", + "statement.0.size_constraint_statement.0.field_to_match.0.all_query_arguments.#": "0", + "statement.0.size_constraint_statement.0.field_to_match.0.body.#": "0", + "statement.0.size_constraint_statement.0.field_to_match.0.cookies.#": "0", + "statement.0.size_constraint_statement.0.field_to_match.0.method.#": "0", + "statement.0.size_constraint_statement.0.field_to_match.0.query_string.#": "1", + "statement.0.size_constraint_statement.0.field_to_match.0.single_header.#": "0", + "statement.0.size_constraint_statement.0.field_to_match.0.single_query_argument.#": "0", + "statement.0.size_constraint_statement.0.field_to_match.0.uri_path.#": "0", + "statement.0.size_constraint_statement.0.size": "50", + "statement.0.size_constraint_statement.0.text_transformation.#": "2", }), resource.TestCheckTypeSetElemNestedAttrs(resourceName, "rule.*.statement.0.size_constraint_statement.0.text_transformation.*", map[string]string{ "priority": "2", diff --git a/website/docs/r/wafv2_rule_group.html.markdown b/website/docs/r/wafv2_rule_group.html.markdown index 23b1c3d203f..69db541d5a4 100644 --- a/website/docs/r/wafv2_rule_group.html.markdown +++ b/website/docs/r/wafv2_rule_group.html.markdown @@ -492,11 +492,12 @@ 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`, `method`, `query_string`, `single_header`, `single_query_argument`, or `uri_path` can be specified. +~> **NOTE:** Only one of `all_query_arguments`, `body`, `cookies`, `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. * `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. @@ -540,6 +541,17 @@ The `single_query_argument` block supports the following arguments: * `name` - (Optional) The name of the query header to inspect. This setting must be provided as lower case characters. +### Cookies + +Inspect the cookies in the web request. You can specify the parts of the cookies to inspect and you can narrow the set of cookies to inspect by including or excluding specific keys. +This is used to indicate the web request component to inspect, in the [FieldToMatch](https://docs.aws.amazon.com/waf/latest/APIReference/API_FieldToMatch.html) specification. + +The `cookies` block supports the following arguments: + +* `match_pattern` - (Required) The filter to use to identify the subset of cookies to inspect in a web request. You must specify exactly one setting: either `all`, `included_cookies` or `excluded_cookies`. More details: [CookieMatchPattern](https://docs.aws.amazon.com/waf/latest/APIReference/API_CookieMatchPattern.html) +* `match_scope` - (Required) The parts of the cookies to inspect with the rule inspection criteria. If you specify All, AWS WAF inspects both keys and values. Valid values: `ALL`, `KEY`, `VALUE` +* `oversize_handling` - (Required) What AWS WAF should do if the cookies of the request are larger than AWS WAF can inspect. AWS WAF does not support inspecting the entire contents of request cookies when they exceed 8 KB (8192 bytes) or 200 total cookies. The underlying host service forwards a maximum of 200 cookies and at most 8 KB of cookie contents to AWS WAF. Valid values: `CONTINUE`, `MATCH`, `NO_MATCH` + ### Text Transformation The `text_transformation` block supports the following arguments: diff --git a/website/docs/r/wafv2_web_acl.html.markdown b/website/docs/r/wafv2_web_acl.html.markdown index 805dae3d8ba..860be6a9889 100644 --- a/website/docs/r/wafv2_web_acl.html.markdown +++ b/website/docs/r/wafv2_web_acl.html.markdown @@ -545,11 +545,12 @@ 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`, `method`, `query_string`, `single_header`, `single_query_argument`, or `uri_path` can be specified. +~> **NOTE:** Only one of `all_query_arguments`, `body`, `cookies`, `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. * `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. @@ -593,6 +594,17 @@ The `single_query_argument` block supports the following arguments: * `name` - (Optional) Name of the query header to inspect. This setting must be provided as lower case characters. +### Cookies + +Inspect the cookies in the web request. You can specify the parts of the cookies to inspect and you can narrow the set of cookies to inspect by including or excluding specific keys. +This is used to indicate the web request component to inspect, in the [FieldToMatch](https://docs.aws.amazon.com/waf/latest/APIReference/API_FieldToMatch.html) specification. + +The `cookies` block supports the following arguments: + +* `match_pattern` - (Required) The filter to use to identify the subset of cookies to inspect in a web request. You must specify exactly one setting: either `all`, `included_cookies` or `excluded_cookies`. More details: [CookieMatchPattern](https://docs.aws.amazon.com/waf/latest/APIReference/API_CookieMatchPattern.html) +* `match_scope` - (Required) The parts of the cookies to inspect with the rule inspection criteria. If you specify All, AWS WAF inspects both keys and values. Valid values: `ALL`, `KEY`, `VALUE` +* `oversize_handling` - (Required) What AWS WAF should do if the cookies of the request are larger than AWS WAF can inspect. AWS WAF does not support inspecting the entire contents of request cookies when they exceed 8 KB (8192 bytes) or 200 total cookies. The underlying host service forwards a maximum of 200 cookies and at most 8 KB of cookie contents to AWS WAF. Valid values: `CONTINUE`, `MATCH`, `NO_MATCH` + ### Text Transformation The `text_transformation` block supports the following arguments: