Skip to content

Commit

Permalink
r/aws_wafv2_web_acl add support for association_config
Browse files Browse the repository at this point in the history
  • Loading branch information
TsvetanMilanov committed Jul 3, 2023
1 parent be00acc commit 41385d6
Show file tree
Hide file tree
Showing 6 changed files with 246 additions and 15 deletions.
3 changes: 3 additions & 0 deletions .changelog/31668.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
resource/aws_wafv2_web_acl: Add association_config argument
```
68 changes: 68 additions & 0 deletions internal/service/wafv2/flex.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,51 @@ func expandCaptchaConfig(l []interface{}) *wafv2.CaptchaConfig {
return configuration
}

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 len(m) > 0 {
configuration.RequestBody = make(map[string]*wafv2.RequestBodyAssociatedResourceTypeConfig)
}

if v, ok := m["cloudfront"]; ok {
inner = v.([]interface{})
configuration.RequestBody[wafv2.AssociatedResourceTypeCloudfront] = expandRequestBodyConfigItem(inner)
}
}

return configuration
}

func expandRequestBodyConfigItem(l []interface{}) *wafv2.RequestBodyAssociatedResourceTypeConfig {
configuration := &wafv2.RequestBodyAssociatedResourceTypeConfig{}

if len(l) == 0 || l[0] == nil {
return configuration
}

m := l[0].(map[string]interface{})
if v, ok := m["default_size_inspection_limit"]; ok {
if v != "" {
configuration.DefaultSizeInspectionLimit = aws.String(v.(string))
}
}

return configuration
}

func expandRuleLabels(l []interface{}) []*wafv2.Label {
if len(l) == 0 || l[0] == nil {
return nil
Expand Down Expand Up @@ -1423,6 +1468,29 @@ func flattenCaptchaConfig(config *wafv2.CaptchaConfig) interface{} {
return []interface{}{m}
}

func flattenAssociationConfig(config *wafv2.AssociationConfig) interface{} {
associationConfig := []interface{}{}
if config == nil {
return associationConfig
}
if config.RequestBody == nil {
return associationConfig
}

cloudfrontRequestBodyConfig := config.RequestBody[wafv2.AssociatedResourceTypeCloudfront]
if cloudfrontRequestBodyConfig != nil {
associationConfig = append(associationConfig, map[string]interface{}{
"request_body": []map[string]interface{}{{
"cloudfront": []map[string]interface{}{{
"default_size_inspection_limit": aws.StringValue(cloudfrontRequestBodyConfig.DefaultSizeInspectionLimit),
}},
}},
})
}

return associationConfig
}

func flattenChallenge(a *wafv2.ChallengeAction) []interface{} {
if a == nil {
return []interface{}{}
Expand Down
37 changes: 37 additions & 0 deletions internal/service/wafv2/schemas.go
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,43 @@ func visibilityConfigSchema() *schema.Schema {
}
}

func associationConfigSchema() *schema.Schema {
return &schema.Schema{
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"request_body": requestBodySchema(),
},
},
}
}

func requestBodySchema() *schema.Schema {
return &schema.Schema{
Type: schema.TypeList,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"cloudfront": {
Type: schema.TypeList,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"default_size_inspection_limit": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice(wafv2.SizeInspectionLimit_Values(), false),
},
},
},
},
},
},
}
}

func allowConfigSchema() *schema.Schema {
return &schema.Schema{
Type: schema.TypeList,
Expand Down
36 changes: 21 additions & 15 deletions internal/service/wafv2/web_acl.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ func ResourceWebACL() *schema.Resource {
Type: schema.TypeString,
Computed: true,
},
"association_config": associationConfigSchema(),
"capacity": {
Type: schema.TypeInt,
Computed: true,
Expand Down Expand Up @@ -173,13 +174,14 @@ 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()),
Scope: aws.String(d.Get("scope").(string)),
Tags: getTagsIn(ctx),
VisibilityConfig: expandVisibilityConfig(d.Get("visibility_config").([]interface{})),
AssociationConfig: expandAssociationConfig(d.Get("association_config").([]interface{})),
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()),
Scope: aws.String(d.Get("scope").(string)),
Tags: getTagsIn(ctx),
VisibilityConfig: expandVisibilityConfig(d.Get("visibility_config").([]interface{})),
}

if v, ok := d.GetOk("custom_response_body"); ok && v.(*schema.Set).Len() > 0 {
Expand Down Expand Up @@ -228,6 +230,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("association_config", flattenAssociationConfig(webACL.AssociationConfig)); err != nil {
return diag.Errorf("setting association_config: %s", err)
}
if err := d.Set("captcha_config", flattenCaptchaConfig(webACL.CaptchaConfig)); err != nil {
return diag.Errorf("setting captcha_config: %s", err)
}
Expand Down Expand Up @@ -257,14 +262,15 @@ 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)),
Name: aws.String(d.Get("name").(string)),
Rules: expandWebACLRules(d.Get("rule").(*schema.Set).List()),
Scope: aws.String(d.Get("scope").(string)),
VisibilityConfig: expandVisibilityConfig(d.Get("visibility_config").([]interface{})),
AssociationConfig: expandAssociationConfig(d.Get("association_config").([]interface{})),
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)),
Name: aws.String(d.Get("name").(string)),
Rules: expandWebACLRules(d.Get("rule").(*schema.Set).List()),
Scope: aws.String(d.Get("scope").(string)),
VisibilityConfig: expandVisibilityConfig(d.Get("visibility_config").([]interface{})),
}

if v, ok := d.GetOk("custom_response_body"); ok && v.(*schema.Set).Len() > 0 {
Expand Down
98 changes: 98 additions & 0 deletions internal/service/wafv2/web_acl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,20 @@ package wafv2_test
import (
"context"
"fmt"
"os"
"regexp"
"testing"
"time"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/endpoints"
"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"
"github.com/hashicorp/terraform-plugin-testing/terraform"
"github.com/hashicorp/terraform-provider-aws/internal/acctest"
"github.com/hashicorp/terraform-provider-aws/internal/conns"
"github.com/hashicorp/terraform-provider-aws/internal/envvar"
tfwafv2 "github.com/hashicorp/terraform-provider-aws/internal/service/wafv2"
"github.com/hashicorp/terraform-provider-aws/internal/tfresource"
)
Expand Down Expand Up @@ -45,6 +48,7 @@ func TestAccWAFV2WebACL_basic(t *testing.T) {
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckWebACLExists(ctx, resourceName, &v),
acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "wafv2", regexp.MustCompile(`regional/webacl/.+$`)),
resource.TestCheckResourceAttr(resourceName, "association_config.#", "0"),
resource.TestCheckResourceAttr(resourceName, "captcha_config.#", "0"),
resource.TestCheckResourceAttr(resourceName, "default_action.#", "1"),
resource.TestCheckResourceAttr(resourceName, "default_action.0.allow.#", "1"),
Expand Down Expand Up @@ -2385,6 +2389,54 @@ func TestAccWAFV2WebACL_tokenDomains(t *testing.T) {
})
}

func TestAccWAFV2WebACL_associationConfig(t *testing.T) {
// All wafv2 requests with scope CLOUDFRONT should be sent to the us-east-1 api.
originalDefaultRegionEnvVarValue := os.Getenv(envvar.DefaultRegion)
defer func() { os.Setenv(envvar.DefaultRegion, originalDefaultRegionEnvVarValue) }()
os.Setenv(envvar.DefaultRegion, endpoints.UsEast1RegionID) //nolint:tenv // Can't use t.Setenv() because we are running the tests in parallel.

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); testAccPreCheckScopeCloudFront(ctx, t) },
ErrorCheck: acctest.ErrorCheck(t, wafv2.EndpointsID),
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
CheckDestroy: testAccCheckWebACLDestroy(ctx),
Steps: []resource.TestStep{
{
Config: testAccWebACLConfig_associationConfig(webACLName),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckWebACLExists(ctx, resourceName, &v),
acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "wafv2", regexp.MustCompile(`global/webacl/.+$`)),
resource.TestCheckResourceAttr(resourceName, "association_config.#", "1"),
resource.TestCheckResourceAttr(resourceName, "association_config.0.request_body.#", "1"),
resource.TestCheckResourceAttr(resourceName, "association_config.0.request_body.0.cloudfront.#", "1"),
resource.TestCheckResourceAttr(resourceName, "association_config.0.request_body.0.cloudfront.0.default_size_inspection_limit", "KB_64"),
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, "description", webACLName),
resource.TestCheckResourceAttr(resourceName, "name", webACLName),
resource.TestCheckResourceAttr(resourceName, "scope", wafv2.ScopeCloudfront),
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"),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
ImportStateIdFunc: testAccWebACLImportStateIdFunc(resourceName),
},
},
})
}

func testAccCheckWebACLDestroy(ctx context.Context) resource.TestCheckFunc {
return func(s *terraform.State) error {
for _, rs := range s.RootModule().Resources {
Expand Down Expand Up @@ -4650,3 +4702,49 @@ resource "aws_wafv2_web_acl" "test" {
}
`, name, domain1, domain2)
}

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 {}
}
association_config {
request_body {
cloudfront {
default_size_inspection_limit = "KB_64"
}
}
}
visibility_config {
cloudwatch_metrics_enabled = false
metric_name = "friendly-metric-name"
sampled_requests_enabled = false
}
}
`, name)
}

func testAccPreCheckScopeCloudFront(ctx context.Context, t *testing.T) {
conn := acctest.Provider.Meta().(*conns.AWSClient).WAFV2Conn(ctx)

input := &wafv2.ListWebACLsInput{
Scope: aws.String(wafv2.ScopeCloudfront),
}

_, err := conn.ListWebACLsWithContext(ctx, input)

if acctest.PreCheckSkipError(err) {
t.Skipf("skipping acceptance testing: %s", err)
}

if err != nil {
t.Fatalf("unexpected PreCheck error: %s", err)
}
}
19 changes: 19 additions & 0 deletions website/docs/r/wafv2_web_acl.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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`

The `association_config` block supports the following arguments:

* `request_body` - (Optional) Customizes the request body that your protected resource 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:
Expand Down Expand Up @@ -853,6 +860,18 @@ 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.

### `request_body`

The `request_body` block supports the following arguments:

* `cloudfront` - (Optional) Customizes the request body that your protected CloudFront distributions forward to AWS WAF for inspection. See [`cloudfront`](#cloudfront) below for details.

### `cloudfront`

The `cloudfront` block supports the following arguments:

* `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. This applies to statements in the web ACL that inspect the body or JSON body. Valid values are `KB_16`, `KB_32`, `KB_48` and `KB_64`.

## Attributes Reference

In addition to all arguments above, the following attributes are exported:
Expand Down

0 comments on commit 41385d6

Please sign in to comment.