diff --git a/.changelog/29608.txt b/.changelog/29608.txt new file mode 100644 index 000000000000..86867c98b118 --- /dev/null +++ b/.changelog/29608.txt @@ -0,0 +1,7 @@ +```release-note:enhancement +resource/aws_wafv2_rule_group: Add `rule.captcha_config` argument +``` + +```release-note:enhancement +resource/aws_wafv2_web_acl: Add `captcha_config` and `rule.captcha_config` arguments +``` \ No newline at end of file diff --git a/internal/service/wafv2/flex.go b/internal/service/wafv2/flex.go index 8772ef984a2d..c43c6c49246f 100644 --- a/internal/service/wafv2/flex.go +++ b/internal/service/wafv2/flex.go @@ -30,9 +30,10 @@ func expandRule(m map[string]interface{}) *wafv2.Rule { } rule := &wafv2.Rule{ + Action: expandRuleAction(m["action"].([]interface{})), + CaptchaConfig: expandCaptchaConfig(m["captcha_config"].([]interface{})), Name: aws.String(m["name"].(string)), Priority: aws.Int64(int64(m["priority"].(int))), - Action: expandRuleAction(m["action"].([]interface{})), Statement: expandRuleGroupRootStatement(m["statement"].([]interface{})), VisibilityConfig: expandVisibilityConfig(m["visibility_config"].([]interface{})), } @@ -44,6 +45,32 @@ func expandRule(m map[string]interface{}) *wafv2.Rule { return rule } +func expandCaptchaConfig(l []interface{}) *wafv2.CaptchaConfig { + configuration := &wafv2.CaptchaConfig{} + + if len(l) == 0 || l[0] == nil { + return configuration + } + + m := l[0].(map[string]interface{}) + if v, ok := m["immunity_time_property"]; ok { + inner := v.([]interface{}) + if len(inner) == 0 || inner[0] == nil { + return configuration + } + + m = inner[0].(map[string]interface{}) + + if v, ok := m["immunity_time"]; ok { + configuration.ImmunityTimeProperty = &wafv2.ImmunityTimeProperty{ + ImmunityTime: aws.Int64(int64(v.(int))), + } + } + } + + return configuration +} + func expandRuleLabels(l []interface{}) []*wafv2.Label { if len(l) == 0 || l[0] == nil { return nil @@ -848,10 +875,11 @@ func expandWebACLRule(m map[string]interface{}) *wafv2.Rule { } rule := &wafv2.Rule{ - Name: aws.String(m["name"].(string)), - Priority: aws.Int64(int64(m["priority"].(int))), Action: expandRuleAction(m["action"].([]interface{})), + CaptchaConfig: expandCaptchaConfig(m["captcha_config"].([]interface{})), + Name: aws.String(m["name"].(string)), OverrideAction: expandOverrideAction(m["override_action"].([]interface{})), + Priority: aws.Int64(int64(m["priority"].(int))), Statement: expandWebACLRootStatement(m["statement"].([]interface{})), VisibilityConfig: expandVisibilityConfig(m["visibility_config"].([]interface{})), } @@ -1323,6 +1351,7 @@ func flattenRules(r []*wafv2.Rule) interface{} { for i, rule := range r { m := make(map[string]interface{}) m["action"] = flattenRuleAction(rule.Action) + m["captcha_config"] = flattenCaptchaConfig(rule.CaptchaConfig) m["name"] = aws.StringValue(rule.Name) m["priority"] = int(aws.Int64Value(rule.Priority)) m["rule_label"] = flattenRuleLabels(rule.RuleLabels) @@ -1405,6 +1434,23 @@ func flattenCaptcha(a *wafv2.CaptchaAction) []interface{} { return []interface{}{m} } +func flattenCaptchaConfig(config *wafv2.CaptchaConfig) interface{} { + if config == nil { + return []interface{}{} + } + if config.ImmunityTimeProperty == nil { + return []interface{}{} + } + + m := map[string]interface{}{ + "immunity_time_property": []interface{}{map[string]interface{}{ + "immunity_time": aws.Int64Value(config.ImmunityTimeProperty.ImmunityTime), + }}, + } + + return []interface{}{m} +} + func flattenChallenge(a *wafv2.ChallengeAction) []interface{} { if a == nil { return []interface{}{} @@ -2074,6 +2120,7 @@ func flattenWebACLRules(r []*wafv2.Rule) interface{} { for i, rule := range r { m := make(map[string]interface{}) m["action"] = flattenRuleAction(rule.Action) + m["captcha_config"] = flattenCaptchaConfig(rule.CaptchaConfig) m["override_action"] = flattenOverrideAction(rule.OverrideAction) m["name"] = aws.StringValue(rule.Name) m["priority"] = int(aws.Int64Value(rule.Priority)) diff --git a/internal/service/wafv2/rule_group.go b/internal/service/wafv2/rule_group.go index edd90ce69d97..be64481e90e1 100644 --- a/internal/service/wafv2/rule_group.go +++ b/internal/service/wafv2/rule_group.go @@ -96,11 +96,12 @@ func ResourceRuleGroup() *schema.Resource { Schema: map[string]*schema.Schema{ "allow": allowConfigSchema(), "block": blockConfigSchema(), - "count": countConfigSchema(), "captcha": captchaConfigSchema(), + "count": countConfigSchema(), }, }, }, + "captcha_config": outerCaptchaConfigSchema(), "name": { Type: schema.TypeString, Required: true, diff --git a/internal/service/wafv2/schemas.go b/internal/service/wafv2/schemas.go index e9cc58c720e7..2d0b87367a7e 100644 --- a/internal/service/wafv2/schemas.go +++ b/internal/service/wafv2/schemas.go @@ -549,6 +549,31 @@ func captchaConfigSchema() *schema.Schema { } } +func outerCaptchaConfigSchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "immunity_time_property": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "immunity_time": { + Type: schema.TypeInt, + Optional: true, + }, + }, + }, + }, + }, + }, + } +} + func challengeConfigSchema() *schema.Schema { return &schema.Schema{ Type: schema.TypeList, diff --git a/internal/service/wafv2/web_acl.go b/internal/service/wafv2/web_acl.go index 6daf3703bb41..ddf81c9fa90e 100644 --- a/internal/service/wafv2/web_acl.go +++ b/internal/service/wafv2/web_acl.go @@ -62,6 +62,7 @@ func ResourceWebACL() *schema.Resource { Type: schema.TypeInt, Computed: true, }, + "captcha_config": outerCaptchaConfigSchema(), "custom_response_body": customResponseBodySchema(), "default_action": { Type: schema.TypeList, @@ -111,6 +112,7 @@ func ResourceWebACL() *schema.Resource { }, }, }, + "captcha_config": outerCaptchaConfigSchema(), "name": { Type: schema.TypeString, Required: true, @@ -157,6 +159,7 @@ func resourceWebACLCreate(ctx context.Context, d *schema.ResourceData, meta inte name := d.Get("name").(string) input := &wafv2.CreateWebACLInput{ + CaptchaConfig: expandCaptchaConfig(d.Get("captcha_config").([]interface{})), DefaultAction: expandDefaultAction(d.Get("default_action").([]interface{})), Name: aws.String(name), Rules: expandWebACLRules(d.Get("rule").(*schema.Set).List()), @@ -207,6 +210,9 @@ func resourceWebACLRead(ctx context.Context, d *schema.ResourceData, meta interf arn := aws.StringValue(webACL.ARN) d.Set("arn", arn) d.Set("capacity", webACL.Capacity) + if err := d.Set("captcha_config", flattenCaptchaConfig(webACL.CaptchaConfig)); err != nil { + return diag.Errorf("setting captcha_config: %s", err) + } if err := d.Set("custom_response_body", flattenCustomResponseBodies(webACL.CustomResponseBodies)); err != nil { return diag.Errorf("setting custom_response_body: %s", err) } @@ -232,6 +238,7 @@ func resourceWebACLUpdate(ctx context.Context, d *schema.ResourceData, meta inte if d.HasChangesExcept("tags", "tags_all") { input := &wafv2.UpdateWebACLInput{ + CaptchaConfig: expandCaptchaConfig(d.Get("captcha_config").([]interface{})), DefaultAction: expandDefaultAction(d.Get("default_action").([]interface{})), Id: aws.String(d.Id()), LockToken: aws.String(d.Get("lock_token").(string)), diff --git a/internal/service/wafv2/web_acl_test.go b/internal/service/wafv2/web_acl_test.go index c29d654be3ca..1556ab3f5407 100644 --- a/internal/service/wafv2/web_acl_test.go +++ b/internal/service/wafv2/web_acl_test.go @@ -49,6 +49,7 @@ func TestAccWAFV2WebACL_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "description", webACLName), resource.TestCheckResourceAttr(resourceName, "rule.#", "0"), resource.TestCheckResourceAttr(resourceName, "scope", wafv2.ScopeRegional), + resource.TestCheckResourceAttr(resourceName, "captcha_config.#", "0"), resource.TestCheckResourceAttr(resourceName, "default_action.#", "1"), resource.TestCheckResourceAttr(resourceName, "default_action.0.allow.#", "1"), resource.TestCheckResourceAttr(resourceName, "default_action.0.block.#", "0"), @@ -2035,11 +2036,15 @@ func TestAccWAFV2WebACL_Custom_requestHandling(t *testing.T) { "action.0.captcha.0.custom_request_handling.0.insert_header.1.value": "test-value-2", "action.0.count.#": "0", "priority": "1", + "captcha_config.#": "1", + "captcha_config.0.immunity_time_property.0.immunity_time": "240", }), resource.TestCheckResourceAttr(resourceName, "visibility_config.#", "1"), resource.TestCheckResourceAttr(resourceName, "visibility_config.0.cloudwatch_metrics_enabled", "false"), resource.TestCheckResourceAttr(resourceName, "visibility_config.0.metric_name", "friendly-metric-name"), resource.TestCheckResourceAttr(resourceName, "visibility_config.0.sampled_requests_enabled", "false"), + resource.TestCheckResourceAttr(resourceName, "captcha_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "captcha_config.0.immunity_time_property.0.immunity_time", "120"), ), }, { @@ -2951,6 +2956,12 @@ resource "aws_wafv2_web_acl" "test" { metric_name = "friendly-rule-metric-name" sampled_requests_enabled = false } + + captcha_config { + immunity_time_property { + immunity_time = 240 + } + } } visibility_config { @@ -2958,6 +2969,12 @@ resource "aws_wafv2_web_acl" "test" { metric_name = "friendly-metric-name" sampled_requests_enabled = false } + + captcha_config { + immunity_time_property { + immunity_time = 120 + } + } } `, name, firstHeader, secondHeader) } diff --git a/website/docs/r/wafv2_rule_group.html.markdown b/website/docs/r/wafv2_rule_group.html.markdown index 6534be707f67..fce7336e4e4d 100644 --- a/website/docs/r/wafv2_rule_group.html.markdown +++ b/website/docs/r/wafv2_rule_group.html.markdown @@ -206,6 +206,12 @@ resource "aws_wafv2_rule_group" "example" { metric_name = "rule-2" sampled_requests_enabled = false } + + captcha_config { + immunity_time_property { + immunity_time = 240 + } + } } rule { @@ -293,6 +299,12 @@ resource "aws_wafv2_rule_group" "example" { sampled_requests_enabled = false } + captcha_config { + immunity_time_property { + immunity_time = 120 + } + } + tags = { Name = "example-and-statement" Code = "123456" @@ -326,6 +338,7 @@ Each `custom_response_body` block supports the following arguments: Each `rule` supports the following arguments: * `action` - (Required) The action that AWS WAF should take on a web request when it matches the rule's statement. Settings at the `aws_wafv2_web_acl` level can override the rule action setting. See [Action](#action) below for details. +* `captcha_config` - (Optional) Specifies how AWS WAF should handle CAPTCHA evaluations. See [Captcha Configuration](#captcha-configuration) below for details. * `name` - (Required, Forces new resource) A friendly name of the rule. * `priority` - (Required) If you define more than one Rule in a WebACL, AWS WAF evaluates each request against the `rules` in order based on the value of `priority`. AWS WAF processes rules with lower priority first. * `rule_label` - (Optional) Labels to apply to web requests that match the rule match statement. See [Rule Label](#rule-label) below for details. @@ -654,6 +667,18 @@ The `visibility_config` block supports the following arguments: * `metric_name` - (Required, Forces new resource) A friendly name of the CloudWatch metric. The name can contain only alphanumeric characters (A-Z, a-z, 0-9) hyphen(-) and underscore (_), with length from one to 128 characters. It can't contain whitespace or metric names reserved for AWS WAF, for example `All` and `Default_Action`. * `sampled_requests_enabled` - (Required) A boolean indicating whether AWS WAF should store a sampling of the web requests that match the rules. You can view the sampled requests through the AWS WAF console. +### Captcha Configuration + +The `captcha_config` block supports the following arguments: + +* `immunity_time_property` - (Optional) Defines custom immunity time. See [Immunity Time Property](#immunity-time-property) below for details. + +### Immunity Time Property + +The `immunity_time_property` block supports the following arguments: + +* `immunity_time` - (Optional) The amount of time, in seconds, that a CAPTCHA or challenge timestamp is considered valid by AWS WAF. The default setting is 300. + ## Attributes Reference In addition to all arguments above, the following attributes are exported: diff --git a/website/docs/r/wafv2_web_acl.html.markdown b/website/docs/r/wafv2_web_acl.html.markdown index 35cd3acf8c5e..9f6d68329245 100644 --- a/website/docs/r/wafv2_web_acl.html.markdown +++ b/website/docs/r/wafv2_web_acl.html.markdown @@ -371,6 +371,7 @@ The `default_action` block supports the following arguments: Each `rule` supports the following arguments: * `action` - (Optional) Action that AWS WAF should take on a web request when it matches the rule's statement. This is used only for rules whose **statements do not reference a rule group**. See [`action`](#action) below for details. +* `captcha_config` - (Optional) Specifies how AWS WAF should handle CAPTCHA evaluations. See [Captcha Configuration](#captcha-configuration) below for details. * `name` - (Required) Friendly name of the rule. **NOTE:** The provider assumes that rules with names matching this pattern, `^ShieldMitigationRuleGroup___.*`, are AWS-added for [automatic application layer DDoS mitigation activities](https://docs.aws.amazon.com/waf/latest/developerguide/ddos-automatic-app-layer-response-rg.html). Such rules will be ignored by the provider unless you explicitly include them in your configuration (for example, by using the AWS CLI to discover their properties and creating matching configuration). However, since these rules are owned and managed by AWS, you may get permission errors. * `override_action` - (Optional) Override action to apply to the rules in a rule group. Used only for rule **statements that reference a rule group**, like `rule_group_reference_statement` and `managed_rule_group_statement`. See [`override_action`](#override_action) below for details. * `priority` - (Required) If you define more than one Rule in a WebACL, AWS WAF evaluates each request against the `rules` in order based on the value of `priority`. AWS WAF processes rules with lower priority first. @@ -835,6 +836,18 @@ The `visibility_config` block supports the following arguments: * `metric_name` - (Required) A friendly name of the CloudWatch metric. The name can contain only alphanumeric characters (A-Z, a-z, 0-9) hyphen(-) and underscore (\_), with length from one to 128 characters. It can't contain whitespace or metric names reserved for AWS WAF, for example `All` and `Default_Action`. * `sampled_requests_enabled` - (Required) Whether AWS WAF should store a sampling of the web requests that match the rules. You can view the sampled requests through the AWS WAF console. +### Captcha Configuration + +The `captcha_config` block supports the following arguments: + +* `immunity_time_property` - (Optional) Defines custom immunity time. See [Immunity Time Property](#immunity-time-property) below for details. + +### Immunity Time Property + +The `immunity_time_property` block supports the following arguments: + +* `immunity_time` - (Optional) The amount of time, in seconds, that a CAPTCHA or challenge timestamp is considered valid by AWS WAF. The default setting is 300. + ## Attributes Reference In addition to all arguments above, the following attributes are exported: