diff --git a/.changelog/41719.txt b/.changelog/41719.txt new file mode 100644 index 00000000000..5262ee376ed --- /dev/null +++ b/.changelog/41719.txt @@ -0,0 +1,15 @@ +```release-note:enhancement +resource/aws_wafv2_web_acl: Add `ja3_fingerprint` and `ja4_fingerprint` to `custom_key` configuration blocks +``` + +```release-note:enhancement +resource/aws_wafv2_rule_group: Add `ja3_fingerprint` and `ja4_fingerprint` to `custom_key` configuration blocks +``` + +```release-note:enhancement +resource/aws_wafv2_web_acl: Add `ja4_fingerprint` to `field_to_match` configuration blocks +``` + +```release-note:enhancement +resource/aws_wafv2_rule_group: Add `ja4_fingerprint` to `field_to_match` configuration blocks +``` \ No newline at end of file diff --git a/internal/service/wafv2/flex.go b/internal/service/wafv2/flex.go index 033a87783b5..e103ac6910f 100644 --- a/internal/service/wafv2/flex.go +++ b/internal/service/wafv2/flex.go @@ -586,6 +586,10 @@ func expandFieldToMatch(l []interface{}) *awstypes.FieldToMatch { f.JA3Fingerprint = expandJA3Fingerprint(v.([]interface{})) } + if v, ok := m["ja4_fingerprint"]; ok && len(v.([]interface{})) > 0 { + f.JA4Fingerprint = expandJA4Fingerprint(v.([]interface{})) + } + if v, ok := m["single_query_argument"]; ok && len(v.([]interface{})) > 0 { f.SingleQueryArgument = expandSingleQueryArgument(m["single_query_argument"].([]interface{})) } @@ -716,6 +720,20 @@ func expandJA3Fingerprint(l []interface{}) *awstypes.JA3Fingerprint { return ja3fingerprint } +func expandJA4Fingerprint(l []interface{}) *awstypes.JA4Fingerprint { + if len(l) == 0 || l[0] == nil { + return nil + } + + m := l[0].(map[string]interface{}) + + ja4fingerprint := &awstypes.JA4Fingerprint{ + FallbackBehavior: awstypes.FallbackBehavior(m["fallback_behavior"].(string)), + } + + return ja4fingerprint +} + func expandJSONMatchPattern(l []interface{}) *awstypes.JsonMatchPattern { if len(l) == 0 || l[0] == nil { return nil @@ -1561,6 +1579,26 @@ func expandRateLimitHeader(l []interface{}) *awstypes.RateLimitHeader { } } +func expandRateLimitJa3Fingerprint(l []interface{}) *awstypes.RateLimitJA3Fingerprint { + if len(l) == 0 || l[0] == nil { + return nil + } + m := l[0].(map[string]interface{}) + return &awstypes.RateLimitJA3Fingerprint{ + FallbackBehavior: awstypes.FallbackBehavior(m["fallback_behavior"].(string)), + } +} + +func expandRateLimitJa4Fingerprint(l []interface{}) *awstypes.RateLimitJA4Fingerprint { + if len(l) == 0 || l[0] == nil { + return nil + } + m := l[0].(map[string]interface{}) + return &awstypes.RateLimitJA4Fingerprint{ + FallbackBehavior: awstypes.FallbackBehavior(m["fallback_behavior"].(string)), + } +} + func expandRateLimitLabelNamespace(l []interface{}) *awstypes.RateLimitLabelNamespace { if len(l) == 0 || l[0] == nil { return nil @@ -1626,6 +1664,12 @@ func expandRateBasedStatementCustomKeys(l []interface{}) []awstypes.RateBasedSta if v, ok := m["ip"]; ok && len(v.([]interface{})) > 0 { r.IP = &awstypes.RateLimitIP{} } + if v, ok := m["ja3_fingerprint"]; ok && len(v.([]interface{})) > 0 { + r.JA3Fingerprint = expandRateLimitJa3Fingerprint(v.([]interface{})) + } + if v, ok := m["ja4_fingerprint"]; ok && len(v.([]interface{})) > 0 { + r.JA4Fingerprint = expandRateLimitJa4Fingerprint(v.([]interface{})) + } if v, ok := m["label_namespace"]; ok { r.LabelNamespace = expandRateLimitLabelNamespace(v.([]interface{})) } @@ -2124,6 +2168,10 @@ func flattenFieldToMatch(f *awstypes.FieldToMatch) interface{} { m["ja3_fingerprint"] = flattenJA3Fingerprint(f.JA3Fingerprint) } + if f.JA4Fingerprint != nil { + m["ja4_fingerprint"] = flattenJA4Fingerprint(f.JA4Fingerprint) + } + if f.JsonBody != nil { m["json_body"] = flattenJSONBody(f.JsonBody) } @@ -2221,6 +2269,18 @@ func flattenJA3Fingerprint(j *awstypes.JA3Fingerprint) interface{} { return []interface{}{m} } +func flattenJA4Fingerprint(j *awstypes.JA4Fingerprint) interface{} { + if j == nil { + return []interface{}{} + } + + m := map[string]interface{}{ + "fallback_behavior": j.FallbackBehavior, + } + + return []interface{}{m} +} + func flattenJSONBody(b *awstypes.JsonBody) interface{} { if b == nil { return []interface{}{} @@ -2949,6 +3009,28 @@ func flattenRateLimitHeader(apiObject *awstypes.RateLimitHeader) []interface{} { } } +func flattenRateLimitJa3Fingerprint(apiObject *awstypes.RateLimitJA3Fingerprint) []interface{} { + if apiObject == nil { + return nil + } + return []interface{}{ + map[string]interface{}{ + "fallback_behavior": apiObject.FallbackBehavior, + }, + } +} + +func flattenRateLimitJa4Fingerprint(apiObject *awstypes.RateLimitJA4Fingerprint) []interface{} { + if apiObject == nil { + return nil + } + return []interface{}{ + map[string]interface{}{ + "fallback_behavior": apiObject.FallbackBehavior, + }, + } +} + func flattenRateLimitLabelNamespace(apiObject *awstypes.RateLimitLabelNamespace) []interface{} { if apiObject == nil { return nil @@ -3024,6 +3106,12 @@ func flattenRateBasedStatementCustomKeys(apiObject []awstypes.RateBasedStatement map[string]interface{}{}, } } + if o.JA3Fingerprint != nil { + tfMap["ja3_fingerprint"] = flattenRateLimitJa3Fingerprint(o.JA3Fingerprint) + } + if o.JA4Fingerprint != nil { + tfMap["ja4_fingerprint"] = flattenRateLimitJa4Fingerprint(o.JA4Fingerprint) + } if o.LabelNamespace != nil { tfMap["label_namespace"] = flattenRateLimitLabelNamespace(o.LabelNamespace) } diff --git a/internal/service/wafv2/schemas.go b/internal/service/wafv2/schemas.go index 8ac8b8d5342..01769b9a1f8 100644 --- a/internal/service/wafv2/schemas.go +++ b/internal/service/wafv2/schemas.go @@ -379,7 +379,8 @@ var fieldToMatchBaseSchema = sync.OnceValue(func() *schema.Resource { "cookies": cookiesSchema(), "header_order": headerOrderSchema(), "headers": headersSchema(), - "ja3_fingerprint": ja3fingerprintSchema(), + "ja3_fingerprint": jaFingerprintSchema(), + "ja4_fingerprint": jaFingerprintSchema(), "json_body": jsonBodySchema(), "method": emptySchema(), "query_string": emptySchema(), @@ -504,6 +505,23 @@ var forwardedIPConfigSchema = sync.OnceValue(func() *schema.Schema { } }) +var rateLimitJAFingerprintConfigSchema = sync.OnceValue(func() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "fallback_behavior": { + Type: schema.TypeString, + Required: true, + ValidateDiagFunc: enum.Validate[awstypes.FallbackBehavior](), + }, + }, + }, + } +}) + var textTransformationSchema = sync.OnceValue(func() *schema.Schema { return &schema.Schema{ Type: schema.TypeSet, @@ -860,7 +878,7 @@ func cookiesMatchPatternSchema() *schema.Schema { } } -func ja3fingerprintSchema() *schema.Schema { +func jaFingerprintSchema() *schema.Schema { return &schema.Schema{ Type: schema.TypeList, Optional: true, @@ -1078,7 +1096,9 @@ func rateBasedStatementSchema(level int) *schema.Schema { }, }, }, - "ip": emptySchema(), + "ip": emptySchema(), + "ja3_fingerprint": rateLimitJAFingerprintConfigSchema(), + "ja4_fingerprint": rateLimitJAFingerprintConfigSchema(), "label_namespace": { Type: schema.TypeList, Optional: true, diff --git a/internal/service/wafv2/web_acl_test.go b/internal/service/wafv2/web_acl_test.go index 89af12aceb2..f29969f3abc 100644 --- a/internal/service/wafv2/web_acl_test.go +++ b/internal/service/wafv2/web_acl_test.go @@ -1233,6 +1233,54 @@ func TestAccWAFV2WebACL_ByteMatchStatement_ja3fingerprint(t *testing.T) { }) } +func TestAccWAFV2WebACL_ByteMatchStatement_ja4fingerprint(t *testing.T) { + ctx := acctest.Context(t) + var v awstypes.WebACL + webACLName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_wafv2_web_acl.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheckScopeRegional(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.WAFV2ServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckWebACLDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccWebACLConfig_byteMatchStatementJA4Fingerprint(webACLName, string(awstypes.FallbackBehaviorMatch)), + Check: resource.ComposeTestCheckFunc( + testAccCheckWebACLExists(ctx, resourceName, &v), + acctest.MatchResourceAttrRegionalARN(ctx, resourceName, names.AttrARN, "wafv2", regexache.MustCompile(`regional/webacl/.+$`)), + resource.TestCheckResourceAttr(resourceName, names.AttrName, webACLName), + resource.TestCheckResourceAttr(resourceName, acctest.CtRulePound, "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "rule.*", map[string]string{ + "statement.0.byte_match_statement.0.field_to_match.0.ja4_fingerprint.#": "1", + "statement.0.byte_match_statement.0.field_to_match.0.ja4_fingerprint.0.fallback_behavior": "MATCH", + }), + ), + }, + { + Config: testAccWebACLConfig_byteMatchStatementJA4Fingerprint(webACLName, string(awstypes.FallbackBehaviorNoMatch)), + Check: resource.ComposeTestCheckFunc( + testAccCheckWebACLExists(ctx, resourceName, &v), + acctest.MatchResourceAttrRegionalARN(ctx, resourceName, names.AttrARN, "wafv2", regexache.MustCompile(`regional/webacl/.+$`)), + resource.TestCheckResourceAttr(resourceName, names.AttrName, webACLName), + resource.TestCheckResourceAttr(resourceName, acctest.CtRulePound, "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "rule.*", map[string]string{ + "statement.0.byte_match_statement.0.field_to_match.0.ja4_fingerprint.#": "1", + "statement.0.byte_match_statement.0.field_to_match.0.ja4_fingerprint.0.fallback_behavior": "NO_MATCH", + }), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: testAccWebACLImportStateIdFunc(resourceName), + }, + }, + }) +} + func TestAccWAFV2WebACL_ByteMatchStatement_jsonBody(t *testing.T) { ctx := acctest.Context(t) var v awstypes.WebACL @@ -2062,6 +2110,122 @@ func TestAccWAFV2WebACL_RateBased_customKeys(t *testing.T) { }), ), }, + { + Config: testAccWebACLConfig_rateBasedStatement_customKeysBasic(webACLName, "cookie", "testcookie"), + Check: resource.ComposeTestCheckFunc( + testAccCheckWebACLExists(ctx, resourceName, &v), + acctest.MatchResourceAttrRegionalARN(ctx, resourceName, names.AttrARN, "wafv2", regexache.MustCompile(`regional/webacl/.+$`)), + resource.TestCheckResourceAttr(resourceName, names.AttrName, webACLName), + resource.TestCheckResourceAttr(resourceName, acctest.CtRulePound, "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "rule.*", map[string]string{ + "statement.#": "1", + "statement.0.rate_based_statement.#": "1", + "statement.0.rate_based_statement.0.custom_key.#": "1", + "statement.0.rate_based_statement.0.aggregate_key_type": "CUSTOM_KEYS", + "statement.0.rate_based_statement.0.evaluation_window_sec": "300", + "statement.0.rate_based_statement.0.forwarded_ip_config.#": "0", + "statement.0.rate_based_statement.0.limit": "50000", + "statement.0.rate_based_statement.0.scope_down_statement.#": "0", + "statement.0.rate_based_statement.0.custom_key.0.cookie.#": "1", + "statement.0.rate_based_statement.0.custom_key.0.forwarded_ip.#": "0", + "statement.0.rate_based_statement.0.custom_key.0.http_method.#": "0", + "statement.0.rate_based_statement.0.custom_key.0.header.#": "0", + "statement.0.rate_based_statement.0.custom_key.0.ip.#": "0", + "statement.0.rate_based_statement.0.custom_key.0.label_namespace.#": "0", + "statement.0.rate_based_statement.0.custom_key.0.query_argument.#": "0", + "statement.0.rate_based_statement.0.custom_key.0.query_string.#": "0", + "statement.0.rate_based_statement.0.custom_key.0.uri_path.#": "0", + "statement.0.rate_based_statement.0.custom_key.0.cookie.0.text_transformation.#": "1", + }), + ), + }, + { + Config: testAccWebACLConfig_rateBasedStatement_customKeysJa3Fingerprint(webACLName), + Check: resource.ComposeTestCheckFunc( + testAccCheckWebACLExists(ctx, resourceName, &v), + acctest.MatchResourceAttrRegionalARN(ctx, resourceName, names.AttrARN, "wafv2", regexache.MustCompile(`regional/webacl/.+$`)), + resource.TestCheckResourceAttr(resourceName, names.AttrName, webACLName), + resource.TestCheckResourceAttr(resourceName, acctest.CtRulePound, "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "rule.*", map[string]string{ + "statement.#": "1", + "statement.0.rate_based_statement.#": "1", + "statement.0.rate_based_statement.0.custom_key.#": "2", + "statement.0.rate_based_statement.0.aggregate_key_type": "CUSTOM_KEYS", + "statement.0.rate_based_statement.0.evaluation_window_sec": "300", + "statement.0.rate_based_statement.0.limit": "50000", + "statement.0.rate_based_statement.0.scope_down_statement.#": "0", + "statement.0.rate_based_statement.0.custom_key.0.cookie.#": "1", + "statement.0.rate_based_statement.0.custom_key.0.forwarded_ip.#": "0", + "statement.0.rate_based_statement.0.custom_key.0.http_method.#": "0", + "statement.0.rate_based_statement.0.custom_key.0.header.#": "0", + "statement.0.rate_based_statement.0.custom_key.0.ip.#": "0", + "statement.0.rate_based_statement.0.custom_key.0.label_namespace.#": "0", + "statement.0.rate_based_statement.0.custom_key.0.query_argument.#": "0", + "statement.0.rate_based_statement.0.custom_key.0.query_string.#": "0", + "statement.0.rate_based_statement.0.custom_key.0.uri_path.#": "0", + "statement.0.rate_based_statement.0.custom_key.1.ja3_fingerprint.#": "1", + "statement.0.rate_based_statement.0.custom_key.1.ja3_fingerprint.0.fallback_behavior": "NO_MATCH", + }), + ), + }, + { + Config: testAccWebACLConfig_rateBasedStatement_customKeysBasic(webACLName, "cookie", "testcookie"), + Check: resource.ComposeTestCheckFunc( + testAccCheckWebACLExists(ctx, resourceName, &v), + acctest.MatchResourceAttrRegionalARN(ctx, resourceName, names.AttrARN, "wafv2", regexache.MustCompile(`regional/webacl/.+$`)), + resource.TestCheckResourceAttr(resourceName, names.AttrName, webACLName), + resource.TestCheckResourceAttr(resourceName, acctest.CtRulePound, "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "rule.*", map[string]string{ + "statement.#": "1", + "statement.0.rate_based_statement.#": "1", + "statement.0.rate_based_statement.0.custom_key.#": "1", + "statement.0.rate_based_statement.0.aggregate_key_type": "CUSTOM_KEYS", + "statement.0.rate_based_statement.0.evaluation_window_sec": "300", + "statement.0.rate_based_statement.0.forwarded_ip_config.#": "0", + "statement.0.rate_based_statement.0.limit": "50000", + "statement.0.rate_based_statement.0.scope_down_statement.#": "0", + "statement.0.rate_based_statement.0.custom_key.0.cookie.#": "1", + "statement.0.rate_based_statement.0.custom_key.0.forwarded_ip.#": "0", + "statement.0.rate_based_statement.0.custom_key.0.http_method.#": "0", + "statement.0.rate_based_statement.0.custom_key.0.header.#": "0", + "statement.0.rate_based_statement.0.custom_key.0.ip.#": "0", + "statement.0.rate_based_statement.0.custom_key.0.label_namespace.#": "0", + "statement.0.rate_based_statement.0.custom_key.0.query_argument.#": "0", + "statement.0.rate_based_statement.0.custom_key.0.query_string.#": "0", + "statement.0.rate_based_statement.0.custom_key.0.uri_path.#": "0", + "statement.0.rate_based_statement.0.custom_key.0.cookie.0.text_transformation.#": "1", + }), + ), + }, + { + Config: testAccWebACLConfig_rateBasedStatement_customKeysJa4Fingerprint(webACLName), + Check: resource.ComposeTestCheckFunc( + testAccCheckWebACLExists(ctx, resourceName, &v), + acctest.MatchResourceAttrRegionalARN(ctx, resourceName, names.AttrARN, "wafv2", regexache.MustCompile(`regional/webacl/.+$`)), + resource.TestCheckResourceAttr(resourceName, names.AttrName, webACLName), + resource.TestCheckResourceAttr(resourceName, acctest.CtRulePound, "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "rule.*", map[string]string{ + "statement.#": "1", + "statement.0.rate_based_statement.#": "1", + "statement.0.rate_based_statement.0.custom_key.#": "2", + "statement.0.rate_based_statement.0.aggregate_key_type": "CUSTOM_KEYS", + "statement.0.rate_based_statement.0.evaluation_window_sec": "300", + "statement.0.rate_based_statement.0.limit": "50000", + "statement.0.rate_based_statement.0.scope_down_statement.#": "0", + "statement.0.rate_based_statement.0.custom_key.0.cookie.#": "1", + "statement.0.rate_based_statement.0.custom_key.0.forwarded_ip.#": "0", + "statement.0.rate_based_statement.0.custom_key.0.http_method.#": "0", + "statement.0.rate_based_statement.0.custom_key.0.header.#": "0", + "statement.0.rate_based_statement.0.custom_key.0.ip.#": "0", + "statement.0.rate_based_statement.0.custom_key.0.label_namespace.#": "0", + "statement.0.rate_based_statement.0.custom_key.0.query_argument.#": "0", + "statement.0.rate_based_statement.0.custom_key.0.query_string.#": "0", + "statement.0.rate_based_statement.0.custom_key.0.uri_path.#": "0", + "statement.0.rate_based_statement.0.custom_key.1.ja4_fingerprint.#": "1", + "statement.0.rate_based_statement.0.custom_key.1.ja4_fingerprint.0.fallback_behavior": "NO_MATCH", + }), + ), + }, { ResourceName: resourceName, ImportState: true, @@ -3425,6 +3589,57 @@ resource "aws_wafv2_web_acl" "test" { `, rName, fallbackBehavior) } +func testAccWebACLConfig_byteMatchStatementJA4Fingerprint(rName, fallbackBehavior string) string { + return fmt.Sprintf(` +resource "aws_wafv2_web_acl" "test" { + name = %[1]q + description = %[1]q + scope = "REGIONAL" + + default_action { + allow {} + } + + rule { + name = "rule-1" + priority = 1 + + action { + count {} + } + + statement { + byte_match_statement { + field_to_match { + ja4_fingerprint { + fallback_behavior = %[2]q + } + } + positional_constraint = "EXACTLY" + search_string = "abcdef1234567890abcdef1234567890" + 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 + } +} +`, rName, fallbackBehavior) +} + func testAccWebACLConfig_byteMatchStatementJSONBody(rName, matchScope, invalidFallbackBehavior, oversizeHandling, matchPattern string) string { return fmt.Sprintf(` resource "aws_wafv2_web_acl" "test" { @@ -5392,6 +5607,134 @@ resource "aws_wafv2_web_acl" "test" { `, rName) } +func testAccWebACLConfig_rateBasedStatement_customKeysJa3Fingerprint(rName string) string { + return fmt.Sprintf(` +resource "aws_wafv2_web_acl" "test" { + name = %[1]q + description = %[1]q + scope = "REGIONAL" + + default_action { + allow {} + } + + rule { + name = "rule-1" + priority = 1 + + action { + count {} + } + + statement { + rate_based_statement { + aggregate_key_type = "CUSTOM_KEYS" + limit = 50000 + + custom_key { + cookie { + name = "cookie-name" + + text_transformation { + type = "NONE" + priority = 0 + } + } + } + + custom_key { + ja3_fingerprint { + fallback_behavior = "NO_MATCH" + } + } + } + } + + visibility_config { + cloudwatch_metrics_enabled = false + metric_name = "friendly-rule-metric-name" + sampled_requests_enabled = false + } + } + + tags = { + Tag1 = "Value1" + Tag2 = "Value2" + } + + visibility_config { + cloudwatch_metrics_enabled = false + metric_name = "friendly-metric-name" + sampled_requests_enabled = false + } +} +`, rName) +} + +func testAccWebACLConfig_rateBasedStatement_customKeysJa4Fingerprint(rName string) string { + return fmt.Sprintf(` +resource "aws_wafv2_web_acl" "test" { + name = %[1]q + description = %[1]q + scope = "REGIONAL" + + default_action { + allow {} + } + + rule { + name = "rule-1" + priority = 1 + + action { + count {} + } + + statement { + rate_based_statement { + aggregate_key_type = "CUSTOM_KEYS" + limit = 50000 + + custom_key { + cookie { + name = "cookie-name" + + text_transformation { + type = "NONE" + priority = 0 + } + } + } + + custom_key { + ja4_fingerprint { + fallback_behavior = "NO_MATCH" + } + } + } + } + + visibility_config { + cloudwatch_metrics_enabled = false + metric_name = "friendly-rule-metric-name" + sampled_requests_enabled = false + } + } + + tags = { + Tag1 = "Value1" + Tag2 = "Value2" + } + + visibility_config { + cloudwatch_metrics_enabled = false + metric_name = "friendly-metric-name" + sampled_requests_enabled = false + } +} +`, rName) +} + func testAccWebACLConfig_rateBasedStatementUpdate(rName 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 c7188276fa3..53436c128e8 100644 --- a/website/docs/r/wafv2_rule_group.html.markdown +++ b/website/docs/r/wafv2_rule_group.html.markdown @@ -588,6 +588,8 @@ An empty configuration block `{}` should be used when specifying `all_query_argu * `cookies` - (Optional) Inspect the cookies in the web request. See [Cookies](#cookies) below for details. * `header_order` - (Optional) Inspect the request headers. See [Header Order](#header-order) below for details. * `headers` - (Optional) Inspect the request headers. See [Headers](#headers) below for details. +* `ja3_fingerprint` - (Optional) Inspect the JA3 fingerprint. See [`ja3_fingerprint`](#ja3_fingerprint-block) below for details. +* `ja4_fingerprint` - (Optional) Inspect the JA3 fingerprint. See [`ja4_fingerprint`](#ja3_fingerprint-block) below for details. * `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. @@ -637,6 +639,18 @@ The `headers` block supports the following arguments: * `match_scope` - (Required) The parts of the headers to inspect with the rule inspection criteria. If you specify `All`, AWS WAF inspects both keys and values. Valid values include the following: `ALL`, `Key`, `Value`. * `oversize_handling` - (Required) Oversize handling tells AWS WAF what to do with a web request when the request component that the rule inspects is over the limits. Valid values include the following: `CONTINUE`, `MATCH`, `NO_MATCH`. See the AWS [documentation](https://docs.aws.amazon.com/waf/latest/developerguide/waf-rule-statement-oversize-handling.html) for more information. +### `ja3_fingerprint` Block + +The `ja3_fingerprint` block supports the following arguments: + +* `fallback_behavior` - (Required) The match status to assign to the web request if the request doesn't have a JA3 fingerprint. Valid values include: `MATCH` or `NO_MATCH`. + +### `ja4_fingerprint` Block + +The `ja4_fingerprint` block supports the following arguments: + +* `fallback_behavior` - (Required) The match status to assign to the web request if the request doesn't have a JA4 fingerprint. Valid values include: `MATCH` or `NO_MATCH`. + ### JSON Body The `json_body` block supports the following arguments: @@ -719,6 +733,8 @@ The `custom_key` block supports the following arguments: * `http_method` - (Optional) Use the request's HTTP method as an aggregate key. See [RateLimit `http_method`](#ratelimit-http_method-block) below for details. * `header` - (Optional) Use the value of a header in the request as an aggregate key. See [RateLimit `header`](#ratelimit-header-block) below for details. * `ip` - (Optional) Use the request's originating IP address as an aggregate key. See [`RateLimit ip`](#ratelimit-ip-block) below for details. +* `ja3_fingerprint` - (Optional) Use the JA3 fingerprint in the request as an aggregate key. See [`RateLimit ip`](#ratelimit-ja3_fingerprint-block) below for details. +* `ja4_fingerprint` - (Optional) Use the JA3 fingerprint in the request as an aggregate key. See [`RateLimit ip`](#ratelimit-ja4_fingerprint-block) below for details. * `label_namespace` - (Optional) Use the specified label namespace as an aggregate key. See [RateLimit `label_namespace`](#ratelimit-label_namespace-block) below for details. * `query_argument` - (Optional) Use the specified query argument as an aggregate key. See [RateLimit `query_argument`](#ratelimit-query_argument-block) below for details. * `query_string` - (Optional) Use the request's query string as an aggregate key. See [RateLimit `query_string`](#ratelimit-query_string-block) below for details. @@ -760,6 +776,22 @@ Use the request's originating IP address as an aggregate key. Each distinct IP a The `ip` block is configured as an empty block `{}`. +### RateLimit `ja3_fingerprint` Block + +Use the JA3 fingerprint in the request as an aggregate key. Each distinct JA3 fingerprint contributes to the aggregation instance. You can use this key type once. + +The `ja3_fingerprint` block supports the following arguments: + +* `fallback_behavior` - (Required) - Match status to assign to the web request if there is insufficient TSL Client Hello information to compute the JA3 fingerprint. Valid values include: `MATCH` or `NO_MATCH`. + +### RateLimit `ja4_fingerprint` Block + +Use the JA3 fingerprint in the request as an aggregate key. Each distinct JA3 fingerprint contributes to the aggregation instance. You can use this key type once. + +The `ja4_fingerprint` block supports the following arguments: + +* `fallback_behavior` - (Required) - Match status to assign to the web request if there is insufficient TSL Client Hello information to compute the JA4 fingerprint. Valid values include: `MATCH` or `NO_MATCH`. + ### RateLimit `label_namespace` Block Use the specified label namespace as an aggregate key. Each distinct fully qualified label name that has the specified label namespace contributes to the aggregation instance. If you use just one label namespace as your custom key, then each label name fully defines an aggregation instance. This uses only labels that have been added to the request by rules that are evaluated before this rate-based rule in the web ACL. For information about label namespaces and names, see Label syntax and naming requirements (https://docs.aws.amazon.com/waf/latest/developerguide/waf-rule-label-requirements.html) in the WAF Developer Guide. diff --git a/website/docs/r/wafv2_web_acl.html.markdown b/website/docs/r/wafv2_web_acl.html.markdown index 9ed7ffe93e8..712539a8682 100644 --- a/website/docs/r/wafv2_web_acl.html.markdown +++ b/website/docs/r/wafv2_web_acl.html.markdown @@ -881,6 +881,7 @@ The `field_to_match` block supports the following arguments: * `header_order` - (Optional) Inspect a string containing the list of the request's header names, ordered as they appear in the web request that AWS WAF receives for inspection. See [`header_order`](#header_order-block) below for details. * `headers` - (Optional) Inspect the request headers. See [`headers`](#headers-block) below for details. * `ja3_fingerprint` - (Optional) Inspect the JA3 fingerprint. See [`ja3_fingerprint`](#ja3_fingerprint-block) below for details. +* `ja4_fingerprint` - (Optional) Inspect the JA3 fingerprint. See [`ja4_fingerprint`](#ja3_fingerprint-block) below for details. * `json_body` - (Optional) Inspect the request body as JSON. See [`json_body`](#json_body-block) 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. @@ -934,6 +935,12 @@ The `ja3_fingerprint` block supports the following arguments: * `fallback_behavior` - (Required) The match status to assign to the web request if the request doesn't have a JA3 fingerprint. Valid values include: `MATCH` or `NO_MATCH`. +### `ja4_fingerprint` Block + +The `ja4_fingerprint` block supports the following arguments: + +* `fallback_behavior` - (Required) The match status to assign to the web request if the request doesn't have a JA4 fingerprint. Valid values include: `MATCH` or `NO_MATCH`. + ### `json_body` Block The `json_body` block supports the following arguments: @@ -1059,6 +1066,8 @@ The `custom_key` block supports the following arguments: * `http_method` - (Optional) Use the request's HTTP method as an aggregate key. See [RateLimit `http_method`](#ratelimit-http_method-block) below for details. * `header` - (Optional) Use the value of a header in the request as an aggregate key. See [RateLimit `header`](#ratelimit-header-block) below for details. * `ip` - (Optional) Use the request's originating IP address as an aggregate key. See [`RateLimit ip`](#ratelimit-ip-block) below for details. +* `ja3_fingerprint` - (Optional) Use the JA3 fingerprint in the request as an aggregate key. See [`RateLimit ip`](#ratelimit-ja3_fingerprint-block) below for details. +* `ja4_fingerprint` - (Optional) Use the JA3 fingerprint in the request as an aggregate key. See [`RateLimit ip`](#ratelimit-ja4_fingerprint-block) below for details. * `label_namespace` - (Optional) Use the specified label namespace as an aggregate key. See [RateLimit `label_namespace`](#ratelimit-label_namespace-block) below for details. * `query_argument` - (Optional) Use the specified query argument as an aggregate key. See [RateLimit `query_argument`](#ratelimit-query_argument-block) below for details. * `query_string` - (Optional) Use the request's query string as an aggregate key. See [RateLimit `query_string`](#ratelimit-query_string-block) below for details. @@ -1100,6 +1109,22 @@ Use the request's originating IP address as an aggregate key. Each distinct IP a The `ip` block is configured as an empty block `{}`. +### RateLimit `ja3_fingerprint` Block + +Use the JA3 fingerprint in the request as an aggregate key. Each distinct JA3 fingerprint contributes to the aggregation instance. You can use this key type once. + +The `ja3_fingerprint` block supports the following arguments: + +* `fallback_behavior` - (Required) - Match status to assign to the web request if there is insufficient TSL Client Hello information to compute the JA3 fingerprint. Valid values include: `MATCH` or `NO_MATCH`. + +### RateLimit `ja4_fingerprint` Block + +Use the JA3 fingerprint in the request as an aggregate key. Each distinct JA3 fingerprint contributes to the aggregation instance. You can use this key type once. + +The `ja4_fingerprint` block supports the following arguments: + +* `fallback_behavior` - (Required) - Match status to assign to the web request if there is insufficient TSL Client Hello information to compute the JA4 fingerprint. Valid values include: `MATCH` or `NO_MATCH`. + ### RateLimit `label_namespace` Block Use the specified label namespace as an aggregate key. Each distinct fully qualified label name that has the specified label namespace contributes to the aggregation instance. If you use just one label namespace as your custom key, then each label name fully defines an aggregation instance. This uses only labels that have been added to the request by rules that are evaluated before this rate-based rule in the web ACL. For information about label namespaces and names, see Label syntax and naming requirements (https://docs.aws.amazon.com/waf/latest/developerguide/waf-rule-label-requirements.html) in the WAF Developer Guide.