From d87cc0721f9dbf60c23385b3659accfd43422bcc Mon Sep 17 00:00:00 2001 From: Paul Stack Date: Tue, 14 Mar 2017 12:37:59 +0200 Subject: [PATCH] provider/aws: Add support for IPv6 to aws_security_group_rule (#12645) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ``` % make testacc TEST=./builtin/providers/aws TESTARGS='-run=TestAccAWSSecurityGroupRule_' ✹ ✭ ==> Checking that code complies with gofmt requirements... go generate $(go list ./... | grep -v /terraform/vendor/) 2017/03/13 15:40:39 Generated command/internal_plugin_list.go TF_ACC=1 go test ./builtin/providers/aws -v -run=TestAccAWSSecurityGroupRule_ -timeout 120m === RUN TestAccAWSSecurityGroupRule_Ingress_VPC --- PASS: TestAccAWSSecurityGroupRule_Ingress_VPC (53.36s) === RUN TestAccAWSSecurityGroupRule_Ingress_Protocol --- PASS: TestAccAWSSecurityGroupRule_Ingress_Protocol (85.22s) === RUN TestAccAWSSecurityGroupRule_Ingress_Ipv6 --- PASS: TestAccAWSSecurityGroupRule_Ingress_Ipv6 (87.55s) === RUN TestAccAWSSecurityGroupRule_Ingress_Classic --- PASS: TestAccAWSSecurityGroupRule_Ingress_Classic (50.58s) === RUN TestAccAWSSecurityGroupRule_MultiIngress --- PASS: TestAccAWSSecurityGroupRule_MultiIngress (47.98s) === RUN TestAccAWSSecurityGroupRule_Egress --- PASS: TestAccAWSSecurityGroupRule_Egress (50.48s) === RUN TestAccAWSSecurityGroupRule_SelfReference --- PASS: TestAccAWSSecurityGroupRule_SelfReference (82.45s) === RUN TestAccAWSSecurityGroupRule_ExpectInvalidTypeError --- PASS: TestAccAWSSecurityGroupRule_ExpectInvalidTypeError (0.01s) === RUN TestAccAWSSecurityGroupRule_PartialMatching_basic --- PASS: TestAccAWSSecurityGroupRule_PartialMatching_basic (95.55s) === RUN TestAccAWSSecurityGroupRule_PartialMatching_Source --- PASS: TestAccAWSSecurityGroupRule_PartialMatching_Source (95.65s) === RUN TestAccAWSSecurityGroupRule_Issue5310 --- PASS: TestAccAWSSecurityGroupRule_Issue5310 (45.91s) === RUN TestAccAWSSecurityGroupRule_Race --- PASS: TestAccAWSSecurityGroupRule_Race (697.79s) === RUN TestAccAWSSecurityGroupRule_SelfSource --- PASS: TestAccAWSSecurityGroupRule_SelfSource (96.19s) === RUN TestAccAWSSecurityGroupRule_PrefixListEgress --- PASS: TestAccAWSSecurityGroupRule_PrefixListEgress (97.51s) PASS ok github.com/hashicorp/terraform/builtin/providers/aws 1586.248s ``` --- .../aws/resource_aws_security_group_rule.go | 57 ++++++++++++- .../resource_aws_security_group_rule_test.go | 82 +++++++++++++++++-- .../aws/r/security_group_rule.html.markdown | 1 + 3 files changed, 130 insertions(+), 10 deletions(-) diff --git a/builtin/providers/aws/resource_aws_security_group_rule.go b/builtin/providers/aws/resource_aws_security_group_rule.go index f110f98ea..6c2f087fb 100644 --- a/builtin/providers/aws/resource_aws_security_group_rule.go +++ b/builtin/providers/aws/resource_aws_security_group_rule.go @@ -61,6 +61,13 @@ func resourceAwsSecurityGroupRule() *schema.Resource { Elem: &schema.Schema{Type: schema.TypeString}, }, + "ipv6_cidr_blocks": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "prefix_list_ids": { Type: schema.TypeList, Optional: true, @@ -400,6 +407,19 @@ func findRuleMatch(p *ec2.IpPermission, rules []*ec2.IpPermission, isVPC bool) * continue } + remaining = len(p.Ipv6Ranges) + for _, ipv6 := range p.Ipv6Ranges { + for _, ipv6ip := range r.Ipv6Ranges { + if *ipv6.CidrIpv6 == *ipv6ip.CidrIpv6 { + remaining-- + } + } + } + + if remaining > 0 { + continue + } + remaining = len(p.PrefixListIds) for _, pl := range p.PrefixListIds { for _, rpl := range r.PrefixListIds { @@ -463,6 +483,18 @@ func ipPermissionIDHash(sg_id, ruleType string, ip *ec2.IpPermission) string { } } + if len(ip.Ipv6Ranges) > 0 { + s := make([]string, len(ip.Ipv6Ranges)) + for i, r := range ip.Ipv6Ranges { + s[i] = *r.CidrIpv6 + } + sort.Strings(s) + + for _, v := range s { + buf.WriteString(fmt.Sprintf("%s-", v)) + } + } + if len(ip.PrefixListIds) > 0 { s := make([]string, len(ip.PrefixListIds)) for i, pl := range ip.PrefixListIds { @@ -555,6 +587,18 @@ func expandIPPerm(d *schema.ResourceData, sg *ec2.SecurityGroup) (*ec2.IpPermiss } } + if raw, ok := d.GetOk("ipv6_cidr_blocks"); ok { + list := raw.([]interface{}) + perm.Ipv6Ranges = make([]*ec2.Ipv6Range, len(list)) + for i, v := range list { + cidrIP, ok := v.(string) + if !ok { + return nil, fmt.Errorf("empty element found in ipv6_cidr_blocks - consider using the compact function") + } + perm.Ipv6Ranges[i] = &ec2.Ipv6Range{CidrIpv6: aws.String(cidrIP)} + } + } + if raw, ok := d.GetOk("prefix_list_ids"); ok { list := raw.([]interface{}) perm.PrefixListIds = make([]*ec2.PrefixListId, len(list)) @@ -584,6 +628,12 @@ func setFromIPPerm(d *schema.ResourceData, sg *ec2.SecurityGroup, rule *ec2.IpPe d.Set("cidr_blocks", cb) + var ipv6 []string + for _, ip := range rule.Ipv6Ranges { + ipv6 = append(ipv6, *ip.CidrIpv6) + } + d.Set("ipv6_cidr_blocks", ipv6) + var pl []string for _, p := range rule.PrefixListIds { pl = append(pl, *p.PrefixListId) @@ -603,15 +653,16 @@ func setFromIPPerm(d *schema.ResourceData, sg *ec2.SecurityGroup, rule *ec2.IpPe return nil } -// Validates that either 'cidr_blocks', 'self', or 'source_security_group_id' is set +// Validates that either 'cidr_blocks', 'ipv6_cidr_blocks', 'self', or 'source_security_group_id' is set func validateAwsSecurityGroupRule(d *schema.ResourceData) error { _, blocksOk := d.GetOk("cidr_blocks") + _, ipv6Ok := d.GetOk("ipv6_cidr_blocks") _, sourceOk := d.GetOk("source_security_group_id") _, selfOk := d.GetOk("self") _, prefixOk := d.GetOk("prefix_list_ids") - if !blocksOk && !sourceOk && !selfOk && !prefixOk { + if !blocksOk && !sourceOk && !selfOk && !prefixOk && !ipv6Ok { return fmt.Errorf( - "One of ['cidr_blocks', 'self', 'source_security_group_id', 'prefix_list_ids'] must be set to create an AWS Security Group Rule") + "One of ['cidr_blocks', 'ipv6_cidr_blocks', 'self', 'source_security_group_id', 'prefix_list_ids'] must be set to create an AWS Security Group Rule") } return nil } diff --git a/builtin/providers/aws/resource_aws_security_group_rule_test.go b/builtin/providers/aws/resource_aws_security_group_rule_test.go index 424e2a40f..d7da96054 100644 --- a/builtin/providers/aws/resource_aws_security_group_rule_test.go +++ b/builtin/providers/aws/resource_aws_security_group_rule_test.go @@ -52,15 +52,15 @@ func TestIpPermissionIDHash(t *testing.T) { FromPort: aws.Int64(int64(80)), ToPort: aws.Int64(int64(8000)), UserIdGroupPairs: []*ec2.UserIdGroupPair{ - &ec2.UserIdGroupPair{ + { UserId: aws.String("987654321"), GroupId: aws.String("sg-12345678"), }, - &ec2.UserIdGroupPair{ + { UserId: aws.String("123456789"), GroupId: aws.String("sg-987654321"), }, - &ec2.UserIdGroupPair{ + { UserId: aws.String("123456789"), GroupId: aws.String("sg-12345678"), }, @@ -72,15 +72,15 @@ func TestIpPermissionIDHash(t *testing.T) { FromPort: aws.Int64(int64(80)), ToPort: aws.Int64(int64(8000)), UserIdGroupPairs: []*ec2.UserIdGroupPair{ - &ec2.UserIdGroupPair{ + { UserId: aws.String("987654321"), GroupName: aws.String("my-security-group"), }, - &ec2.UserIdGroupPair{ + { UserId: aws.String("123456789"), GroupName: aws.String("my-security-group"), }, - &ec2.UserIdGroupPair{ + { UserId: aws.String("123456789"), GroupName: aws.String("my-other-security-group"), }, @@ -183,6 +183,46 @@ func TestAccAWSSecurityGroupRule_Ingress_Protocol(t *testing.T) { }) } +func TestAccAWSSecurityGroupRule_Ingress_Ipv6(t *testing.T) { + var group ec2.SecurityGroup + + testRuleCount := func(*terraform.State) error { + if len(group.IpPermissions) != 1 { + return fmt.Errorf("Wrong Security Group rule count, expected %d, got %d", + 1, len(group.IpPermissions)) + } + + rule := group.IpPermissions[0] + if *rule.FromPort != int64(80) { + return fmt.Errorf("Wrong Security Group port setting, expected %d, got %d", + 80, int(*rule.FromPort)) + } + + ipv6Address := rule.Ipv6Ranges[0] + if *ipv6Address.CidrIpv6 != "::/0" { + return fmt.Errorf("Wrong Security Group IPv6 address, expected %s, got %s", + "::/0", *ipv6Address.CidrIpv6) + } + + return nil + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSecurityGroupRuleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSSecurityGroupRuleIngress_ipv6Config, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSecurityGroupRuleExists("aws_security_group.web", &group), + testRuleCount, + ), + }, + }, + }) +} + func TestAccAWSSecurityGroupRule_Ingress_Classic(t *testing.T) { var group ec2.SecurityGroup rInt := acctest.RandInt() @@ -376,7 +416,7 @@ func TestAccAWSSecurityGroupRule_PartialMatching_Source(t *testing.T) { ToPort: aws.Int64(80), IpProtocol: aws.String("tcp"), UserIdGroupPairs: []*ec2.UserIdGroupPair{ - &ec2.UserIdGroupPair{GroupId: nat.GroupId}, + {GroupId: nat.GroupId}, }, } @@ -696,6 +736,34 @@ func testAccAWSSecurityGroupRuleIngressConfig(rInt int) string { }`, rInt) } +const testAccAWSSecurityGroupRuleIngress_ipv6Config = ` +resource "aws_vpc" "tftest" { + cidr_block = "10.0.0.0/16" + + tags { + Name = "tf-testing" + } +} + +resource "aws_security_group" "web" { + vpc_id = "${aws_vpc.tftest.id}" + + tags { + Name = "tf-acc-test" + } +} + +resource "aws_security_group_rule" "ingress_1" { + type = "ingress" + protocol = "6" + from_port = 80 + to_port = 8000 + ipv6_cidr_blocks = ["::/0"] + + security_group_id = "${aws_security_group.web.id}" +} +` + const testAccAWSSecurityGroupRuleIngress_protocolConfig = ` resource "aws_vpc" "tftest" { cidr_block = "10.0.0.0/16" diff --git a/website/source/docs/providers/aws/r/security_group_rule.html.markdown b/website/source/docs/providers/aws/r/security_group_rule.html.markdown index db123effe..56deb5b47 100644 --- a/website/source/docs/providers/aws/r/security_group_rule.html.markdown +++ b/website/source/docs/providers/aws/r/security_group_rule.html.markdown @@ -42,6 +42,7 @@ The following arguments are supported: * `type` - (Required) The type of rule being created. Valid options are `ingress` (inbound) or `egress` (outbound). * `cidr_blocks` - (Optional) List of CIDR blocks. Cannot be specified with `source_security_group_id`. +* `ipv6_cidr_blocks` - (Optional) List of IPv6 CIDR blocks. * `prefix_list_ids` - (Optional) List of prefix list IDs (for allowing access to VPC endpoints). Only valid with `egress`. * `from_port` - (Required) The start port (or ICMP type number if protocol is "icmp").