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

resource/aws_waf(regional)_ipset: Properly handle updates and deletions over 1000 IP set descriptors #5588

Merged
merged 1 commit into from
Aug 20, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 32 additions & 15 deletions aws/resource_aws_waf_ipset.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import (
"github.com/hashicorp/terraform/helper/schema"
)

// WAF requires UpdateIPSet operations be split into batches of 1000 Updates
const wafUpdateIPSetUpdatesLimit = 1000

func resourceAwsWafIPSet() *schema.Resource {
return &schema.Resource{
Create: resourceAwsWafIPSetCreate,
Expand Down Expand Up @@ -138,7 +141,7 @@ func resourceAwsWafIPSetDelete(d *schema.ResourceData, meta interface{}) error {
noDescriptors := []interface{}{}
err := updateWafIpSetDescriptors(d.Id(), oldDescriptors, noDescriptors, conn)
if err != nil {
return fmt.Errorf("Error updating IPSetDescriptors: %s", err)
return fmt.Errorf("Error Deleting IPSetDescriptors: %s", err)
}
}

Expand All @@ -159,25 +162,28 @@ func resourceAwsWafIPSetDelete(d *schema.ResourceData, meta interface{}) error {
}

func updateWafIpSetDescriptors(id string, oldD, newD []interface{}, conn *waf.WAF) error {
wr := newWafRetryer(conn, "global")
_, err := wr.RetryWithToken(func(token *string) (interface{}, error) {
req := &waf.UpdateIPSetInput{
ChangeToken: token,
IPSetId: aws.String(id),
Updates: diffWafIpSetDescriptors(oldD, newD),
for _, ipSetUpdates := range diffWafIpSetDescriptors(oldD, newD) {
wr := newWafRetryer(conn, "global")
_, err := wr.RetryWithToken(func(token *string) (interface{}, error) {
req := &waf.UpdateIPSetInput{
ChangeToken: token,
IPSetId: aws.String(id),
Updates: ipSetUpdates,
}
log.Printf("[INFO] Updating IPSet descriptors: %s", req)
return conn.UpdateIPSet(req)
})
if err != nil {
return fmt.Errorf("Error Updating WAF IPSet: %s", err)
}
log.Printf("[INFO] Updating IPSet descriptors: %s", req)
return conn.UpdateIPSet(req)
})
if err != nil {
return fmt.Errorf("Error Updating WAF IPSet: %s", err)
}

return nil
}

func diffWafIpSetDescriptors(oldD, newD []interface{}) []*waf.IPSetUpdate {
updates := make([]*waf.IPSetUpdate, 0)
func diffWafIpSetDescriptors(oldD, newD []interface{}) [][]*waf.IPSetUpdate {
updates := make([]*waf.IPSetUpdate, 0, wafUpdateIPSetUpdatesLimit)
updatesBatches := make([][]*waf.IPSetUpdate, 0)

for _, od := range oldD {
descriptor := od.(map[string]interface{})
Expand All @@ -187,6 +193,11 @@ func diffWafIpSetDescriptors(oldD, newD []interface{}) []*waf.IPSetUpdate {
continue
}

if len(updates) == wafUpdateIPSetUpdatesLimit {
updatesBatches = append(updatesBatches, updates)
updates = make([]*waf.IPSetUpdate, 0, wafUpdateIPSetUpdatesLimit)
}

updates = append(updates, &waf.IPSetUpdate{
Action: aws.String(waf.ChangeActionDelete),
IPSetDescriptor: &waf.IPSetDescriptor{
Expand All @@ -199,6 +210,11 @@ func diffWafIpSetDescriptors(oldD, newD []interface{}) []*waf.IPSetUpdate {
for _, nd := range newD {
descriptor := nd.(map[string]interface{})

if len(updates) == wafUpdateIPSetUpdatesLimit {
updatesBatches = append(updatesBatches, updates)
updates = make([]*waf.IPSetUpdate, 0, wafUpdateIPSetUpdatesLimit)
}

updates = append(updates, &waf.IPSetUpdate{
Action: aws.String(waf.ChangeActionInsert),
IPSetDescriptor: &waf.IPSetDescriptor{
Expand All @@ -207,5 +223,6 @@ func diffWafIpSetDescriptors(oldD, newD []interface{}) []*waf.IPSetUpdate {
},
})
}
return updates
updatesBatches = append(updatesBatches, updates)
return updatesBatches
}
141 changes: 98 additions & 43 deletions aws/resource_aws_waf_ipset_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package aws

import (
"fmt"
"net"
"reflect"
"regexp"
"strings"
"testing"

"github.com/hashicorp/terraform/helper/resource"
Expand Down Expand Up @@ -169,11 +171,51 @@ func TestAccAWSWafIPSet_noDescriptors(t *testing.T) {
})
}

func TestAccAWSWafIPSet_IpSetDescriptors_1000UpdateLimit(t *testing.T) {
var ipset waf.IPSet
ipsetName := fmt.Sprintf("ip-set-%s", acctest.RandString(5))
resourceName := "aws_waf_ipset.ipset"

incrementIP := func(ip net.IP) {
for j := len(ip) - 1; j >= 0; j-- {
ip[j]++
if ip[j] > 0 {
break
}
}
}

// Generate 2048 IPs
ip, ipnet, err := net.ParseCIDR("10.0.0.0/21")
if err != nil {
t.Fatal(err)
}
ipSetDescriptors := make([]string, 0, 2048)
for ip := ip.Mask(ipnet.Mask); ipnet.Contains(ip); incrementIP(ip) {
ipSetDescriptors = append(ipSetDescriptors, fmt.Sprintf("ip_set_descriptors {\ntype=\"IPV4\"\nvalue=\"%s/32\"\n}", ip))
}

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSWafIPSetDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSWafIPSetConfig_IpSetDescriptors(ipsetName, strings.Join(ipSetDescriptors, "\n")),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckAWSWafIPSetExists(resourceName, &ipset),
resource.TestCheckResourceAttr(resourceName, "ip_set_descriptors.#", "2048"),
),
},
},
})
}

func TestDiffWafIpSetDescriptors(t *testing.T) {
testCases := []struct {
Old []interface{}
New []interface{}
ExpectedUpdates []*waf.IPSetUpdate
ExpectedUpdates [][]*waf.IPSetUpdate
}{
{
// Change
Expand All @@ -183,19 +225,21 @@ func TestDiffWafIpSetDescriptors(t *testing.T) {
New: []interface{}{
map[string]interface{}{"type": "IPV4", "value": "192.0.8.0/24"},
},
ExpectedUpdates: []*waf.IPSetUpdate{
&waf.IPSetUpdate{
Action: aws.String(waf.ChangeActionDelete),
IPSetDescriptor: &waf.IPSetDescriptor{
Type: aws.String("IPV4"),
Value: aws.String("192.0.7.0/24"),
ExpectedUpdates: [][]*waf.IPSetUpdate{
[]*waf.IPSetUpdate{
&waf.IPSetUpdate{
Action: aws.String(waf.ChangeActionDelete),
IPSetDescriptor: &waf.IPSetDescriptor{
Type: aws.String("IPV4"),
Value: aws.String("192.0.7.0/24"),
},
},
},
&waf.IPSetUpdate{
Action: aws.String(waf.ChangeActionInsert),
IPSetDescriptor: &waf.IPSetDescriptor{
Type: aws.String("IPV4"),
Value: aws.String("192.0.8.0/24"),
&waf.IPSetUpdate{
Action: aws.String(waf.ChangeActionInsert),
IPSetDescriptor: &waf.IPSetDescriptor{
Type: aws.String("IPV4"),
Value: aws.String("192.0.8.0/24"),
},
},
},
},
Expand All @@ -208,26 +252,28 @@ func TestDiffWafIpSetDescriptors(t *testing.T) {
map[string]interface{}{"type": "IPV4", "value": "10.0.2.0/24"},
map[string]interface{}{"type": "IPV4", "value": "10.0.3.0/24"},
},
ExpectedUpdates: []*waf.IPSetUpdate{
&waf.IPSetUpdate{
Action: aws.String(waf.ChangeActionInsert),
IPSetDescriptor: &waf.IPSetDescriptor{
Type: aws.String("IPV4"),
Value: aws.String("10.0.1.0/24"),
ExpectedUpdates: [][]*waf.IPSetUpdate{
[]*waf.IPSetUpdate{
&waf.IPSetUpdate{
Action: aws.String(waf.ChangeActionInsert),
IPSetDescriptor: &waf.IPSetDescriptor{
Type: aws.String("IPV4"),
Value: aws.String("10.0.1.0/24"),
},
},
},
&waf.IPSetUpdate{
Action: aws.String(waf.ChangeActionInsert),
IPSetDescriptor: &waf.IPSetDescriptor{
Type: aws.String("IPV4"),
Value: aws.String("10.0.2.0/24"),
&waf.IPSetUpdate{
Action: aws.String(waf.ChangeActionInsert),
IPSetDescriptor: &waf.IPSetDescriptor{
Type: aws.String("IPV4"),
Value: aws.String("10.0.2.0/24"),
},
},
},
&waf.IPSetUpdate{
Action: aws.String(waf.ChangeActionInsert),
IPSetDescriptor: &waf.IPSetDescriptor{
Type: aws.String("IPV4"),
Value: aws.String("10.0.3.0/24"),
&waf.IPSetUpdate{
Action: aws.String(waf.ChangeActionInsert),
IPSetDescriptor: &waf.IPSetDescriptor{
Type: aws.String("IPV4"),
Value: aws.String("10.0.3.0/24"),
},
},
},
},
Expand All @@ -239,19 +285,21 @@ func TestDiffWafIpSetDescriptors(t *testing.T) {
map[string]interface{}{"type": "IPV4", "value": "192.0.8.0/24"},
},
New: []interface{}{},
ExpectedUpdates: []*waf.IPSetUpdate{
&waf.IPSetUpdate{
Action: aws.String(waf.ChangeActionDelete),
IPSetDescriptor: &waf.IPSetDescriptor{
Type: aws.String("IPV4"),
Value: aws.String("192.0.7.0/24"),
ExpectedUpdates: [][]*waf.IPSetUpdate{
[]*waf.IPSetUpdate{
&waf.IPSetUpdate{
Action: aws.String(waf.ChangeActionDelete),
IPSetDescriptor: &waf.IPSetDescriptor{
Type: aws.String("IPV4"),
Value: aws.String("192.0.7.0/24"),
},
},
},
&waf.IPSetUpdate{
Action: aws.String(waf.ChangeActionDelete),
IPSetDescriptor: &waf.IPSetDescriptor{
Type: aws.String("IPV4"),
Value: aws.String("192.0.8.0/24"),
&waf.IPSetUpdate{
Action: aws.String(waf.ChangeActionDelete),
IPSetDescriptor: &waf.IPSetDescriptor{
Type: aws.String("IPV4"),
Value: aws.String("192.0.8.0/24"),
},
},
},
},
Expand Down Expand Up @@ -401,6 +449,13 @@ func testAccAWSWafIPSetConfigChangeIPSetDescriptors(name string) string {
}`, name)
}

func testAccAWSWafIPSetConfig_IpSetDescriptors(name, ipSetDescriptors string) string {
return fmt.Sprintf(`resource "aws_waf_ipset" "ipset" {
name = "%s"
%s
}`, name, ipSetDescriptors)
}

func testAccAWSWafIPSetConfig_noDescriptors(name string) string {
return fmt.Sprintf(`resource "aws_waf_ipset" "ipset" {
name = "%s"
Expand Down
29 changes: 15 additions & 14 deletions aws/resource_aws_wafregional_ipset.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ func resourceAwsWafRegionalIPSetDelete(d *schema.ResourceData, meta interface{})
err := updateIPSetResourceWR(d.Id(), oldD, noD, conn, region)

if err != nil {
return fmt.Errorf("Error Removing IPSetDescriptors: %s", err)
return fmt.Errorf("Error Deleting IPSetDescriptors: %s", err)
}
}

Expand All @@ -164,20 +164,21 @@ func resourceAwsWafRegionalIPSetDelete(d *schema.ResourceData, meta interface{})
}

func updateIPSetResourceWR(id string, oldD, newD []interface{}, conn *wafregional.WAFRegional, region string) error {

wr := newWafRegionalRetryer(conn, region)
_, err := wr.RetryWithToken(func(token *string) (interface{}, error) {
req := &waf.UpdateIPSetInput{
ChangeToken: token,
IPSetId: aws.String(id),
Updates: diffWafIpSetDescriptors(oldD, newD),
for _, ipSetUpdates := range diffWafIpSetDescriptors(oldD, newD) {
wr := newWafRegionalRetryer(conn, region)
_, err := wr.RetryWithToken(func(token *string) (interface{}, error) {
req := &waf.UpdateIPSetInput{
ChangeToken: token,
IPSetId: aws.String(id),
Updates: ipSetUpdates,
}
log.Printf("[INFO] Updating IPSet descriptor: %s", req)

return conn.UpdateIPSet(req)
})
if err != nil {
return fmt.Errorf("Error Updating WAF IPSet: %s", err)
}
log.Printf("[INFO] Updating IPSet descriptor: %s", req)

return conn.UpdateIPSet(req)
})
if err != nil {
return fmt.Errorf("Error Updating WAF IPSet: %s", err)
}

return nil
Expand Down
Loading