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

[Enhancement] Add support for JA3 and JA4 Fingerprint in WebACL rate limits and JA4 support for matching #41719

Merged
merged 12 commits into from
Mar 10, 2025
15 changes: 15 additions & 0 deletions .changelog/41719.txt
Original file line number Diff line number Diff line change
@@ -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
```
88 changes: 88 additions & 0 deletions internal/service/wafv2/flex.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{}))
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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{}))
}
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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{}{}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
Expand Down
26 changes: 23 additions & 3 deletions internal/service/wafv2/schemas.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -860,7 +878,7 @@ func cookiesMatchPatternSchema() *schema.Schema {
}
}

func ja3fingerprintSchema() *schema.Schema {
func jaFingerprintSchema() *schema.Schema {
return &schema.Schema{
Type: schema.TypeList,
Optional: true,
Expand Down Expand Up @@ -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,
Expand Down
Loading
Loading