From 4162652f26c6a92d743575302b431a97e3cec793 Mon Sep 17 00:00:00 2001 From: Sergio Charpinel Date: Tue, 4 Jul 2023 13:24:19 -0300 Subject: [PATCH] add association_config attribute to aws_wafv2_web_acl resource --- internal/service/wafv2/flex.go | 50 +++++++++++++++ internal/service/wafv2/rule_group_test.go | 18 ++++++ internal/service/wafv2/schemas.go | 34 ++++++++++ internal/service/wafv2/web_acl.go | 12 ++++ internal/service/wafv2/web_acl_test.go | 74 ++++++++++++++++++++++ website/docs/r/wafv2_web_acl.html.markdown | 14 ++++ 6 files changed, 202 insertions(+) diff --git a/internal/service/wafv2/flex.go b/internal/service/wafv2/flex.go index c27fbd4d704..d374ccf6fe6 100644 --- a/internal/service/wafv2/flex.go +++ b/internal/service/wafv2/flex.go @@ -144,6 +144,36 @@ func expandAllowAction(l []interface{}) *wafv2.AllowAction { return action } +func expandAssociationConfig(l []interface{}) *wafv2.AssociationConfig { + configuration := &wafv2.AssociationConfig{} + + if len(l) == 0 || l[0] == nil { + return configuration + } + + m := l[0].(map[string]interface{}) + if v, ok := m["request_body"]; ok { + inner := v.([]interface{}) + if len(inner) == 0 || inner[0] == nil { + return configuration + } + + m = inner[0].(map[string]interface{}) + + if t, ok := m["key"]; ok { + if v, ok := m["default_size_inspection_limit"]; ok { + configuration.RequestBody = map[string]*wafv2.RequestBodyAssociatedResourceTypeConfig{ + t.(string): { + DefaultSizeInspectionLimit: aws.String(v.(string)), + }, + } + } + } + } + + return configuration +} + func expandBlockAction(l []interface{}) *wafv2.BlockAction { action := &wafv2.BlockAction{} @@ -1381,6 +1411,26 @@ func flattenAllow(a *wafv2.AllowAction) []interface{} { return []interface{}{m} } +func flattenAssociationConfig(config *wafv2.AssociationConfig) []interface{} { + if config == nil { + return []interface{}{} + } + if config.RequestBody == nil { + return []interface{}{} + } + + m := map[string]interface{}{} + for k, v := range config.RequestBody { + body := map[string]interface{}{ + "key": aws.StringValue(&k), + "default_size_inspection_limit": aws.StringValue(v.DefaultSizeInspectionLimit), + } + m["request_body"] = []interface{}{body} + } + + return []interface{}{m} +} + func flattenBlock(a *wafv2.BlockAction) []interface{} { if a == nil { return []interface{}{} diff --git a/internal/service/wafv2/rule_group_test.go b/internal/service/wafv2/rule_group_test.go index 20a081553e0..bc6dc0d4bfb 100644 --- a/internal/service/wafv2/rule_group_test.go +++ b/internal/service/wafv2/rule_group_test.go @@ -2126,6 +2126,24 @@ func testAccPreCheckScopeRegional(ctx context.Context, t *testing.T) { } } +func testAccPreCheckScopeCloudfront(ctx context.Context, t *testing.T) { + conn := acctest.Provider.Meta().(*conns.AWSClient).WAFV2Conn(ctx) + + input := &wafv2.ListRuleGroupsInput{ + Scope: aws.String(wafv2.ScopeCloudfront), + } + + _, err := conn.ListRuleGroupsWithContext(ctx, input) + + if acctest.PreCheckSkipError(err) { + t.Skipf("skipping acceptance testing: %s", err) + } + + if err != nil { + t.Fatalf("unexpected PreCheck error: %s", err) + } +} + func testAccCheckRuleGroupDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { for _, rs := range s.RootModule().Resources { diff --git a/internal/service/wafv2/schemas.go b/internal/service/wafv2/schemas.go index 4b6771d2f4b..5b18ed6640b 100644 --- a/internal/service/wafv2/schemas.go +++ b/internal/service/wafv2/schemas.go @@ -1235,3 +1235,37 @@ func managedRuleGroupConfigATPResponseInspectionSchema() *schema.Schema { }, } } + +func associationConfigSchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "request_body": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.All( + validation.StringLenBetween(1, 128), + validation.StringMatch(regexp.MustCompile(`^[\w\-]+$`), "must contain only alphanumeric, hyphen, and underscore characters"), + ), + }, + "default_size_inspection_limit": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice(wafv2.SizeInspectionLimit_Values(), false), + }, + }, + }, + }, + }, + }, + } +} diff --git a/internal/service/wafv2/web_acl.go b/internal/service/wafv2/web_acl.go index 2cd463fcfb3..1e48dceca8b 100644 --- a/internal/service/wafv2/web_acl.go +++ b/internal/service/wafv2/web_acl.go @@ -63,6 +63,7 @@ func ResourceWebACL() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "association_config": associationConfigSchema(), "capacity": { Type: schema.TypeInt, Computed: true, @@ -185,6 +186,10 @@ func resourceWebACLCreate(ctx context.Context, d *schema.ResourceData, meta inte VisibilityConfig: expandVisibilityConfig(d.Get("visibility_config").([]interface{})), } + if v, ok := d.GetOk("association_config"); ok { + input.AssociationConfig = expandAssociationConfig(v.([]interface{})) + } + if v, ok := d.GetOk("custom_response_body"); ok && v.(*schema.Set).Len() > 0 { input.CustomResponseBodies = expandCustomResponseBodies(v.(*schema.Set).List()) } @@ -230,6 +235,9 @@ func resourceWebACLRead(ctx context.Context, d *schema.ResourceData, meta interf webACL := output.WebACL arn := aws.StringValue(webACL.ARN) d.Set("arn", arn) + if err := d.Set("association_config", flattenAssociationConfig(webACL.AssociationConfig)); err != nil { + return diag.Errorf("setting association_config: %s", err) + } d.Set("capacity", webACL.Capacity) if err := d.Set("captcha_config", flattenCaptchaConfig(webACL.CaptchaConfig)); err != nil { return diag.Errorf("setting captcha_config: %s", err) @@ -270,6 +278,10 @@ func resourceWebACLUpdate(ctx context.Context, d *schema.ResourceData, meta inte VisibilityConfig: expandVisibilityConfig(d.Get("visibility_config").([]interface{})), } + if v, ok := d.GetOk("association_config"); ok && v.(*schema.Set).Len() > 0 { + input.AssociationConfig = expandAssociationConfig(v.(*schema.Set).List()) + } + if v, ok := d.GetOk("custom_response_body"); ok && v.(*schema.Set).Len() > 0 { input.CustomResponseBodies = expandCustomResponseBodies(v.(*schema.Set).List()) } diff --git a/internal/service/wafv2/web_acl_test.go b/internal/service/wafv2/web_acl_test.go index 8afd137c4d8..8f581632759 100644 --- a/internal/service/wafv2/web_acl_test.go +++ b/internal/service/wafv2/web_acl_test.go @@ -11,6 +11,7 @@ import ( "time" "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/cloudfront" "github.com/aws/aws-sdk-go/service/wafv2" sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" @@ -2388,6 +2389,79 @@ func TestAccWAFV2WebACL_tokenDomains(t *testing.T) { }) } +func TestAccWAFV2WebACL_AssociationConfig(t *testing.T) { + ctx := acctest.Context(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(ctx, t) + acctest.PreCheckRegion(t, "us-east-1") + acctest.PreCheckPartitionHasService(t, wafv2.EndpointsID) + acctest.PreCheckPartitionHasService(t, cloudfront.EndpointsID) + testAccPreCheckScopeCloudfront(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, wafv2.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckWebACLDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccWebACLConfig_associationConfig(webACLName), + Check: resource.ComposeTestCheckFunc( + testAccCheckWebACLExists(ctx, resourceName, &v), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "wafv2", regexp.MustCompile(`global/webacl/.+$`)), + resource.TestCheckResourceAttr(resourceName, "name", webACLName), + resource.TestCheckResourceAttr(resourceName, "scope", "CLOUDFRONT"), + resource.TestCheckResourceAttr(resourceName, "description", webACLName), + resource.TestCheckResourceAttr(resourceName, "rule.#", "0"), + resource.TestCheckResourceAttr(resourceName, "default_action.#", "1"), + resource.TestCheckResourceAttr(resourceName, "default_action.0.allow.#", "1"), + resource.TestCheckResourceAttr(resourceName, "default_action.0.block.#", "0"), + resource.TestCheckResourceAttr(resourceName, "association_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "association_config.0.request_body.#", "1"), + resource.TestCheckResourceAttr(resourceName, "association_config.0.request_body.0.key", "CLOUDFRONT"), + resource.TestCheckResourceAttr(resourceName, "association_config.0.request_body.0.default_size_inspection_limit", "KB_32"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: testAccWebACLImportStateIdFunc(resourceName), + }, + }, + }) +} + +func testAccWebACLConfig_associationConfig(name string) string { + return fmt.Sprintf(` +resource "aws_wafv2_web_acl" "test" { + name = %[1]q + description = %[1]q + scope = "CLOUDFRONT" + + default_action { + allow {} + } + + visibility_config { + cloudwatch_metrics_enabled = false + metric_name = "friendly-metric-name" + sampled_requests_enabled = false + } + + association_config { + request_body { + key = "CLOUDFRONT" + default_size_inspection_limit = "KB_32" + } + } +} +`, name) +} + func testAccCheckWebACLDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { for _, rs := range s.RootModule().Resources { diff --git a/website/docs/r/wafv2_web_acl.html.markdown b/website/docs/r/wafv2_web_acl.html.markdown index 0e6c4892d2c..e2228e93e5a 100644 --- a/website/docs/r/wafv2_web_acl.html.markdown +++ b/website/docs/r/wafv2_web_acl.html.markdown @@ -349,6 +349,7 @@ resource "aws_wafv2_web_acl" "test" { The following arguments are supported: +* `association_config` - (Optional) Specifies custom configurations for the associations between the web ACL and protected resources. See [`association_config`](#association_config) below for details. * `custom_response_body` - (Optional) Defines custom response bodies that can be referenced by `custom_response` actions. See [`custom_response_body`](#custom_response_body) below for details. * `default_action` - (Required) Action to perform if none of the `rules` contained in the WebACL match. See [`default_ action`](#default_action) below for details. * `description` - (Optional) Friendly description of the WebACL. @@ -359,6 +360,12 @@ The following arguments are supported: * `token_domains` - (Optional) Specifies the domains that AWS WAF should accept in a web request token. This enables the use of tokens across multiple protected websites. When AWS WAF provides a token, it uses the domain of the AWS resource that the web ACL is protecting. If you don't specify a list of token domains, AWS WAF accepts tokens only for the domain of the protected resource. With a token domain list, AWS WAF accepts the resource's host domain plus all domains in the token domain list, including their prefixed subdomains. * `visibility_config` - (Required) Defines and enables Amazon CloudWatch metrics and web request sample collection. See [`visibility_config`](#visibility_config) below for details. +### `association_config` + +Each `association_config` block supports the following arguments: + +* `request_body` - (Optional) Customizes the maximum size of the request body that your protected CloudFront distributions forward to AWS WAF for inspection. See [`request_body`](#request_body) below for details. + ### `custom_response_body` Each `custom_response_body` block supports the following arguments: @@ -588,6 +595,13 @@ The `rate_based_statement` block supports the following arguments: * `limit` - (Required) Limit on requests per 5-minute period for a single originating IP address. * `scope_down_statement` - (Optional) Optional nested statement that narrows the scope of the rate-based statement to matching web requests. This can be any nestable statement, and you can nest statements at any level below this scope-down statement. See [`statement`](#statement) above for details. +#### `request_body` + +The `request_body` block supports the following arguments: + +* `key` - (Required) Unique key identifying the associated resource type. +* `default_size_inspection_limit` - (Required) Specifies the maximum size of the web request body component that an associated CloudFront distribution should send to AWS WAF for inspection. + #### `regex_match_statement` A rule statement used to search web request components for a match against a single regular expression.