From 464912e33a0a1f331fc7a5d2e36bb61e8bfd2402 Mon Sep 17 00:00:00 2001 From: David Meyer Date: Thu, 5 Oct 2017 12:16:59 -0500 Subject: [PATCH] New resource aws_security_group_rules --- aws/provider.go | 3 +- aws/resource_aws_security_group_rules.go | 296 ++++ aws/resource_aws_security_group_rules_test.go | 1505 +++++++++++++++++ website/aws.erb | 4 + website/docs/r/security_group.html.markdown | 27 +- .../docs/r/security_group_rule.html.markdown | 25 +- .../docs/r/security_group_rules.html.markdown | 117 ++ 7 files changed, 1962 insertions(+), 15 deletions(-) create mode 100644 aws/resource_aws_security_group_rules.go create mode 100644 aws/resource_aws_security_group_rules_test.go create mode 100644 website/docs/r/security_group_rules.html.markdown diff --git a/aws/provider.go b/aws/provider.go index 795893d3332..fffdeb63b5e 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -434,10 +434,11 @@ func Provider() terraform.ResourceProvider { "aws_s3_bucket_policy": resourceAwsS3BucketPolicy(), "aws_s3_bucket_object": resourceAwsS3BucketObject(), "aws_s3_bucket_notification": resourceAwsS3BucketNotification(), - "aws_security_group": resourceAwsSecurityGroup(), "aws_network_interface_sg_attachment": resourceAwsNetworkInterfaceSGAttachment(), "aws_default_security_group": resourceAwsDefaultSecurityGroup(), + "aws_security_group": resourceAwsSecurityGroup(), "aws_security_group_rule": resourceAwsSecurityGroupRule(), + "aws_security_group_rules": resourceAwsSecurityGroupRules(), "aws_simpledb_domain": resourceAwsSimpleDBDomain(), "aws_ssm_activation": resourceAwsSsmActivation(), "aws_ssm_association": resourceAwsSsmAssociation(), diff --git a/aws/resource_aws_security_group_rules.go b/aws/resource_aws_security_group_rules.go new file mode 100644 index 00000000000..e9bb0f31c07 --- /dev/null +++ b/aws/resource_aws_security_group_rules.go @@ -0,0 +1,296 @@ +package aws + +import ( + "fmt" + "log" + + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceAwsSecurityGroupRules() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsSecurityGroupRulesCreate, + Read: resourceAwsSecurityGroupRulesRead, + Update: resourceAwsSecurityGroupRulesUpdate, + Delete: resourceAwsSecurityGroupRulesDelete, + + Schema: map[string]*schema.Schema{ + "security_group_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "ingress": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "from_port": { + Type: schema.TypeInt, + Required: true, + }, + + "to_port": { + Type: schema.TypeInt, + Required: true, + }, + + "protocol": { + Type: schema.TypeString, + Required: true, + StateFunc: protocolStateFunc, + }, + + "cidr_blocks": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validateCIDRNetworkAddress, + }, + }, + + "ipv6_cidr_blocks": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validateCIDRNetworkAddress, + }, + }, + + "security_groups": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + }, + + "self": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + }, + }, + Set: resourceAwsSecurityGroupRuleHash, + }, + + "egress": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "from_port": { + Type: schema.TypeInt, + Required: true, + }, + + "to_port": { + Type: schema.TypeInt, + Required: true, + }, + + "protocol": { + Type: schema.TypeString, + Required: true, + StateFunc: protocolStateFunc, + }, + + "cidr_blocks": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validateCIDRNetworkAddress, + }, + }, + + "ipv6_cidr_blocks": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validateCIDRNetworkAddress, + }, + }, + + "prefix_list_ids": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + + "security_groups": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + }, + + "self": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + }, + }, + Set: resourceAwsSecurityGroupRuleHash, + }, + }, + } +} + +func resourceAwsSecurityGroupRulesCreate(d *schema.ResourceData, meta interface{}) error { + return resourceAwsSecurityGroupRulesUpdate(d, meta) +} + +func resourceAwsSecurityGroupRulesRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + sgRaw, _, err := SGStateRefreshFunc(conn, d.Id())() + if err != nil { + return err + } + if sgRaw == nil { + d.SetId("") + return nil + } + + sg := sgRaw.(*ec2.SecurityGroup) + + remoteIngressRules := resourceAwsSecurityGroupIPPermGather(d.Id(), sg.IpPermissions, sg.OwnerId) + remoteEgressRules := resourceAwsSecurityGroupIPPermGather(d.Id(), sg.IpPermissionsEgress, sg.OwnerId) + + localIngressRules := d.Get("ingress").(*schema.Set).List() + localEgressRules := d.Get("egress").(*schema.Set).List() + + // Loop through the local state of rules, doing a match against the remote + // ruleSet we built above. + ingressRules := matchRules("ingress", localIngressRules, remoteIngressRules) + egressRules := matchRules("egress", localEgressRules, remoteEgressRules) + + if err := d.Set("ingress", ingressRules); err != nil { + log.Printf("[WARN] Error setting Ingress rule set for (%s): %s", d.Id(), err) + } + + if err := d.Set("egress", egressRules); err != nil { + log.Printf("[WARN] Error setting Egress rule set for (%s): %s", d.Id(), err) + } + + return nil +} + +func resourceAwsSecurityGroupRulesUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + id := d.Get("security_group_id").(string) + + awsMutexKV.Lock(id) + defer awsMutexKV.Unlock(id) + + sgRaw, _, err := SGStateRefreshFunc(conn, id)() + if err != nil { + return err + } + if sgRaw == nil { + d.SetId("") + return nil + } + + d.SetId(id) + + group := sgRaw.(*ec2.SecurityGroup) + isVPC := group.VpcId != nil && *group.VpcId != "" + + err = resourceAwsSecurityGroupUpdateRules(d, "ingress", meta, group) + if err != nil { + return err + } + + if isVPC { + err = resourceAwsSecurityGroupUpdateRules(d, "egress", meta, group) + if err != nil { + return err + } + } + + return resourceAwsSecurityGroupRulesRead(d, meta) +} + +func resourceAwsSecurityGroupRulesDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + id := d.Get("security_group_id").(string) + + awsMutexKV.Lock(id) + defer awsMutexKV.Unlock(id) + + log.Printf("[DEBUG] Security Group Rules destroy: %v", id) + + sgRaw, _, err := SGStateRefreshFunc(conn, id)() + if err != nil { + return err + } + if sgRaw == nil { + return nil + } + + group := sgRaw.(*ec2.SecurityGroup) + + ingress, err := expandIPPerms(group, d.Get("ingress").(*schema.Set).List()) + if err != nil { + return err + } + egress, err := expandIPPerms(group, d.Get("egress").(*schema.Set).List()) + if err != nil { + return err + } + + if len(ingress) > 0 || len(egress) > 0 { + conn := meta.(*AWSClient).ec2conn + + var err error + if len(ingress) > 0 { + log.Printf("[DEBUG] Revoking security group %#v %s rule: %#v", + group, "ingress", ingress) + + req := &ec2.RevokeSecurityGroupIngressInput{ + GroupId: group.GroupId, + IpPermissions: ingress, + } + if group.VpcId == nil || *group.VpcId == "" { + req.GroupId = nil + req.GroupName = group.GroupName + } + _, err = conn.RevokeSecurityGroupIngress(req) + if err != nil { + return fmt.Errorf( + "Error revoking security group %s rules: %s", + "ingress", err) + } + } + + if len(egress) > 0 { + log.Printf("[DEBUG] Revoking security group %#v %s rule: %#v", + group, "egress", egress) + + req := &ec2.RevokeSecurityGroupEgressInput{ + GroupId: group.GroupId, + IpPermissions: egress, + } + _, err = conn.RevokeSecurityGroupEgress(req) + if err != nil { + return fmt.Errorf( + "Error revoking security group %s rules: %s", + "egress", err) + } + } + } + + d.SetId("") + + return nil +} diff --git a/aws/resource_aws_security_group_rules_test.go b/aws/resource_aws_security_group_rules_test.go new file mode 100644 index 00000000000..ebc27a3b14e --- /dev/null +++ b/aws/resource_aws_security_group_rules_test.go @@ -0,0 +1,1505 @@ +package aws + +import ( + "fmt" + "reflect" + "regexp" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAWSSecurityGroupRules_basic(t *testing.T) { + var group ec2.SecurityGroup + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + IDRefreshName: "aws_security_group_rules.web", + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSecurityGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSSecurityGroupRulesConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSecurityGroupExists("aws_security_group_rules.web", &group), + testAccCheckAWSSecurityGroupRulesAttributes(&group), + resource.TestCheckResourceAttr( + "aws_security_group_rules.web", "ingress.3629188364.protocol", "tcp"), + resource.TestCheckResourceAttr( + "aws_security_group_rules.web", "ingress.3629188364.from_port", "80"), + resource.TestCheckResourceAttr( + "aws_security_group_rules.web", "ingress.3629188364.to_port", "8000"), + resource.TestCheckResourceAttr( + "aws_security_group_rules.web", "ingress.3629188364.cidr_blocks.#", "1"), + resource.TestCheckResourceAttr( + "aws_security_group_rules.web", "ingress.3629188364.cidr_blocks.0", "10.0.0.0/8"), + ), + }, + }, + }) +} + +func TestAccAWSSecurityGroupRules_ipv6(t *testing.T) { + var group ec2.SecurityGroup + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + IDRefreshName: "aws_security_group_rules.web", + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSecurityGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSSecurityGroupRulesConfigIpv6, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSecurityGroupExists("aws_security_group_rules.web", &group), + resource.TestCheckResourceAttr( + "aws_security_group_rules.web", "ingress.2293451516.protocol", "tcp"), + resource.TestCheckResourceAttr( + "aws_security_group_rules.web", "ingress.2293451516.from_port", "80"), + resource.TestCheckResourceAttr( + "aws_security_group_rules.web", "ingress.2293451516.to_port", "8000"), + resource.TestCheckResourceAttr( + "aws_security_group_rules.web", "ingress.2293451516.ipv6_cidr_blocks.#", "1"), + resource.TestCheckResourceAttr( + "aws_security_group_rules.web", "ingress.2293451516.ipv6_cidr_blocks.0", "::/0"), + ), + }, + }, + }) +} + +func TestAccAWSSecurityGroupRules_self(t *testing.T) { + var group ec2.SecurityGroup + + checkSelf := func(s *terraform.State) (err error) { + defer func() { + if e := recover(); e != nil { + err = fmt.Errorf("bad: %#v", group) + } + }() + + if *group.IpPermissions[0].UserIdGroupPairs[0].GroupId != *group.GroupId { + return fmt.Errorf("bad: %#v", group) + } + + return nil + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + IDRefreshName: "aws_security_group_rules.web", + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSecurityGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSSecurityGroupRulesConfigSelf, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSecurityGroupExists("aws_security_group_rules.web", &group), + resource.TestCheckResourceAttr( + "aws_security_group_rules.web", "ingress.3971148406.protocol", "tcp"), + resource.TestCheckResourceAttr( + "aws_security_group_rules.web", "ingress.3971148406.from_port", "80"), + resource.TestCheckResourceAttr( + "aws_security_group_rules.web", "ingress.3971148406.to_port", "8000"), + resource.TestCheckResourceAttr( + "aws_security_group_rules.web", "ingress.3971148406.self", "true"), + checkSelf, + ), + }, + }, + }) +} + +func TestAccAWSSecurityGroupRules_vpc(t *testing.T) { + var group ec2.SecurityGroup + + testCheck := func(*terraform.State) error { + if *group.VpcId == "" { + return fmt.Errorf("should have vpc ID") + } + + return nil + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + IDRefreshName: "aws_security_group_rules.web", + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSecurityGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSSecurityGroupRulesConfigVpc, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSecurityGroupExists("aws_security_group_rules.web", &group), + testAccCheckAWSSecurityGroupRulesAttributes(&group), + resource.TestCheckResourceAttr( + "aws_security_group_rules.web", "ingress.3629188364.protocol", "tcp"), + resource.TestCheckResourceAttr( + "aws_security_group_rules.web", "ingress.3629188364.from_port", "80"), + resource.TestCheckResourceAttr( + "aws_security_group_rules.web", "ingress.3629188364.to_port", "8000"), + resource.TestCheckResourceAttr( + "aws_security_group_rules.web", "ingress.3629188364.cidr_blocks.#", "1"), + resource.TestCheckResourceAttr( + "aws_security_group_rules.web", "ingress.3629188364.cidr_blocks.0", "10.0.0.0/8"), + resource.TestCheckResourceAttr( + "aws_security_group_rules.web", "egress.3629188364.protocol", "tcp"), + resource.TestCheckResourceAttr( + "aws_security_group_rules.web", "egress.3629188364.from_port", "80"), + resource.TestCheckResourceAttr( + "aws_security_group_rules.web", "egress.3629188364.to_port", "8000"), + resource.TestCheckResourceAttr( + "aws_security_group_rules.web", "egress.3629188364.cidr_blocks.#", "1"), + resource.TestCheckResourceAttr( + "aws_security_group_rules.web", "egress.3629188364.cidr_blocks.0", "10.0.0.0/8"), + testCheck, + ), + }, + }, + }) +} + +func TestAccAWSSecurityGroupRules_vpcNegOneIngress(t *testing.T) { + var group ec2.SecurityGroup + + testCheck := func(*terraform.State) error { + if *group.VpcId == "" { + return fmt.Errorf("should have vpc ID") + } + + return nil + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + IDRefreshName: "aws_security_group_rules.web", + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSecurityGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSSecurityGroupRulesConfigVpcNegOneIngress, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSecurityGroupExists("aws_security_group_rules.web", &group), + testAccCheckAWSSecurityGroupRulesAttributesNegOneProtocol(&group), + resource.TestCheckResourceAttr( + "aws_security_group_rules.web", "ingress.956249133.protocol", "-1"), + resource.TestCheckResourceAttr( + "aws_security_group_rules.web", "ingress.956249133.from_port", "0"), + resource.TestCheckResourceAttr( + "aws_security_group_rules.web", "ingress.956249133.to_port", "0"), + resource.TestCheckResourceAttr( + "aws_security_group_rules.web", "ingress.956249133.cidr_blocks.#", "1"), + resource.TestCheckResourceAttr( + "aws_security_group_rules.web", "ingress.956249133.cidr_blocks.0", "10.0.0.0/8"), + testCheck, + ), + }, + }, + }) +} +func TestAccAWSSecurityGroupRules_vpcProtoNumIngress(t *testing.T) { + var group ec2.SecurityGroup + + testCheck := func(*terraform.State) error { + if *group.VpcId == "" { + return fmt.Errorf("should have vpc ID") + } + + return nil + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + IDRefreshName: "aws_security_group_rules.web", + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSecurityGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSSecurityGroupRulesConfigVpcProtoNumIngress, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSecurityGroupExists("aws_security_group_rules.web", &group), + resource.TestCheckResourceAttr( + "aws_security_group_rules.web", "ingress.2449525218.protocol", "50"), + resource.TestCheckResourceAttr( + "aws_security_group_rules.web", "ingress.2449525218.from_port", "0"), + resource.TestCheckResourceAttr( + "aws_security_group_rules.web", "ingress.2449525218.to_port", "0"), + resource.TestCheckResourceAttr( + "aws_security_group_rules.web", "ingress.2449525218.cidr_blocks.#", "1"), + resource.TestCheckResourceAttr( + "aws_security_group_rules.web", "ingress.2449525218.cidr_blocks.0", "10.0.0.0/8"), + testCheck, + ), + }, + }, + }) +} +func TestAccAWSSecurityGroupRules_MultiIngress(t *testing.T) { + var group ec2.SecurityGroup + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + IDRefreshName: "aws_security_group_rules.web", + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSecurityGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSSecurityGroupRulesConfigMultiIngress, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSecurityGroupExists("aws_security_group_rules.web", &group), + ), + }, + }, + }) +} + +func TestAccAWSSecurityGroupRules_Change(t *testing.T) { + var group ec2.SecurityGroup + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + IDRefreshName: "aws_security_group_rules.web", + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSecurityGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSSecurityGroupRulesConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSecurityGroupExists("aws_security_group_rules.web", &group), + ), + }, + { + Config: testAccAWSSecurityGroupRulesConfigChange, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSecurityGroupExists("aws_security_group_rules.web", &group), + testAccCheckAWSSecurityGroupRulesAttributesChanged(&group), + ), + }, + }, + }) +} + +// Testing drift detection with groups containing the same port and types +func TestAccAWSSecurityGroupRules_drift(t *testing.T) { + var group ec2.SecurityGroup + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSecurityGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSSecurityGroupRulesConfig_drift(), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSecurityGroupExists("aws_security_group_rules.web", &group), + resource.TestCheckResourceAttr( + "aws_security_group_rules.web", "ingress.3629188364.protocol", "tcp"), + resource.TestCheckResourceAttr( + "aws_security_group_rules.web", "ingress.3629188364.from_port", "80"), + resource.TestCheckResourceAttr( + "aws_security_group_rules.web", "ingress.3629188364.to_port", "8000"), + resource.TestCheckResourceAttr( + "aws_security_group_rules.web", "ingress.3629188364.cidr_blocks.#", "1"), + resource.TestCheckResourceAttr( + "aws_security_group_rules.web", "ingress.3629188364.cidr_blocks.0", "10.0.0.0/8"), + ), + }, + }, + }) +} + +func TestAccAWSSecurityGroupRules_drift_complex(t *testing.T) { + var group ec2.SecurityGroup + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSecurityGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSSecurityGroupRulesConfig_drift_complex(), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSecurityGroupExists("aws_security_group_rules.web", &group), + resource.TestCheckResourceAttr( + "aws_security_group_rules.web", "ingress.3629188364.protocol", "tcp"), + resource.TestCheckResourceAttr( + "aws_security_group_rules.web", "ingress.3629188364.from_port", "80"), + resource.TestCheckResourceAttr( + "aws_security_group_rules.web", "ingress.3629188364.to_port", "8000"), + resource.TestCheckResourceAttr( + "aws_security_group_rules.web", "ingress.3629188364.cidr_blocks.#", "1"), + resource.TestCheckResourceAttr( + "aws_security_group_rules.web", "ingress.3629188364.cidr_blocks.0", "10.0.0.0/8"), + ), + }, + }, + }) +} + +func TestAccAWSSecurityGroupRules_invalidCIDRBlock(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSecurityGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSSecurityGroupRulesInvalidIngressCidr, + ExpectError: regexp.MustCompile("invalid CIDR address: 1.2.3.4/33"), + }, + { + Config: testAccAWSSecurityGroupRulesInvalidEgressCidr, + ExpectError: regexp.MustCompile("invalid CIDR address: 1.2.3.4/33"), + }, + { + Config: testAccAWSSecurityGroupRulesInvalidIPv6IngressCidr, + ExpectError: regexp.MustCompile("invalid CIDR address: ::/244"), + }, + { + Config: testAccAWSSecurityGroupRulesInvalidIPv6EgressCidr, + ExpectError: regexp.MustCompile("invalid CIDR address: ::/244"), + }, + }, + }) +} + +func testAccCheckAWSSecurityGroupRulesEmpty(n string, group *ec2.SecurityGroup) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No Security Group is set") + } + + conn := testAccProvider.Meta().(*AWSClient).ec2conn + req := &ec2.DescribeSecurityGroupsInput{ + GroupIds: []*string{aws.String(rs.Primary.ID)}, + } + resp, err := conn.DescribeSecurityGroups(req) + if err != nil { + return err + } + + if len(resp.SecurityGroups) > 0 && *resp.SecurityGroups[0].GroupId == rs.Primary.ID { + *group = *resp.SecurityGroups[0] + + if len(group.IpPermissions) != 0 { + return fmt.Errorf("Ingress rules are not empty") + } + + if len(group.IpPermissionsEgress) != 0 { + return fmt.Errorf("Egress rules are not empty") + } + + return nil + } + + return fmt.Errorf("Security Group not found") + } +} + +func testAccCheckAWSSecurityGroupRulesAttributes(group *ec2.SecurityGroup) resource.TestCheckFunc { + return func(s *terraform.State) error { + p := &ec2.IpPermission{ + FromPort: aws.Int64(80), + ToPort: aws.Int64(8000), + IpProtocol: aws.String("tcp"), + IpRanges: []*ec2.IpRange{{CidrIp: aws.String("10.0.0.0/8")}}, + } + + if len(group.IpPermissions) == 0 { + return fmt.Errorf("No IPPerms") + } + + // Compare our ingress + if !reflect.DeepEqual(group.IpPermissions[0], p) { + return fmt.Errorf( + "Got:\n\n%#v\n\nExpected:\n\n%#v\n", + group.IpPermissions[0], + p) + } + + return nil + } +} + +func testAccCheckAWSSecurityGroupRulesAttributesNegOneProtocol(group *ec2.SecurityGroup) resource.TestCheckFunc { + return func(s *terraform.State) error { + p := &ec2.IpPermission{ + IpProtocol: aws.String("-1"), + IpRanges: []*ec2.IpRange{{CidrIp: aws.String("10.0.0.0/8")}}, + } + + if len(group.IpPermissions) == 0 { + return fmt.Errorf("No IPPerms") + } + + // Compare our ingress + if !reflect.DeepEqual(group.IpPermissions[0], p) { + return fmt.Errorf( + "Got:\n\n%#v\n\nExpected:\n\n%#v\n", + group.IpPermissions[0], + p) + } + + return nil + } +} + +func TestAccAWSSecurityGroupRules_CIDRandGroups(t *testing.T) { + var group ec2.SecurityGroup + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSecurityGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSSecurityGroupRulesCombindCIDRandGroups, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSecurityGroupExists("aws_security_group_rules.mixed", &group), + // testAccCheckAWSSecurityGroupRulesAttributes(&group), + ), + }, + }, + }) +} + +func TestAccAWSSecurityGroupRules_ingressWithCidrAndSGs(t *testing.T) { + var group ec2.SecurityGroup + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSecurityGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSSecurityGroupRulesConfig_ingressWithCidrAndSGs, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSecurityGroupExists("aws_security_group_rules.web", &group), + testAccCheckAWSSecurityGroupRulesSGandCidrAttributes(&group), + resource.TestCheckResourceAttr( + "aws_security_group_rules.web", "ingress.#", "2"), + ), + }, + }, + }) +} + +// This test requires an EC2 Classic region +func TestAccAWSSecurityGroupRules_ingressWithCidrAndSGs_classic(t *testing.T) { + var group ec2.SecurityGroup + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSecurityGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSSecurityGroupRulesConfig_ingressWithCidrAndSGs_classic, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSecurityGroupExists("aws_security_group_rules.web", &group), + testAccCheckAWSSecurityGroupRulesSGandCidrAttributes(&group), + resource.TestCheckResourceAttr( + "aws_security_group_rules.web", "ingress.#", "2"), + ), + }, + }, + }) +} + +func TestAccAWSSecurityGroupRules_egressWithPrefixList(t *testing.T) { + var group ec2.SecurityGroup + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSecurityGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSSecurityGroupRulesConfigPrefixListEgress, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSecurityGroupExists("aws_security_group_rules.egress", &group), + testAccCheckAWSSecurityGroupRulesPrefixListAttributes(&group), + resource.TestCheckResourceAttr( + "aws_security_group_rules.egress", "egress.#", "1"), + ), + }, + }, + }) +} + +func testAccCheckAWSSecurityGroupRulesSGandCidrAttributes(group *ec2.SecurityGroup) resource.TestCheckFunc { + return func(s *terraform.State) error { + if len(group.IpPermissions) == 0 { + return fmt.Errorf("No IPPerms") + } + + if len(group.IpPermissions) != 2 { + return fmt.Errorf("Expected 2 ingress rules, got %d", len(group.IpPermissions)) + } + + for _, p := range group.IpPermissions { + if *p.FromPort == int64(22) { + if len(p.IpRanges) != 1 || p.UserIdGroupPairs != nil { + return fmt.Errorf("Found ip perm of 22, but not the right ipranges / pairs: %s", p) + } + continue + } else if *p.FromPort == int64(80) { + if len(p.IpRanges) != 1 || len(p.UserIdGroupPairs) != 1 { + return fmt.Errorf("Found ip perm of 80, but not the right ipranges / pairs: %s", p) + } + continue + } + return fmt.Errorf("Found a rouge rule") + } + + return nil + } +} + +func testAccCheckAWSSecurityGroupRulesPrefixListAttributes(group *ec2.SecurityGroup) resource.TestCheckFunc { + return func(s *terraform.State) error { + if len(group.IpPermissionsEgress) == 0 { + return fmt.Errorf("No egress IPPerms") + } + if len(group.IpPermissionsEgress) != 1 { + return fmt.Errorf("Expected 1 egress rule, got %d", len(group.IpPermissions)) + } + + p := group.IpPermissionsEgress[0] + + if len(p.PrefixListIds) != 1 { + return fmt.Errorf("Expected 1 prefix list, got %d", len(p.PrefixListIds)) + } + + return nil + } +} + +func testAccCheckAWSSecurityGroupRulesAttributesChanged(group *ec2.SecurityGroup) resource.TestCheckFunc { + return func(s *terraform.State) error { + p := []*ec2.IpPermission{ + { + FromPort: aws.Int64(80), + ToPort: aws.Int64(9000), + IpProtocol: aws.String("tcp"), + IpRanges: []*ec2.IpRange{{CidrIp: aws.String("10.0.0.0/8")}}, + }, + { + FromPort: aws.Int64(80), + ToPort: aws.Int64(8000), + IpProtocol: aws.String("tcp"), + IpRanges: []*ec2.IpRange{ + { + CidrIp: aws.String("0.0.0.0/0"), + }, + { + CidrIp: aws.String("10.0.0.0/8"), + }, + }, + }, + } + + // Compare our ingress + if len(group.IpPermissions) != 2 { + return fmt.Errorf( + "Got:\n\n%#v\n\nExpected:\n\n%#v\n", + group.IpPermissions, + p) + } + + if *group.IpPermissions[0].ToPort == 8000 { + group.IpPermissions[1], group.IpPermissions[0] = + group.IpPermissions[0], group.IpPermissions[1] + } + + if !reflect.DeepEqual(group.IpPermissions, p) { + return fmt.Errorf( + "Got:\n\n%#v\n\nExpected:\n\n%#v\n", + group.IpPermissions, + p) + } + + return nil + } +} + +func TestAccAWSSecurityGroupRules_failWithDiffMismatch(t *testing.T) { + var group ec2.SecurityGroup + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSecurityGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSSecurityGroupRulesConfig_failWithDiffMismatch, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSecurityGroupExists("aws_security_group_rules.nat", &group), + ), + }, + }, + }) +} + +func TestAccAWSSecurityGroupRules_Destroy(t *testing.T) { + var group ec2.SecurityGroup + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + IDRefreshName: "aws_security_group_rules.web", + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSecurityGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSSecurityGroupRulesConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSecurityGroupExists("aws_security_group.web", &group), + ), + }, + { + Config: testAccAWSSecurityGroupRulesDestroyConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSecurityGroupRulesEmpty("aws_security_group.web", &group), + ), + }, + }, + }) +} + +func TestAccAWSSecurityGroupRules_Empty(t *testing.T) { + var group ec2.SecurityGroup + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + IDRefreshName: "aws_security_group_rules.web", + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSecurityGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSSecurityGroupRulesConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSecurityGroupExists("aws_security_group_rules.web", &group), + ), + }, + { + Config: testAccAWSSecurityGroupRulesEmptyConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSecurityGroupRulesEmpty("aws_security_group_rules.web", &group), + ), + }, + }, + }) +} + +func TestAccAWSSecurityGroupRules_ConflictEmpty(t *testing.T) { + var group ec2.SecurityGroup + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + IDRefreshName: "aws_security_group_rules.web", + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSecurityGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSSecurityGroupRulesConflictEmptyConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSecurityGroupExists("aws_security_group_rules.web", &group), + ), + ExpectNonEmptyPlan: true, + }, + { + Config: testAccAWSSecurityGroupRulesConflictEmptyConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSecurityGroupRulesEmpty("aws_security_group_rules.web", &group), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +const testAccAWSSecurityGroupRulesConfigIpv6 = ` +resource "aws_vpc" "foo" { + cidr_block = "10.1.0.0/16" +} + +resource "aws_security_group" "web" { + name = "terraform_acceptance_test_example" + description = "Used in the terraform acceptance tests" + vpc_id = "${aws_vpc.foo.id}" + + tags { + Name = "tf-acc-test" + } +} + +resource "aws_security_group_rules" "web" { + security_group_id = "${aws_security_group.web.id}" + + ingress { + protocol = "6" + from_port = 80 + to_port = 8000 + ipv6_cidr_blocks = ["::/0"] + } + + egress { + protocol = "tcp" + from_port = 80 + to_port = 8000 + ipv6_cidr_blocks = ["::/0"] + } +} +` + +const testAccAWSSecurityGroupRulesConfig = ` +resource "aws_vpc" "foo" { + cidr_block = "10.1.0.0/16" +} + +resource "aws_security_group" "web" { + name = "terraform_acceptance_test_example" + description = "Used in the terraform acceptance tests" + vpc_id = "${aws_vpc.foo.id}" + + tags { + Name = "tf-acc-test" + } +} + +resource "aws_security_group_rules" "web" { + security_group_id = "${aws_security_group.web.id}" + + ingress { + protocol = "6" + from_port = 80 + to_port = 8000 + cidr_blocks = ["10.0.0.0/8"] + } + + egress { + protocol = "tcp" + from_port = 80 + to_port = 8000 + cidr_blocks = ["10.0.0.0/8"] + } +} +` + +const testAccAWSSecurityGroupRulesDestroyConfig = ` +resource "aws_vpc" "foo" { + cidr_block = "10.1.0.0/16" +} + +resource "aws_security_group" "web" { + name = "terraform_acceptance_test_example" + description = "Used in the terraform acceptance tests" + vpc_id = "${aws_vpc.foo.id}" + + tags { + Name = "tf-acc-test" + } +} +` + +const testAccAWSSecurityGroupRulesEmptyConfig = ` +resource "aws_vpc" "foo" { + cidr_block = "10.1.0.0/16" +} + +resource "aws_security_group" "web" { + name = "terraform_acceptance_test_example" + description = "Used in the terraform acceptance tests" + vpc_id = "${aws_vpc.foo.id}" + + tags { + Name = "tf-acc-test" + } +} + +resource "aws_security_group_rules" "web" { + security_group_id = "${aws_security_group.web.id}" +} +` + +const testAccAWSSecurityGroupRulesConflictEmptyConfig = ` +resource "aws_vpc" "foo" { + cidr_block = "10.1.0.0/16" +} + +resource "aws_security_group" "web" { + name = "terraform_acceptance_test_example" + description = "Used in the terraform acceptance tests" + vpc_id = "${aws_vpc.foo.id}" + + ingress { + protocol = "6" + from_port = 80 + to_port = 8000 + cidr_blocks = ["10.0.0.0/8"] + } + + egress { + protocol = "tcp" + from_port = 80 + to_port = 8000 + cidr_blocks = ["10.0.0.0/8"] + } + + tags { + Name = "tf-acc-test" + } +} + +resource "aws_security_group_rules" "web" { + security_group_id = "${aws_security_group.web.id}" +} +` + +const testAccAWSSecurityGroupRulesConfigChange = ` +resource "aws_vpc" "foo" { + cidr_block = "10.1.0.0/16" +} + +resource "aws_security_group" "web" { + name = "terraform_acceptance_test_example" + description = "Used in the terraform acceptance tests" + vpc_id = "${aws_vpc.foo.id}" + + tags { + Name = "tf-acc-test" + } +} + +resource "aws_security_group_rules" "web" { + security_group_id = "${aws_security_group.web.id}" + + ingress { + protocol = "tcp" + from_port = 80 + to_port = 9000 + cidr_blocks = ["10.0.0.0/8"] + } + + ingress { + protocol = "tcp" + from_port = 80 + to_port = 8000 + cidr_blocks = ["0.0.0.0/0", "10.0.0.0/8"] + } + + egress { + protocol = "tcp" + from_port = 80 + to_port = 8000 + cidr_blocks = ["10.0.0.0/8"] + } +} +` + +const testAccAWSSecurityGroupRulesConfigSelf = ` +resource "aws_vpc" "foo" { + cidr_block = "10.1.0.0/16" +} + +resource "aws_security_group" "web" { + name = "terraform_acceptance_test_example" + description = "Used in the terraform acceptance tests" + vpc_id = "${aws_vpc.foo.id}" +} + +resource "aws_security_group_rules" "web" { + security_group_id = "${aws_security_group.web.id}" + + ingress { + protocol = "tcp" + from_port = 80 + to_port = 8000 + self = true + } + + egress { + protocol = "tcp" + from_port = 80 + to_port = 8000 + cidr_blocks = ["10.0.0.0/8"] + } +} +` + +const testAccAWSSecurityGroupRulesConfigVpc = ` +resource "aws_vpc" "foo" { + cidr_block = "10.1.0.0/16" +} + +resource "aws_security_group" "web" { + name = "terraform_acceptance_test_example" + description = "Used in the terraform acceptance tests" + vpc_id = "${aws_vpc.foo.id}" +} + +resource "aws_security_group_rules" "web" { + security_group_id = "${aws_security_group.web.id}" + + ingress { + protocol = "tcp" + from_port = 80 + to_port = 8000 + cidr_blocks = ["10.0.0.0/8"] + } + + egress { + protocol = "tcp" + from_port = 80 + to_port = 8000 + cidr_blocks = ["10.0.0.0/8"] + } +} +` + +const testAccAWSSecurityGroupRulesConfigVpcNegOneIngress = ` +resource "aws_vpc" "foo" { + cidr_block = "10.1.0.0/16" +} + +resource "aws_security_group" "web" { + name = "terraform_acceptance_test_example" + description = "Used in the terraform acceptance tests" + vpc_id = "${aws_vpc.foo.id}" +} + +resource "aws_security_group_rules" "web" { + security_group_id = "${aws_security_group.web.id}" + + ingress { + protocol = "-1" + from_port = 0 + to_port = 0 + cidr_blocks = ["10.0.0.0/8"] + } +} +` + +const testAccAWSSecurityGroupRulesConfigVpcProtoNumIngress = ` +resource "aws_vpc" "foo" { + cidr_block = "10.1.0.0/16" +} + +resource "aws_security_group" "web" { + name = "terraform_acceptance_test_example" + description = "Used in the terraform acceptance tests" + vpc_id = "${aws_vpc.foo.id}" +} + +resource "aws_security_group_rules" "web" { + security_group_id = "${aws_security_group.web.id}" + + ingress { + protocol = "50" + from_port = 0 + to_port = 0 + cidr_blocks = ["10.0.0.0/8"] + } +} +` + +const testAccAWSSecurityGroupRulesConfigMultiIngress = ` +resource "aws_vpc" "foo" { + cidr_block = "10.1.0.0/16" +} + +resource "aws_security_group" "worker" { + name = "terraform_acceptance_test_example_1" + description = "Used in the terraform acceptance tests" + vpc_id = "${aws_vpc.foo.id}" +} + +resource "aws_security_group_rules" "worker" { + security_group_id = "${aws_security_group.worker.id}" + + ingress { + protocol = "tcp" + from_port = 80 + to_port = 8000 + cidr_blocks = ["10.0.0.0/8"] + } + + egress { + protocol = "tcp" + from_port = 80 + to_port = 8000 + cidr_blocks = ["10.0.0.0/8"] + } +} + +resource "aws_security_group" "web" { + name = "terraform_acceptance_test_example_2" + description = "Used in the terraform acceptance tests" + vpc_id = "${aws_vpc.foo.id}" +} + +resource "aws_security_group_rules" "web" { + security_group_id = "${aws_security_group.web.id}" + + ingress { + protocol = "tcp" + from_port = 22 + to_port = 22 + cidr_blocks = ["10.0.0.0/8"] + } + + ingress { + protocol = "tcp" + from_port = 800 + to_port = 800 + cidr_blocks = ["10.0.0.0/8"] + } + + ingress { + protocol = "tcp" + from_port = 80 + to_port = 8000 + security_groups = ["${aws_security_group.worker.id}"] + } + + egress { + protocol = "tcp" + from_port = 80 + to_port = 8000 + cidr_blocks = ["10.0.0.0/8"] + } +} +` + +func testAccAWSSecurityGroupRulesConfig_drift() string { + return fmt.Sprintf(` +resource "aws_security_group" "web" { + name = "tf_acc_%d" + description = "Used in the terraform acceptance tests" + + tags { + Name = "tf-acc-test" + } +} + +resource "aws_security_group_rules" "web" { + security_group_id = "${aws_security_group.web.id}" + + ingress { + protocol = "tcp" + from_port = 80 + to_port = 8000 + cidr_blocks = ["10.0.0.0/8"] + } + + ingress { + protocol = "tcp" + from_port = 80 + to_port = 8000 + cidr_blocks = ["206.0.0.0/8"] + } +} +`, acctest.RandInt()) +} + +func testAccAWSSecurityGroupRulesConfig_drift_complex() string { + return fmt.Sprintf(` +resource "aws_vpc" "foo" { + cidr_block = "10.1.0.0/16" +} + +resource "aws_security_group" "otherweb" { + name = "tf_acc_%d" + description = "Used in the terraform acceptance tests" + vpc_id = "${aws_vpc.foo.id}" +} + +resource "aws_security_group" "web" { + name = "tf_acc_%d" + description = "Used in the terraform acceptance tests" + vpc_id = "${aws_vpc.foo.id}" + + tags { + Name = "tf-acc-test" + } +} + +resource "aws_security_group_rules" "web" { + security_group_id = "${aws_security_group.web.id}" + + ingress { + protocol = "tcp" + from_port = 80 + to_port = 8000 + cidr_blocks = ["10.0.0.0/8"] + } + + ingress { + protocol = "tcp" + from_port = 80 + to_port = 8000 + cidr_blocks = ["206.0.0.0/8"] + } + + ingress { + protocol = "tcp" + from_port = 22 + to_port = 22 + security_groups = ["${aws_security_group.otherweb.id}"] + } + + egress { + protocol = "tcp" + from_port = 80 + to_port = 8000 + cidr_blocks = ["206.0.0.0/8"] + } + + egress { + protocol = "tcp" + from_port = 80 + to_port = 8000 + cidr_blocks = ["10.0.0.0/8"] + } + + egress { + protocol = "tcp" + from_port = 22 + to_port = 22 + security_groups = ["${aws_security_group.otherweb.id}"] + } +}`, acctest.RandInt(), acctest.RandInt()) +} + +const testAccAWSSecurityGroupRulesInvalidIngressCidr = ` +resource "aws_security_group" "foo" { + name = "testing-foo" + description = "foo-testing" +} + +resource "aws_security_group_rules" "foo" { + security_group_id = "${aws_security_group.foo.id}" + + ingress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["1.2.3.4/33"] + } +}` + +const testAccAWSSecurityGroupRulesInvalidEgressCidr = ` +resource "aws_security_group" "foo" { + name = "testing-foo" + description = "foo-testing" +} + +resource "aws_security_group_rules" "foo" { + security_group_id = "${aws_security_group.foo.id}" + + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["1.2.3.4/33"] + } +}` + +const testAccAWSSecurityGroupRulesInvalidIPv6IngressCidr = ` +resource "aws_security_group" "foo" { + name = "testing-foo" + description = "foo-testing" +} + +resource "aws_security_group_rules" "foo" { + security_group_id = "${aws_security_group.foo.id}" + + ingress { + from_port = 0 + to_port = 0 + protocol = "-1" + ipv6_cidr_blocks = ["::/244"] + } +}` + +const testAccAWSSecurityGroupRulesInvalidIPv6EgressCidr = ` +resource "aws_security_group" "foo" { + name = "testing-foo" + description = "foo-testing" +} + +resource "aws_security_group_rules" "foo" { + security_group_id = "${aws_security_group.foo.id}" + + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + ipv6_cidr_blocks = ["::/244"] + } +}` + +const testAccAWSSecurityGroupRulesCombindCIDRandGroups = ` +resource "aws_vpc" "foo" { + cidr_block = "10.1.0.0/16" +} + +resource "aws_security_group" "two" { + name = "tf-test-1" + vpc_id = "${aws_vpc.foo.id}" + tags { + Name = "tf-test-1" + } +} + +resource "aws_security_group" "one" { + name = "tf-test-2" + vpc_id = "${aws_vpc.foo.id}" + tags { + Name = "tf-test-w" + } +} + +resource "aws_security_group" "three" { + name = "tf-test-3" + vpc_id = "${aws_vpc.foo.id}" + tags { + Name = "tf-test-3" + } +} + +resource "aws_security_group" "mixed" { + name = "tf-mix-test" + vpc_id = "${aws_vpc.foo.id}" + + tags { + Name = "tf-mix-test" + } +} + +resource "aws_security_group_rules" "mixed" { + security_group_id = "${aws_security_group.mixed.id}" + + ingress { + from_port = 80 + to_port = 80 + protocol = "tcp" + cidr_blocks = ["10.0.0.0/16", "10.1.0.0/16", "10.7.0.0/16"] + + security_groups = [ + "${aws_security_group.one.id}", + "${aws_security_group.two.id}", + "${aws_security_group.three.id}", + ] + } +} +` + +const testAccAWSSecurityGroupRulesConfig_ingressWithCidrAndSGs = ` +resource "aws_vpc" "foo" { + cidr_block = "10.1.0.0/16" +} + +resource "aws_security_group" "other_web" { + name = "tf_other_acc_tests" + description = "Used in the terraform acceptance tests" + vpc_id = "${aws_vpc.foo.id}" + + tags { + Name = "tf-acc-test" + } +} + +resource "aws_security_group" "web" { + name = "terraform_acceptance_test_example" + description = "Used in the terraform acceptance tests" + vpc_id = "${aws_vpc.foo.id}" + + tags { + Name = "tf-acc-test" + } +} + +resource "aws_security_group_rules" "web" { + security_group_id = "${aws_security_group.web.id}" + + ingress { + protocol = "tcp" + from_port = "22" + to_port = "22" + + cidr_blocks = [ + "192.168.0.1/32", + ] + } + + ingress { + protocol = "tcp" + from_port = 80 + to_port = 8000 + cidr_blocks = ["10.0.0.0/8"] + security_groups = ["${aws_security_group.other_web.id}"] + } + + egress { + protocol = "tcp" + from_port = 80 + to_port = 8000 + cidr_blocks = ["10.0.0.0/8"] + } +} +` + +const testAccAWSSecurityGroupRulesConfig_ingressWithCidrAndSGs_classic = ` +provider "aws" { + region = "us-east-1" +} + +resource "aws_security_group" "other_web" { + name = "tf_other_acc_tests" + description = "Used in the terraform acceptance tests" + + tags { + Name = "tf-acc-test" + } +} + +resource "aws_security_group" "web" { + name = "terraform_acceptance_test_example" + description = "Used in the terraform acceptance tests" + + tags { + Name = "tf-acc-test" + } +} + +resource "aws_security_group_rules" "web" { + security_group_id = "${aws_security_group.web.id}" + + ingress { + protocol = "tcp" + from_port = "22" + to_port = "22" + + cidr_blocks = [ + "192.168.0.1/32", + ] + } + + ingress { + protocol = "tcp" + from_port = 80 + to_port = 8000 + cidr_blocks = ["10.0.0.0/8"] + security_groups = ["${aws_security_group.other_web.name}"] + } +} +` + +// fails to apply in one pass with the error "diffs didn't match during apply" +// GH-2027 +const testAccAWSSecurityGroupRulesConfig_failWithDiffMismatch = ` +resource "aws_vpc" "main" { + cidr_block = "10.0.0.0/16" + + tags { + Name = "tf-test" + } +} + +resource "aws_security_group" "ssh_base" { + name = "test-ssh-base" + vpc_id = "${aws_vpc.main.id}" +} + +resource "aws_security_group" "jump" { + name = "test-jump" + vpc_id = "${aws_vpc.main.id}" +} + +resource "aws_security_group" "provision" { + name = "test-provision" + vpc_id = "${aws_vpc.main.id}" +} + +resource "aws_security_group" "nat" { + vpc_id = "${aws_vpc.main.id}" + name = "nat" + description = "For nat servers " +} + +resource "aws_security_group_rules" "nat" { + security_group_id = "${aws_security_group.nat.id}" + + ingress { + from_port = 22 + to_port = 22 + protocol = "tcp" + security_groups = ["${aws_security_group.jump.id}"] + } + + ingress { + from_port = 22 + to_port = 22 + protocol = "tcp" + security_groups = ["${aws_security_group.provision.id}"] + } +} +` + +const testAccAWSSecurityGroupRulesConfigPrefixListEgress = ` +resource "aws_vpc" "tf_sg_prefix_list_egress_test" { + cidr_block = "10.0.0.0/16" + tags { + Name = "tf_sg_prefix_list_egress_test" + } +} + +resource "aws_route_table" "default" { + vpc_id = "${aws_vpc.tf_sg_prefix_list_egress_test.id}" +} + +resource "aws_vpc_endpoint" "s3-us-west-2" { + vpc_id = "${aws_vpc.tf_sg_prefix_list_egress_test.id}" + service_name = "com.amazonaws.us-west-2.s3" + route_table_ids = ["${aws_route_table.default.id}"] + policy = <aws_security_group_rule + > + aws_security_group_rules + + > aws_subnet diff --git a/website/docs/r/security_group.html.markdown b/website/docs/r/security_group.html.markdown index 5b21907be8d..d18da7006b5 100644 --- a/website/docs/r/security_group.html.markdown +++ b/website/docs/r/security_group.html.markdown @@ -10,12 +10,23 @@ description: |- Provides a security group resource. -~> **NOTE on Security Groups and Security Group Rules:** Terraform currently -provides both a standalone [Security Group Rule resource](security_group_rule.html) (a single `ingress` or -`egress` rule), and a Security Group resource with `ingress` and `egress` rules -defined in-line. At this time you cannot use a Security Group with in-line rules -in conjunction with any Security Group Rule resources. Doing so will cause -a conflict of rule settings and will overwrite rules. +~> **NOTE on Security Groups and Security Group Rules:** Terraform currently provides +a standalone [Security Group Rules resource](security_group_rules.html) +(all `ingress` and `egress` rules for a group), +standalone [Security Group Rule resources](security_group_rule.html) +(individual `ingress` or `egress` rules), and the ability to define +`ingress` and `egress` rules in-line with +this Security Group resource. +At this time, you cannot combine any of these methods to define rules for the same group. +Doing so will cause a conflict of rule settings and will overwrite rules. + +~> **NOTE on Limitations of Security Groups with In-Line Rules:** AWS supports +security group rules that refer to other security groups. It is possible to have +two security groups refer to each other in their rules. However, if this is done +using only Security Group resources with in-line rules, it may result in a +circular dependency error. To get around this, consider using the standalone +[Security Group Rules resource](security_group_rules.html) or +[Security Group Rule resources](security_group_rule.html) instead of in-line rules. ## Example Usage @@ -77,8 +88,10 @@ assign a random, unique name to classify your security groups in a way that can be updated, use `tags`. * `ingress` - (Optional) Can be specified multiple times for each ingress rule. Each ingress block supports fields documented below. + If no ingress blocks are defined, then ingress rules will not be managed by this resource. * `egress` - (Optional, VPC only) Can be specified multiple times for each - egress rule. Each egress block supports fields documented below. + egress rule. Each egress block supports fields documented below. + If no egress blocks are defined, then egress rules will not be managed by this resource. * `vpc_id` - (Optional, Forces new resource) The VPC ID. * `tags` - (Optional) A mapping of tags to assign to the resource. diff --git a/website/docs/r/security_group_rule.html.markdown b/website/docs/r/security_group_rule.html.markdown index bdf70c4b600..44f019295aa 100644 --- a/website/docs/r/security_group_rule.html.markdown +++ b/website/docs/r/security_group_rule.html.markdown @@ -3,7 +3,7 @@ layout: "aws" page_title: "AWS: aws_security_group_rule" sidebar_current: "docs-aws-resource-security-group-rule" description: |- - Provides an security group rule resource. + Provides a security group rule resource. --- # aws\_security\_group\_rule @@ -11,12 +11,23 @@ description: |- Provides a security group rule resource. Represents a single `ingress` or `egress` group rule, which can be added to external Security Groups. -~> **NOTE on Security Groups and Security Group Rules:** Terraform currently -provides both a standalone Security Group Rule resource (a single `ingress` or -`egress` rule), and a [Security Group resource](security_group.html) with `ingress` and `egress` rules -defined in-line. At this time you cannot use a Security Group with in-line rules -in conjunction with any Security Group Rule resources. Doing so will cause -a conflict of rule settings and will overwrite rules. +~> **NOTE on Security Groups and Security Group Rules:** Terraform currently provides +a standalone [Security Group Rules resource](security_group_rules.html) +(all `ingress` and `egress` rules for a group), +this standalone Security Group Rule resource +(individual `ingress` or `egress` rules), and the ability to define +`ingress` and `egress` rules in-line with +a [Security Group resource](security_group.html). +At this time, you cannot combine any of these methods to define rules for the same group. +Doing so will cause a conflict of rule settings and will overwrite rules. + +~> **NOTE on Limitations of Security Group Rule Resources:** In AWS, security group +rules do not have a unique identity. Terraform tracks the rule according to its +exact state. As long as the rule is not changed outside of Terraform, the rule will +be managed by Terraform as you would expect. However, if it is changed or if other +rules are added outside of Terraform, then Terraform will not touch those rules. If +this limitation is problematic, you may want to consider using a +[Security Group Rules resource](security_group_rules.html) instead. ## Example Usage diff --git a/website/docs/r/security_group_rules.html.markdown b/website/docs/r/security_group_rules.html.markdown new file mode 100644 index 00000000000..569cc940dec --- /dev/null +++ b/website/docs/r/security_group_rules.html.markdown @@ -0,0 +1,117 @@ +--- +layout: "aws" +page_title: "AWS: aws_security_group_rules" +sidebar_current: "docs-aws-resource-security-group-rules" +description: |- + Provides a security group rules resource. +--- + +# aws\_security\_group\_rules + +Provides a security group rules resource. Represents all the `ingress` and `egress` +rules that should exist for a given Security Group. + +~> **NOTE on Security Groups and Security Group Rules:** Terraform currently provides +this standalone Security Group Rules resource +(all `ingress` and `egress` rules for a group), +standalone [Security Group Rule resources](security_group_rule.html) +(individual `ingress` or `egress` rules), and the ability to define +`ingress` and `egress` rules in-line with +a [Security Group resource](security_group.html). +At this time, you cannot combine any of these methods to define rules for the same group. +Doing so will cause a conflict of rule settings and will overwrite rules. + +## Example Usage + +Basic usage + +```hcl +resource "aws_security_group_rules" "allow_all" { + security_group_id = "sg-123456" + + ingress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + prefix_list_ids = ["pl-12c4e678"] + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `security_group_id` - (Required) The security group to apply these rules to. +* `ingress` - (Optional) Can be specified multiple times for each + ingress rule. Each ingress block supports fields documented below. + If no ingress blocks are defined, then Terraform will remove all ingress rules. +* `egress` - (Optional, VPC only) Can be specified multiple times for each + egress rule. Each egress block supports fields documented below. + If no egress blocks are defined, then Terraform will remove all egress rules. + +The `ingress` block supports: + +* `cidr_blocks` - (Optional) List of CIDR blocks. +* `ipv6_cidr_blocks` - (Optional) List of IPv6 CIDR blocks. +* `from_port` - (Required) The start port (or ICMP type number if protocol is "icmp") +* `protocol` - (Required) The protocol. If you select a protocol of +"-1" (semantically equivalent to `"all"`, which is not a valid value here), you must specify a "from_port" and "to_port" equal to 0. If not icmp, tcp, udp, or "-1" use the [protocol number](https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml) +* `security_groups` - (Optional) List of security group Group Names if using + EC2-Classic, or Group IDs if using a VPC. +* `self` - (Optional) If true, the security group itself will be added as + a source to this ingress rule. +* `to_port` - (Required) The end range port (or ICMP code if protocol is "icmp"). + +The `egress` block supports: + +* `cidr_blocks` - (Optional) List of CIDR blocks. +* `ipv6_cidr_blocks` - (Optional) List of IPv6 CIDR blocks. +* `prefix_list_ids` - (Optional) List of prefix list IDs (for allowing access to VPC endpoints) +* `from_port` - (Required) The start port (or ICMP type number if protocol is "icmp") +* `protocol` - (Required) The protocol. If you select a protocol of +"-1" (semantically equivalent to `"all"`, which is not a valid value here), you must specify a "from_port" and "to_port" equal to 0. If not icmp, tcp, udp, or "-1" use the [protocol number](https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml) +* `security_groups` - (Optional) List of security group Group Names if using + EC2-Classic, or Group IDs if using a VPC. +* `self` - (Optional) If true, the security group itself will be added as + a source to this egress rule. +* `to_port` - (Required) The end range port (or ICMP code if protocol is "icmp"). + +## Usage with prefix list IDs + +Prefix list IDs are managed by AWS internally. Prefix list IDs +are associated with a prefix list name, or service name, that is linked to a specific region. +Prefix list IDs are exported on VPC Endpoints, so you can use this format: + +```hcl +resource "aws_security_group_rules" "allow_all" { + security_group_id = "sg-123456" + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + prefix_list_ids = ["${aws_vpc_endpoint.my_endpoint.prefix_list_id}"] + } +} + +# ... +resource "aws_vpc_endpoint" "my_endpoint" { + # ... +} +``` + +## Attributes Reference + +The following attributes are exported: + +* `id` - The ID of the security group +* `ingress` - The ingress rules. See above for more. +* `egress` - The egress rules. See above for more.