diff --git a/.changelog/15011.txt b/.changelog/15011.txt new file mode 100644 index 000000000000..99810e79b322 --- /dev/null +++ b/.changelog/15011.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +resource/aws_security_group: Additional plan-time validation for `name` and `name_prefix` +``` \ No newline at end of file diff --git a/.changelog/24340.txt b/.changelog/24340.txt new file mode 100644 index 000000000000..24cadc229c27 --- /dev/null +++ b/.changelog/24340.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +resource/aws_security_group_rule: Add configurable Create timeout +``` \ No newline at end of file diff --git a/internal/service/ec2/consts.go b/internal/service/ec2/consts.go index 7e24d9bf34b4..89e273860632 100644 --- a/internal/service/ec2/consts.go +++ b/internal/service/ec2/consts.go @@ -249,3 +249,15 @@ func outsideIPAddressType_Values() []string { OutsideIPAddressTypePublicIPv4, } } + +const ( + securityGroupRuleTypeEgress = "egress" + securityGroupRuleTypeIngress = "ingress" +) + +func securityGroupRuleType_Values() []string { + return []string{ + securityGroupRuleTypeEgress, + securityGroupRuleTypeIngress, + } +} diff --git a/internal/service/ec2/errors.go b/internal/service/ec2/errors.go index 6489b37ead32..6b0abe6f9099 100644 --- a/internal/service/ec2/errors.go +++ b/internal/service/ec2/errors.go @@ -36,6 +36,7 @@ const ( errCodeInvalidFleetIdNotFound = "InvalidFleetId.NotFound" errCodeInvalidFlowLogIdNotFound = "InvalidFlowLogId.NotFound" errCodeInvalidGatewayIDNotFound = "InvalidGatewayID.NotFound" + errCodeInvalidGroupInUse = "InvalidGroup.InUse" errCodeInvalidGroupNotFound = "InvalidGroup.NotFound" errCodeInvalidHostIDNotFound = "InvalidHostID.NotFound" errCodeInvalidInstanceIDNotFound = "InvalidInstanceID.NotFound" diff --git a/internal/service/ec2/vpc_default_network_acl.go b/internal/service/ec2/vpc_default_network_acl.go index f2db0933e5d6..c5864d1b3264 100644 --- a/internal/service/ec2/vpc_default_network_acl.go +++ b/internal/service/ec2/vpc_default_network_acl.go @@ -21,10 +21,10 @@ const ( ) func ResourceDefaultNetworkACL() *schema.Resource { - networkACLRuleSetSchema := &schema.Schema{ + networkACLRuleSetNestedBlock := &schema.Schema{ Type: schema.TypeSet, Optional: true, - Elem: networkACLRuleResource, + Elem: networkACLRuleNestedBlock, Set: networkACLRuleHash, } @@ -60,8 +60,8 @@ func ResourceDefaultNetworkACL() *schema.Resource { // We want explicit management of Rules here, so we do not allow them to be // computed. Instead, an empty config will enforce just that; removal of the // rules - "egress": networkACLRuleSetSchema, - "ingress": networkACLRuleSetSchema, + "egress": networkACLRuleSetNestedBlock, + "ingress": networkACLRuleSetNestedBlock, "owner_id": { Type: schema.TypeString, Computed: true, diff --git a/internal/service/ec2/vpc_default_security_group.go b/internal/service/ec2/vpc_default_security_group.go index 1bbdfd62994d..28a363a64a94 100644 --- a/internal/service/ec2/vpc_default_security_group.go +++ b/internal/service/ec2/vpc_default_security_group.go @@ -1,20 +1,13 @@ package ec2 import ( - "bytes" "fmt" - "log" - "sort" "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/arn" "github.com/aws/aws-sdk-go/service/ec2" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "github.com/hashicorp/terraform-provider-aws/internal/conns" - "github.com/hashicorp/terraform-provider-aws/internal/create" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" - "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/internal/verify" ) @@ -22,18 +15,23 @@ func ResourceDefaultSecurityGroup() *schema.Resource { //lintignore:R011 return &schema.Resource{ Create: resourceDefaultSecurityGroupCreate, - Read: resourceDefaultSecurityGroupRead, - Update: resourceDefaultSecurityGroupUpdate, - Delete: resourceDefaultSecurityGroupDelete, + Read: resourceSecurityGroupRead, + Update: resourceSecurityGroupUpdate, + Delete: schema.Noop, + Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, - SchemaVersion: 1, - MigrateState: DefaultSecurityGroupMigrateState, + SchemaVersion: 1, // Keep in sync with aws_security_group's schema version. + MigrateState: SecurityGroupMigrateState, + // Keep in sync with aws_security_group's schema with the following changes: + // - description is Computed-only + // - name is Computed-only + // - name_prefix is Computed-only Schema: map[string]*schema.Schema{ - "name": { + "arn": { Type: schema.TypeString, Computed: true, }, @@ -41,135 +39,13 @@ func ResourceDefaultSecurityGroup() *schema.Resource { Type: schema.TypeString, Computed: true, }, - "vpc_id": { + "egress": securityGroupRuleSetNestedBlock, + "ingress": securityGroupRuleSetNestedBlock, + "name": { Type: schema.TypeString, - Optional: true, - ForceNew: true, Computed: true, }, - "ingress": { - Type: schema.TypeSet, - Optional: true, - Computed: true, - ConfigMode: schema.SchemaConfigModeAttr, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "cidr_blocks": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateFunc: verify.ValidCIDRNetworkAddress, - }, - }, - "description": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validSecurityGroupRuleDescription, - }, - "from_port": { - Type: schema.TypeInt, - Required: true, - }, - "ipv6_cidr_blocks": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateFunc: verify.ValidCIDRNetworkAddress, - }, - }, - "prefix_list_ids": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - "protocol": { - Type: schema.TypeString, - Required: true, - StateFunc: ProtocolStateFunc, - }, - "security_groups": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, - Set: schema.HashString, - }, - "self": { - Type: schema.TypeBool, - Optional: true, - Default: false, - }, - "to_port": { - Type: schema.TypeInt, - Required: true, - }, - }, - }, - Set: resourceDefaultSecurityGroupRuleHash, - }, - "egress": { - Type: schema.TypeSet, - Optional: true, - Computed: true, - ConfigMode: schema.SchemaConfigModeAttr, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "cidr_blocks": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateFunc: verify.ValidCIDRNetworkAddress, - }, - }, - "description": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validSecurityGroupRuleDescription, - }, - "from_port": { - Type: schema.TypeInt, - Required: true, - }, - "ipv6_cidr_blocks": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateFunc: verify.ValidCIDRNetworkAddress, - }, - }, - "prefix_list_ids": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - "protocol": { - Type: schema.TypeString, - Required: true, - StateFunc: ProtocolStateFunc, - }, - "security_groups": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, - Set: schema.HashString, - }, - "self": { - Type: schema.TypeBool, - Optional: true, - Default: false, - }, - "to_port": { - Type: schema.TypeInt, - Required: true, - }, - }, - }, - Set: resourceDefaultSecurityGroupRuleHash, - }, - "arn": { + "name_prefix": { Type: schema.TypeString, Computed: true, }, @@ -177,14 +53,20 @@ func ResourceDefaultSecurityGroup() *schema.Resource { Type: schema.TypeString, Computed: true, }, - "tags": tftags.TagsSchema(), - "tags_all": tftags.TagsSchemaComputed(), - // This is not implemented. Added to prevent breaking changes. + // Not used. "revoke_rules_on_delete": { Type: schema.TypeBool, Default: false, Optional: true, }, + "tags": tftags.TagsSchema(), + "tags_all": tftags.TagsSchemaComputed(), + "vpc_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + }, }, CustomizeDiff: verify.SetTagsDiff, @@ -194,299 +76,50 @@ func ResourceDefaultSecurityGroup() *schema.Resource { func resourceDefaultSecurityGroupCreate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*conns.AWSClient).EC2Conn defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig - tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{}))) - securityGroupOpts := &ec2.DescribeSecurityGroupsInput{ - Filters: []*ec2.Filter{ - { - Name: aws.String("group-name"), - Values: aws.StringSlice([]string{DefaultSecurityGroupName}), + ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig + + input := &ec2.DescribeSecurityGroupsInput{ + Filters: BuildAttributeFilterList( + map[string]string{ + "group-name": DefaultSecurityGroupName, }, - }, + ), } - var vpcID string if v, ok := d.GetOk("vpc_id"); ok { - vpcID = v.(string) - securityGroupOpts.Filters = append(securityGroupOpts.Filters, &ec2.Filter{ - Name: aws.String("vpc-id"), - Values: aws.StringSlice([]string{vpcID}), - }) - } - - var err error - log.Printf("[DEBUG] Commandeer Default Security Group: %s", securityGroupOpts) - resp, err := conn.DescribeSecurityGroups(securityGroupOpts) - if err != nil { - return fmt.Errorf("Error creating Default Security Group: %w", err) - } - - var g *ec2.SecurityGroup - if vpcID != "" { - // if vpcId contains a value, then we expect just a single Security Group - // returned, as default is a protected name for each VPC, and for each - // Region on EC2 Classic - if len(resp.SecurityGroups) != 1 { - return fmt.Errorf("Error finding default security group; found (%d) groups: %s", len(resp.SecurityGroups), resp) - } - g = resp.SecurityGroups[0] + input.Filters = append(input.Filters, BuildAttributeFilterList( + map[string]string{ + "vpc-id": v.(string), + }, + )...) } else { - // we need to filter through any returned security groups for the group - // named "default", and does not belong to a VPC - for _, sg := range resp.SecurityGroups { - if sg.VpcId == nil && aws.StringValue(sg.GroupName) == DefaultSecurityGroupName { - g = sg - break - } - } - } - - if g == nil { - return fmt.Errorf("Error finding default security group: no matching group found") - } - - d.SetId(aws.StringValue(g.GroupId)) - - log.Printf("[INFO] Default Security Group ID: %s", d.Id()) - - if len(tags) > 0 { - if err := CreateTags(conn, d.Id(), tags); err != nil { - return fmt.Errorf("error adding EC2 Default Security Group (%s) tags: %w", d.Id(), err) - } - } - - if err := revokeDefaultSecurityGroupRules(meta, g); err != nil { - return err - } - - return resourceDefaultSecurityGroupUpdate(d, meta) -} - -func resourceDefaultSecurityGroupRead(d *schema.ResourceData, meta interface{}) error { - conn := meta.(*conns.AWSClient).EC2Conn - defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig - ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig - - group, err := FindSecurityGroupByID(conn, d.Id()) - if !d.IsNewResource() && tfresource.NotFound(err) { - log.Printf("[WARN] Security group (%s) not found, removing from state", d.Id()) - d.SetId("") - return nil - } - if err != nil { - return err - } - - remoteIngressRules := SecurityGroupIPPermGather(d.Id(), group.IpPermissions, group.OwnerId) - remoteEgressRules := SecurityGroupIPPermGather(d.Id(), group.IpPermissionsEgress, group.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) - - sgArn := arn.ARN{ - AccountID: aws.StringValue(group.OwnerId), - Partition: meta.(*conns.AWSClient).Partition, - Region: meta.(*conns.AWSClient).Region, - Resource: fmt.Sprintf("security-group/%s", aws.StringValue(group.GroupId)), - Service: ec2.ServiceName, - } - - d.Set("arn", sgArn.String()) - d.Set("description", group.Description) - d.Set("name", group.GroupName) - d.Set("owner_id", group.OwnerId) - d.Set("vpc_id", group.VpcId) - - if err := d.Set("ingress", ingressRules); err != nil { - return fmt.Errorf("error setting Ingress rule set for (%s): %w", d.Id(), err) - } - - if err := d.Set("egress", egressRules); err != nil { - return fmt.Errorf("error setting Egress rule set for (%s): %w", d.Id(), err) - } - - tags := KeyValueTags(group.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig) - - //lintignore:AWSR002 - if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { - return fmt.Errorf("error setting tags: %w", err) - } - - if err := d.Set("tags_all", tags.Map()); err != nil { - return fmt.Errorf("error setting tags_all: %w", err) + input.Filters = append(input.Filters, BuildAttributeFilterList( + map[string]string{ + "description": "default group", + }, + )...) } - return nil -} + sg, err := FindSecurityGroup(conn, input) -func resourceDefaultSecurityGroupUpdate(d *schema.ResourceData, meta interface{}) error { - conn := meta.(*conns.AWSClient).EC2Conn - - group, err := FindSecurityGroupByID(conn, d.Id()) if err != nil { - return fmt.Errorf("error updating Default Security Group (%s): %w", d.Id(), err) - } - - err = resourceSecurityGroupUpdateRules(d, "ingress", meta, group) - if err != nil { - return fmt.Errorf("error updating Default Security Group (%s): %w", d.Id(), err) - } - - if d.Get("vpc_id") != nil { - err = resourceSecurityGroupUpdateRules(d, "egress", meta, group) - if err != nil { - return fmt.Errorf("error updating Default Security Group (%s): %w", d.Id(), err) - } - } - - if d.HasChange("tags_all") && !d.IsNewResource() { - o, n := d.GetChange("tags_all") - - if err := UpdateTags(conn, d.Id(), o, n); err != nil { - return fmt.Errorf("error updating Default Security Group (%s) tags: %w", d.Id(), err) - } - } - - return resourceDefaultSecurityGroupRead(d, meta) -} - -func resourceDefaultSecurityGroupDelete(d *schema.ResourceData, meta interface{}) error { - log.Printf("[WARN] Cannot destroy Default Security Group. Terraform will remove this resource from the state file, however resources may remain.") - return nil -} - -func revokeDefaultSecurityGroupRules(meta interface{}, g *ec2.SecurityGroup) error { - conn := meta.(*conns.AWSClient).EC2Conn - - groupID := aws.StringValue(g.GroupId) - log.Printf("[WARN] Removing all ingress and egress rules found on Default Security Group (%s)", groupID) - if len(g.IpPermissionsEgress) > 0 { - req := &ec2.RevokeSecurityGroupEgressInput{ - GroupId: g.GroupId, - IpPermissions: g.IpPermissionsEgress, - } - - log.Printf("[DEBUG] Revoking default egress rules for Default Security Group for %s", groupID) - if _, err := conn.RevokeSecurityGroupEgress(req); err != nil { - return fmt.Errorf("error revoking default egress rules for Default Security Group (%s): %w", groupID, err) - } - } - if len(g.IpPermissions) > 0 { - // a limitation in EC2 Classic is that a call to RevokeSecurityGroupIngress - // cannot contain both the GroupName and the GroupId - for _, p := range g.IpPermissions { - for _, uigp := range p.UserIdGroupPairs { - if uigp.GroupId != nil && uigp.GroupName != nil { - uigp.GroupName = nil - } - } - } - req := &ec2.RevokeSecurityGroupIngressInput{ - GroupId: g.GroupId, - IpPermissions: g.IpPermissions, - } - - log.Printf("[DEBUG] Revoking default ingress rules for Default Security Group for (%s): %s", groupID, req) - if _, err := conn.RevokeSecurityGroupIngress(req); err != nil { - return fmt.Errorf("Error revoking default ingress rules for Default Security Group (%s): %w", groupID, err) - } + return fmt.Errorf("reading Default Security Group: %w", err) } - return nil -} - -func resourceDefaultSecurityGroupRuleHash(v interface{}) int { - var buf bytes.Buffer - m := v.(map[string]interface{}) - buf.WriteString(fmt.Sprintf("%d-", m["from_port"].(int))) - buf.WriteString(fmt.Sprintf("%d-", m["to_port"].(int))) - p := ProtocolForValue(m["protocol"].(string)) - buf.WriteString(fmt.Sprintf("%s-", p)) - buf.WriteString(fmt.Sprintf("%t-", m["self"].(bool))) - - // We need to make sure to sort the strings below so that we always - // generate the same hash code no matter what is in the set. - if v, ok := m["cidr_blocks"]; ok { - vs := v.([]interface{}) - s := make([]string, len(vs)) - for i, raw := range vs { - s[i] = raw.(string) - } - sort.Strings(s) - - for _, v := range s { - buf.WriteString(fmt.Sprintf("%s-", v)) - } - } - if v, ok := m["ipv6_cidr_blocks"]; ok { - vs := v.([]interface{}) - s := make([]string, len(vs)) - for i, raw := range vs { - s[i] = raw.(string) - } - sort.Strings(s) - - for _, v := range s { - buf.WriteString(fmt.Sprintf("%s-", v)) - } - } - if v, ok := m["prefix_list_ids"]; ok { - vs := v.([]interface{}) - s := make([]string, len(vs)) - for i, raw := range vs { - s[i] = raw.(string) - } - sort.Strings(s) + d.SetId(aws.StringValue(sg.GroupId)) - for _, v := range s { - buf.WriteString(fmt.Sprintf("%s-", v)) - } - } - if v, ok := m["security_groups"]; ok { - vs := v.(*schema.Set).List() - s := make([]string, len(vs)) - for i, raw := range vs { - s[i] = raw.(string) - } - sort.Strings(s) + oTagsAll := KeyValueTags(sg.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig) + nTagsAll := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{}))) - for _, v := range s { - buf.WriteString(fmt.Sprintf("%s-", v)) + if !nTagsAll.Equal(oTagsAll) { + if err := UpdateTags(conn, d.Id(), oTagsAll.Map(), nTagsAll.Map()); err != nil { + return fmt.Errorf("updating Default Security Group (%s) tags: %w", d.Id(), err) } } - if m["description"].(string) != "" { - buf.WriteString(fmt.Sprintf("%s-", m["description"].(string))) - } - return create.StringHashcode(buf.String()) -} - -func DefaultSecurityGroupMigrateState( - v int, is *terraform.InstanceState, meta interface{}) (*terraform.InstanceState, error) { - switch v { - case 0: - log.Println("[INFO] Found AWS Default Security Group state v0; migrating to v1") - return migrateDefaultSecurityGroupStateV0toV1(is) - default: - return is, fmt.Errorf("Unexpected schema version: %d", v) - } -} - -func migrateDefaultSecurityGroupStateV0toV1(is *terraform.InstanceState) (*terraform.InstanceState, error) { - if is.Empty() || is.Attributes == nil { - log.Println("[DEBUG] Empty InstanceState; nothing to migrate.") - return is, nil + if err := forceRevokeSecurityGroupRules(conn, d.Id()); err != nil { + return err } - log.Printf("[DEBUG] Attributes before migration: %#v", is.Attributes) - - // set default for revoke_rules_on_delete - is.Attributes["revoke_rules_on_delete"] = "false" - - log.Printf("[DEBUG] Attributes after migration: %#v", is.Attributes) - return is, nil + return resourceSecurityGroupUpdate(d, meta) } diff --git a/internal/service/ec2/vpc_default_security_group_test.go b/internal/service/ec2/vpc_default_security_group_test.go index 95d76b35cbce..d77047062bec 100644 --- a/internal/service/ec2/vpc_default_security_group_test.go +++ b/internal/service/ec2/vpc_default_security_group_test.go @@ -6,15 +6,15 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" + sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "github.com/hashicorp/terraform-provider-aws/internal/acctest" - "github.com/hashicorp/terraform-provider-aws/internal/conns" - tfec2 "github.com/hashicorp/terraform-provider-aws/internal/service/ec2" ) func TestAccVPCDefaultSecurityGroup_VPC_basic(t *testing.T) { var group ec2.SecurityGroup + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_default_security_group.test" vpcResourceName := "aws_vpc.test" @@ -22,12 +22,12 @@ func TestAccVPCDefaultSecurityGroup_VPC_basic(t *testing.T) { PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), ProviderFactories: acctest.ProviderFactories, - CheckDestroy: testAccCheckDefaultSecurityGroupDestroy, + CheckDestroy: acctest.CheckDestroyNoop, Steps: []resource.TestStep{ { - Config: testAccVPCDefaultSecurityGroupConfig_basic, + Config: testAccVPCDefaultSecurityGroupConfig_basic(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckDefaultSecurityGroupExists(resourceName, &group), + testAccCheckSecurityGroupExists(resourceName, &group), resource.TestCheckResourceAttr(resourceName, "name", "default"), resource.TestCheckResourceAttr(resourceName, "description", "default VPC security group"), resource.TestCheckResourceAttrPair(resourceName, "vpc_id", vpcResourceName, "id"), @@ -49,12 +49,11 @@ func TestAccVPCDefaultSecurityGroup_VPC_basic(t *testing.T) { }), testAccCheckDefaultSecurityGroupARN(resourceName, &group), acctest.CheckResourceAttrAccountID(resourceName, "owner_id"), - resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), - resource.TestCheckResourceAttr(resourceName, "tags.Name", acctest.ResourcePrefix), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), ), }, { - Config: testAccVPCDefaultSecurityGroupConfig_basic, + Config: testAccVPCDefaultSecurityGroupConfig_basic(rName), PlanOnly: true, }, { @@ -69,20 +68,23 @@ func TestAccVPCDefaultSecurityGroup_VPC_basic(t *testing.T) { func TestAccVPCDefaultSecurityGroup_VPC_empty(t *testing.T) { var group ec2.SecurityGroup + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_default_security_group.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), ProviderFactories: acctest.ProviderFactories, - CheckDestroy: testAccCheckDefaultSecurityGroupDestroy, + CheckDestroy: acctest.CheckDestroyNoop, Steps: []resource.TestStep{ { - Config: testAccVPCDefaultSecurityGroupConfig_empty, + Config: testAccVPCDefaultSecurityGroupConfig_empty(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckDefaultSecurityGroupExists(resourceName, &group), + testAccCheckSecurityGroupExists(resourceName, &group), resource.TestCheckResourceAttr(resourceName, "ingress.#", "0"), resource.TestCheckResourceAttr(resourceName, "egress.#", "0"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.Name", rName), ), }, { @@ -95,20 +97,35 @@ func TestAccVPCDefaultSecurityGroup_VPC_empty(t *testing.T) { }) } -func TestAccVPCDefaultSecurityGroup_Classic_basic(t *testing.T) { +func TestAccVPCDefaultSecurityGroup_Classic_serial(t *testing.T) { + testCases := map[string]func(t *testing.T){ + "basic": testAccVPCDefaultSecurityGroup_Classic_basic, + "empty": testAccVPCDefaultSecurityGroup_Classic_empty, + } + + for name, tc := range testCases { + tc := tc + t.Run(name, func(t *testing.T) { + tc(t) + }) + } +} + +func testAccVPCDefaultSecurityGroup_Classic_basic(t *testing.T) { var group ec2.SecurityGroup + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_default_security_group.test" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckEC2Classic(t) }, ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), ProviderFactories: acctest.ProviderFactories, - CheckDestroy: testAccCheckDefaultSecurityGroupDestroy, + CheckDestroy: acctest.CheckDestroyNoop, Steps: []resource.TestStep{ { - Config: testAccVPCDefaultSecurityGroupConfig_classic(), + Config: testAccVPCDefaultSecurityGroupConfig_classic(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckDefaultSecurityGroupClassicExists(resourceName, &group), + testAccCheckSecurityGroupEC2ClassicExists(resourceName, &group), resource.TestCheckResourceAttr(resourceName, "name", "default"), resource.TestCheckResourceAttr(resourceName, "description", "default group"), resource.TestCheckResourceAttr(resourceName, "vpc_id", ""), @@ -124,15 +141,15 @@ func TestAccVPCDefaultSecurityGroup_Classic_basic(t *testing.T) { testAccCheckDefaultSecurityGroupARNClassic(resourceName, &group), acctest.CheckResourceAttrAccountID(resourceName, "owner_id"), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), - resource.TestCheckResourceAttr(resourceName, "tags.Name", acctest.ResourcePrefix), + resource.TestCheckResourceAttr(resourceName, "tags.Name", rName), ), }, { - Config: testAccVPCDefaultSecurityGroupConfig_classic(), + Config: testAccVPCDefaultSecurityGroupConfig_classic(rName), PlanOnly: true, }, { - Config: testAccVPCDefaultSecurityGroupConfig_classic(), + Config: testAccVPCDefaultSecurityGroupConfig_classic(rName), ResourceName: resourceName, ImportState: true, ImportStateVerify: true, @@ -142,86 +159,29 @@ func TestAccVPCDefaultSecurityGroup_Classic_basic(t *testing.T) { }) } -func TestAccVPCDefaultSecurityGroup_Classic_empty(t *testing.T) { - - acctest.Skip(t, "This resource does not currently clear tags when adopting the resource") - // Additional references: - // * https://github.com/hashicorp/terraform-provider-aws/issues/14631 - +func testAccVPCDefaultSecurityGroup_Classic_empty(t *testing.T) { var group ec2.SecurityGroup resourceName := "aws_default_security_group.test" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckEC2Classic(t) }, ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), ProviderFactories: acctest.ProviderFactories, - CheckDestroy: testAccCheckDefaultSecurityGroupDestroy, + CheckDestroy: acctest.CheckDestroyNoop, Steps: []resource.TestStep{ { Config: testAccVPCDefaultSecurityGroupConfig_classicEmpty(), Check: resource.ComposeTestCheckFunc( - testAccCheckDefaultSecurityGroupClassicExists(resourceName, &group), + testAccCheckSecurityGroupEC2ClassicExists(resourceName, &group), resource.TestCheckResourceAttr(resourceName, "ingress.#", "0"), resource.TestCheckResourceAttr(resourceName, "egress.#", "0"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), ), }, }, }) } -func testAccCheckDefaultSecurityGroupDestroy(s *terraform.State) error { - // We expect Security Group to still exist - return nil -} - -func testAccCheckDefaultSecurityGroupExists(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 EC2 Default Security Group ID is set") - } - - conn := acctest.Provider.Meta().(*conns.AWSClient).EC2Conn - - sg, err := tfec2.FindSecurityGroupByID(conn, rs.Primary.ID) - if err != nil { - return err - } - - *group = *sg - - return nil - } -} - -func testAccCheckDefaultSecurityGroupClassicExists(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 EC2 Default Security Group ID is set") - } - - conn := acctest.ProviderEC2Classic.Meta().(*conns.AWSClient).EC2Conn - - sg, err := tfec2.FindSecurityGroupByID(conn, rs.Primary.ID) - if err != nil { - return err - } - - *group = *sg - - return nil - } -} - func testAccCheckDefaultSecurityGroupARN(resourceName string, group *ec2.SecurityGroup) resource.TestCheckFunc { return func(s *terraform.State) error { return acctest.CheckResourceAttrRegionalARN(resourceName, "arn", "ec2", fmt.Sprintf("security-group/%s", aws.StringValue(group.GroupId)))(s) @@ -234,12 +194,13 @@ func testAccCheckDefaultSecurityGroupARNClassic(resourceName string, group *ec2. } } -const testAccVPCDefaultSecurityGroupConfig_basic = ` +func testAccVPCDefaultSecurityGroupConfig_basic(rName string) string { + return fmt.Sprintf(` resource "aws_vpc" "test" { cidr_block = "10.1.0.0/16" tags = { - Name = "terraform-testacc-default-security-group" + Name = %[1]q } } @@ -259,31 +220,32 @@ resource "aws_default_security_group" "test" { to_port = 8000 cidr_blocks = ["10.0.0.0/8"] } - - tags = { - Name = "tf-acc-test" - } } -` +`, rName) +} -const testAccVPCDefaultSecurityGroupConfig_empty = ` +func testAccVPCDefaultSecurityGroupConfig_empty(rName string) string { + return fmt.Sprintf(` resource "aws_vpc" "test" { cidr_block = "10.1.0.0/16" tags = { - Name = "terraform-testacc-default-security-group" + Name = %[1]q } } resource "aws_default_security_group" "test" { vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } +} +`, rName) } -` -func testAccVPCDefaultSecurityGroupConfig_classic() string { - return acctest.ConfigCompose( - acctest.ConfigEC2ClassicRegionProvider(), - ` +func testAccVPCDefaultSecurityGroupConfig_classic(rName string) string { + return acctest.ConfigCompose(acctest.ConfigEC2ClassicRegionProvider(), fmt.Sprintf(` resource "aws_default_security_group" "test" { ingress { protocol = "6" @@ -293,82 +255,15 @@ resource "aws_default_security_group" "test" { } tags = { - Name = "tf-acc-test" + Name = %[1]q } } -`) +`, rName)) } func testAccVPCDefaultSecurityGroupConfig_classicEmpty() string { - return acctest.ConfigCompose( - acctest.ConfigEC2ClassicRegionProvider(), - ` + return acctest.ConfigCompose(acctest.ConfigEC2ClassicRegionProvider(), ` resource "aws_default_security_group" "test" { # No attributes set. -} -`) -} - -func TestDefaultSecurityGroupMigrateState(t *testing.T) { - cases := map[string]struct { - StateVersion int - Attributes map[string]string - Expected map[string]string - Meta interface{} - }{ - "v0": { - StateVersion: 0, - Attributes: map[string]string{ - "name": "test", - }, - Expected: map[string]string{ - "name": "test", - "revoke_rules_on_delete": "false", - }, - }, - } - - for tn, tc := range cases { - is := &terraform.InstanceState{ - ID: "i-abc123", - Attributes: tc.Attributes, - } - is, err := tfec2.DefaultSecurityGroupMigrateState( - tc.StateVersion, is, tc.Meta) - - if err != nil { - t.Fatalf("bad: %s, err: %#v", tn, err) - } - - for k, v := range tc.Expected { - if is.Attributes[k] != v { - t.Fatalf( - "bad: %s\n\n expected: %#v -> %#v\n got: %#v -> %#v\n in: %#v", - tn, k, v, k, is.Attributes[k], is.Attributes) - } - } - } -} - -func TestDefaultSecurityGroupMigrateState_empty(t *testing.T) { - var is *terraform.InstanceState - var meta interface{} - - // should handle nil - is, err := tfec2.DefaultSecurityGroupMigrateState(0, is, meta) - - if err != nil { - t.Fatalf("err: %#v", err) - } - if is != nil { - t.Fatalf("expected nil instancestate, got: %#v", is) - } - - // should handle non-nil but empty - is = &terraform.InstanceState{} - _, err = tfec2.DefaultSecurityGroupMigrateState(0, is, meta) - - if err != nil { - t.Fatalf("err: %#v", err) - } +}`) } diff --git a/internal/service/ec2/vpc_default_subnet.go b/internal/service/ec2/vpc_default_subnet.go index 9a0a8151e201..40e3b028c8f0 100644 --- a/internal/service/ec2/vpc_default_subnet.go +++ b/internal/service/ec2/vpc_default_subnet.go @@ -218,7 +218,7 @@ func resourceDefaultSubnetCreate(d *schema.ResourceData, meta interface{}) error computedIPv6CIDRBlock = true } } else { - return fmt.Errorf("error reading EC2 Default Subnet (%s): %w", d.Id(), err) + return fmt.Errorf("reading EC2 Default Subnet (%s): %w", availabilityZone, err) } if err := modifySubnetAttributesOnCreate(conn, d, subnet, computedIPv6CIDRBlock); err != nil { diff --git a/internal/service/ec2/vpc_network_acl.go b/internal/service/ec2/vpc_network_acl.go index 148c6ae6fd2e..4d950e0bb80f 100644 --- a/internal/service/ec2/vpc_network_acl.go +++ b/internal/service/ec2/vpc_network_acl.go @@ -21,12 +21,12 @@ import ( ) func ResourceNetworkACL() *schema.Resource { - networkACLRuleSetSchema := &schema.Schema{ + networkACLRuleSetNestedBlock := &schema.Schema{ Type: schema.TypeSet, Optional: true, Computed: true, ConfigMode: schema.SchemaConfigModeAttr, - Elem: networkACLRuleResource, + Elem: networkACLRuleNestedBlock, Set: networkACLRuleHash, } @@ -61,8 +61,8 @@ func ResourceNetworkACL() *schema.Resource { Type: schema.TypeString, Computed: true, }, - "egress": networkACLRuleSetSchema, - "ingress": networkACLRuleSetSchema, + "egress": networkACLRuleSetNestedBlock, + "ingress": networkACLRuleSetNestedBlock, "owner_id": { Type: schema.TypeString, Computed: true, @@ -86,9 +86,9 @@ func ResourceNetworkACL() *schema.Resource { } } -// NACL rule Resource definition. +// NACL rule nested block definition. // Used in aws_network_acl and aws_default_network_acl ingress and egress rule sets. -var networkACLRuleResource = &schema.Resource{ +var networkACLRuleNestedBlock = &schema.Resource{ Schema: map[string]*schema.Schema{ "action": { Type: schema.TypeString, diff --git a/internal/service/ec2/vpc_network_interface.go b/internal/service/ec2/vpc_network_interface.go index 8bc55c25d528..090cc23930f8 100644 --- a/internal/service/ec2/vpc_network_interface.go +++ b/internal/service/ec2/vpc_network_interface.go @@ -1126,7 +1126,7 @@ func DeleteNetworkInterface(conn *ec2.EC2, networkInterfaceID string) error { } if err != nil { - return fmt.Errorf("error deleting EC2 Network Interface (%s): %w", networkInterfaceID, err) + return fmt.Errorf("deleting EC2 Network Interface (%s): %w", networkInterfaceID, err) } return nil @@ -1146,7 +1146,7 @@ func DetachNetworkInterface(conn *ec2.EC2, networkInterfaceID, attachmentID stri } if err != nil { - return fmt.Errorf("error detaching EC2 Network Interface (%s/%s): %w", networkInterfaceID, attachmentID, err) + return fmt.Errorf("detaching EC2 Network Interface (%s/%s): %w", networkInterfaceID, attachmentID, err) } _, err = WaitNetworkInterfaceDetached(conn, attachmentID, timeout) @@ -1156,7 +1156,7 @@ func DetachNetworkInterface(conn *ec2.EC2, networkInterfaceID, attachmentID stri } if err != nil { - return fmt.Errorf("error waiting for EC2 Network Interface (%s/%s) detach: %w", networkInterfaceID, attachmentID, err) + return fmt.Errorf("waiting for EC2 Network Interface (%s/%s) detach: %w", networkInterfaceID, attachmentID, err) } return nil diff --git a/internal/service/ec2/vpc_security_group.go b/internal/service/ec2/vpc_security_group.go index e7e5114b1ca6..070b92412b0c 100644 --- a/internal/service/ec2/vpc_security_group.go +++ b/internal/service/ec2/vpc_security_group.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "log" + "regexp" "sort" "strconv" "strings" @@ -43,231 +44,162 @@ func ResourceSecurityGroup() *schema.Resource { SchemaVersion: 1, MigrateState: SecurityGroupMigrateState, + // Keep in sync with aws_default_security_group's schema. + // See notes in vpc_default_security_group.go. Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "description": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: "Managed by Terraform", + ValidateFunc: validation.StringLenBetween(0, 255), + }, + "egress": securityGroupRuleSetNestedBlock, + "ingress": securityGroupRuleSetNestedBlock, "name": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, ConflictsWith: []string{"name_prefix"}, - ValidateFunc: validation.StringLenBetween(0, 255), + ValidateFunc: validation.All( + validation.StringLenBetween(0, 255), + validation.StringDoesNotMatch(regexp.MustCompile(`^sg-`), "cannot begin with sg-"), + ), }, - "name_prefix": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, ConflictsWith: []string{"name"}, - ValidateFunc: validation.StringLenBetween(0, 100), + ValidateFunc: validation.All( + validation.StringLenBetween(0, 255-resource.UniqueIDSuffixLength), + validation.StringDoesNotMatch(regexp.MustCompile(`^sg-`), "cannot begin with sg-"), + ), }, - - "description": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - Default: "Managed by Terraform", - ValidateFunc: validation.StringLenBetween(0, 255), + "owner_id": { + Type: schema.TypeString, + Computed: true, }, - + "revoke_rules_on_delete": { + Type: schema.TypeBool, + Default: false, + Optional: true, + }, + "tags": tftags.TagsSchema(), + "tags_all": tftags.TagsSchemaComputed(), "vpc_id": { Type: schema.TypeString, Optional: true, ForceNew: true, Computed: true, }, + }, - "ingress": { - Type: schema.TypeSet, - Optional: true, - Computed: true, - ConfigMode: schema.SchemaConfigModeAttr, - 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: verify.ValidCIDRNetworkAddress, - }, - }, - - "ipv6_cidr_blocks": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateFunc: verify.ValidCIDRNetworkAddress, - }, - }, - - "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, - }, + CustomizeDiff: verify.SetTagsDiff, + } +} - "self": { - Type: schema.TypeBool, - Optional: true, - Default: false, - }, +// Security Group rule nested block definition. +// Used in aws_security_group and aws_default_security_group ingress and egress rule sets. +var ( + securityGroupRuleSetNestedBlock = &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + Computed: true, + ConfigMode: schema.SchemaConfigModeAttr, + Elem: securityGroupRuleNestedBlock, + Set: SecurityGroupRuleHash, + } - "description": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validSecurityGroupRuleDescription, - }, - }, + securityGroupRuleNestedBlock = &schema.Resource{ + Schema: map[string]*schema.Schema{ + "cidr_blocks": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: verify.ValidIPv4CIDRNetworkAddress, }, - Set: SecurityGroupRuleHash, }, - - "egress": { - Type: schema.TypeSet, - Optional: true, - Computed: true, - ConfigMode: schema.SchemaConfigModeAttr, - 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: verify.ValidCIDRNetworkAddress, - }, - }, - - "ipv6_cidr_blocks": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateFunc: verify.ValidCIDRNetworkAddress, - }, - }, - - "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, - }, - - "description": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validSecurityGroupRuleDescription, - }, - }, + "description": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validSecurityGroupRuleDescription, + }, + "from_port": { + Type: schema.TypeInt, + Required: true, + }, + "ipv6_cidr_blocks": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: verify.ValidIPv6CIDRNetworkAddress, }, - Set: SecurityGroupRuleHash, }, - - "arn": { - Type: schema.TypeString, - Computed: true, + "prefix_list_ids": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, }, - - "owner_id": { - Type: schema.TypeString, - Computed: true, + "protocol": { + Type: schema.TypeString, + Required: true, + StateFunc: ProtocolStateFunc, }, - - "tags": tftags.TagsSchema(), - "tags_all": tftags.TagsSchemaComputed(), - - "revoke_rules_on_delete": { + "security_groups": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, // Required to ensure consistent hashing + }, + "self": { Type: schema.TypeBool, - Default: false, Optional: true, + Default: false, + }, + "to_port": { + Type: schema.TypeInt, + Required: true, }, }, - - CustomizeDiff: verify.SetTagsDiff, } -} +) func resourceSecurityGroupCreate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*conns.AWSClient).EC2Conn defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{}))) - groupName := create.Name(d.Get("name").(string), d.Get("name_prefix").(string)) + name := create.Name(d.Get("name").(string), d.Get("name_prefix").(string)) input := &ec2.CreateSecurityGroupInput{ - GroupName: aws.String(groupName), + GroupName: aws.String(name), } if v := d.Get("description"); v != nil { input.Description = aws.String(v.(string)) } - if len(tags) > 0 { - input.TagSpecifications = tagSpecificationsFromKeyValueTags(tags, ec2.ResourceTypeSecurityGroup) - } - if v, ok := d.GetOk("vpc_id"); ok { input.VpcId = aws.String(v.(string)) } + if len(tags) > 0 { + input.TagSpecifications = tagSpecificationsFromKeyValueTags(tags, ec2.ResourceTypeSecurityGroup) + } + log.Printf("[DEBUG] Creating Security Group: %s", input) output, err := conn.CreateSecurityGroup(input) if err != nil { - return fmt.Errorf("error creating Security Group (%s): %w", groupName, err) + return fmt.Errorf("creating Security Group (%s): %w", name, err) } d.SetId(aws.StringValue(output.GroupId)) @@ -276,16 +208,14 @@ func resourceSecurityGroupCreate(d *schema.ResourceData, meta interface{}) error group, err := WaitSecurityGroupCreated(conn, d.Id(), d.Timeout(schema.TimeoutCreate)) if err != nil { - return fmt.Errorf("error waiting for Security Group (%s) create: %w", d.Id(), err) + return fmt.Errorf("waiting for Security Group (%s) create: %w", d.Id(), err) } - // AWS defaults all Security Groups to have an ALLOW ALL egress rule. Here we - // revoke that rule, so users don't unknowingly have/use it. + // AWS defaults all Security Groups to have an ALLOW ALL egress rule. + // Here we revoke that rule, so users don't unknowingly have/use it. if aws.StringValue(group.VpcId) != "" { - log.Printf("[DEBUG] Revoking default egress rule for Security Group for %s", d.Id()) - input := &ec2.RevokeSecurityGroupEgressInput{ - GroupId: output.GroupId, + GroupId: aws.String(d.Id()), IpPermissions: []*ec2.IpPermission{ { FromPort: aws.Int64(0), @@ -300,13 +230,12 @@ func resourceSecurityGroupCreate(d *schema.ResourceData, meta interface{}) error }, } - if _, err = conn.RevokeSecurityGroupEgress(input); err != nil { - return fmt.Errorf("Error revoking default egress rule for Security Group (%s): %w", d.Id(), err) + if _, err := conn.RevokeSecurityGroupEgress(input); err != nil { + return fmt.Errorf("revoking default IPv4 egress rule for Security Group (%s): %w", d.Id(), err) } - log.Printf("[DEBUG] Revoking default IPv6 egress rule for Security Group for %s", d.Id()) input = &ec2.RevokeSecurityGroupEgressInput{ - GroupId: output.GroupId, + GroupId: aws.String(d.Id()), IpPermissions: []*ec2.IpPermission{ { FromPort: aws.Int64(0), @@ -321,15 +250,12 @@ func resourceSecurityGroupCreate(d *schema.ResourceData, meta interface{}) error }, } - _, err = conn.RevokeSecurityGroupEgress(input) - if err != nil { - //If we have a NotFound or InvalidParameterValue, then we are trying to remove the default IPv6 egress of a non-IPv6 - //enabled SG + if _, err := conn.RevokeSecurityGroupEgress(input); err != nil { + // If we have a NotFound or InvalidParameterValue, then we are trying to remove the default IPv6 egress of a non-IPv6 enabled SG. if !tfawserr.ErrCodeEquals(err, errCodeInvalidPermissionNotFound) && !tfawserr.ErrMessageContains(err, errCodeInvalidParameterValue, "remote-ipv6-range") { - return fmt.Errorf("Error revoking default IPv6 egress rule for Security Group (%s): %w", d.Id(), err) + return fmt.Errorf("revoking default IPv6 egress rule for Security Group (%s): %w", d.Id(), err) } } - } return resourceSecurityGroupUpdate(d, meta) @@ -343,13 +269,13 @@ func resourceSecurityGroupRead(d *schema.ResourceData, meta interface{}) error { sg, err := FindSecurityGroupByID(conn, d.Id()) if !d.IsNewResource() && tfresource.NotFound(err) { - log.Printf("[WARN] Security Group %s not found, removing from state", d.Id()) + log.Printf("[WARN] Security Group (%s) not found, removing from state", d.Id()) d.SetId("") return nil } if err != nil { - return fmt.Errorf("error reading Security Group (%s): %w", d.Id(), err) + return fmt.Errorf("reading Security Group (%s): %w", d.Id(), err) } remoteIngressRules := SecurityGroupIPPermGather(d.Id(), sg.IpPermissions, sg.OwnerId) @@ -363,38 +289,38 @@ func resourceSecurityGroupRead(d *schema.ResourceData, meta interface{}) error { ingressRules := MatchRules("ingress", localIngressRules, remoteIngressRules) egressRules := MatchRules("egress", localEgressRules, remoteEgressRules) - sgArn := arn.ARN{ - AccountID: aws.StringValue(sg.OwnerId), + ownerID := aws.StringValue(sg.OwnerId) + arn := arn.ARN{ Partition: meta.(*conns.AWSClient).Partition, - Region: meta.(*conns.AWSClient).Region, - Resource: fmt.Sprintf("security-group/%s", aws.StringValue(sg.GroupId)), Service: ec2.ServiceName, + Region: meta.(*conns.AWSClient).Region, + AccountID: ownerID, + Resource: fmt.Sprintf("security-group/%s", d.Id()), } - - d.Set("arn", sgArn.String()) + d.Set("arn", arn.String()) d.Set("description", sg.Description) d.Set("name", sg.GroupName) d.Set("name_prefix", create.NamePrefixFromName(aws.StringValue(sg.GroupName))) - d.Set("owner_id", sg.OwnerId) + d.Set("owner_id", ownerID) d.Set("vpc_id", sg.VpcId) if err := d.Set("ingress", ingressRules); err != nil { - return fmt.Errorf("error setting ingress: %w", err) + return fmt.Errorf("setting ingress: %w", err) } if err := d.Set("egress", egressRules); err != nil { - return fmt.Errorf("error setting egress: %w", err) + return fmt.Errorf("setting egress: %w", err) } tags := KeyValueTags(sg.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig) //lintignore:AWSR002 if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { - return fmt.Errorf("error setting tags: %w", err) + return fmt.Errorf("setting tags: %w", err) } if err := d.Set("tags_all", tags.Map()); err != nil { - return fmt.Errorf("error setting tags_all: %w", err) + return fmt.Errorf("setting tags_all: %w", err) } return nil @@ -404,19 +330,22 @@ func resourceSecurityGroupUpdate(d *schema.ResourceData, meta interface{}) error conn := meta.(*conns.AWSClient).EC2Conn group, err := FindSecurityGroupByID(conn, d.Id()) + if err != nil { - return fmt.Errorf("error updating Security Group (%s): %w", d.Id(), err) + return fmt.Errorf("reading Security Group (%s): %w", d.Id(), err) } - err = resourceSecurityGroupUpdateRules(d, "ingress", meta, group) + err = updateSecurityGroupRules(conn, d, securityGroupRuleTypeIngress, group) + if err != nil { - return fmt.Errorf("error updating Security Group (%s): %w", d.Id(), err) + return fmt.Errorf("updating Security Group (%s) %s rules: %w", d.Id(), securityGroupRuleTypeIngress, err) } if d.Get("vpc_id") != nil { - err = resourceSecurityGroupUpdateRules(d, "egress", meta, group) + err = updateSecurityGroupRules(conn, d, securityGroupRuleTypeEgress, group) + if err != nil { - return fmt.Errorf("error updating Security Group (%s): %w", d.Id(), err) + return fmt.Errorf("updating Security Group (%s) %s rules: %w", d.Id(), securityGroupRuleTypeEgress, err) } } @@ -424,7 +353,7 @@ func resourceSecurityGroupUpdate(d *schema.ResourceData, meta interface{}) error o, n := d.GetChange("tags_all") if err := UpdateTags(conn, d.Id(), o, n); err != nil { - return fmt.Errorf("error updating Security Group (%s) tags: %w", d.Id(), err) + return fmt.Errorf("updating Security Group (%s) tags: %w", d.Id(), err) } } @@ -434,89 +363,85 @@ func resourceSecurityGroupUpdate(d *schema.ResourceData, meta interface{}) error func resourceSecurityGroupDelete(d *schema.ResourceData, meta interface{}) error { conn := meta.(*conns.AWSClient).EC2Conn - log.Printf("[DEBUG] Security Group destroy: %v", d.Id()) - if err := deleteLingeringLambdaENIs(conn, "group-id", d.Id(), d.Timeout(schema.TimeoutDelete)); err != nil { - return fmt.Errorf("error deleting Lambda ENIs using Security Group (%s): %w", d.Id(), err) + return fmt.Errorf("deleting Lambda ENIs using Security Group (%s): %w", d.Id(), err) } // conditionally revoke rules first before attempting to delete the group if v := d.Get("revoke_rules_on_delete").(bool); v { - if err := forceRevokeSecurityGroupRules(conn, d); err != nil { + err := forceRevokeSecurityGroupRules(conn, d.Id()) + + if tfawserr.ErrCodeEquals(err, errCodeInvalidGroupNotFound) { + return nil + } + + if err != nil { return err } } - input := &ec2.DeleteSecurityGroupInput{ - GroupId: aws.String(d.Id()), - } - err := resource.Retry(d.Timeout(schema.TimeoutDelete), func() *resource.RetryError { - _, err := conn.DeleteSecurityGroup(input) - if err != nil { - if tfawserr.ErrCodeEquals(err, "InvalidGroup.NotFound") { - return nil - } - // If it is a dependency violation, we want to retry - if tfawserr.ErrMessageContains(err, "DependencyViolation", "has a dependent object") { - return resource.RetryableError(err) - } + log.Printf("[DEBUG] Deleting Security Group: %s", d.Id()) + _, err := tfresource.RetryWhenAWSErrCodeEquals( + d.Timeout(schema.TimeoutDelete), + func() (interface{}, error) { + return conn.DeleteSecurityGroup(&ec2.DeleteSecurityGroupInput{ + GroupId: aws.String(d.Id()), + }) + }, + errCodeDependencyViolation, errCodeInvalidGroupInUse, + ) - if tfawserr.ErrCodeEquals(err, "DependencyViolation") { - return resource.RetryableError(err) - } + if tfawserr.ErrCodeEquals(err, errCodeInvalidGroupNotFound) { + return nil + } - if tfawserr.ErrCodeEquals(err, "InvalidGroup.InUse") { - return resource.RetryableError(err) - } + if err != nil { + return fmt.Errorf("deleting Security Group (%s): %w", d.Id(), err) + } - return resource.NonRetryableError(err) - } - return nil + _, err = tfresource.RetryUntilNotFound(propagationTimeout, func() (interface{}, error) { + return FindSecurityGroupByID(conn, d.Id()) }) - if tfresource.TimedOut(err) { - _, err = conn.DeleteSecurityGroup(input) - if tfawserr.ErrCodeEquals(err, "InvalidGroup.NotFound") { - return nil - } - } + if err != nil { - return fmt.Errorf("Error deleting security group: %w", err) + return fmt.Errorf("waiting for Security Group (%s) delete: %w", d.Id(), err) } + return nil } -// Revoke all ingress/egress rules that a Security Group has -func forceRevokeSecurityGroupRules(conn *ec2.EC2, d *schema.ResourceData) error { - group, err := FindSecurityGroupByID(conn, d.Id()) +// forceRevokeSecurityGroupRules revokes all of the specified Security Group's ingress & egress rules. +func forceRevokeSecurityGroupRules(conn *ec2.EC2, id string) error { + group, err := FindSecurityGroupByID(conn, id) + if err != nil { - return err + return fmt.Errorf("reading Security Group (%s): %w", id, err) } if len(group.IpPermissions) > 0 { - req := &ec2.RevokeSecurityGroupIngressInput{ - GroupId: group.GroupId, + input := &ec2.RevokeSecurityGroupIngressInput{ IpPermissions: group.IpPermissions, } + if aws.StringValue(group.VpcId) == "" { - req.GroupId = nil - req.GroupName = group.GroupName + input.GroupName = group.GroupName + } else { + input.GroupId = group.GroupId } - _, err = conn.RevokeSecurityGroupIngress(req) - if err != nil { - return fmt.Errorf("error revoking Security Group (%s) rules: %w", aws.StringValue(group.GroupId), err) + if _, err := conn.RevokeSecurityGroupIngress(input); err != nil { + return fmt.Errorf("revoking Security Group (%s) ingress rules: %w", id, err) } } if len(group.IpPermissionsEgress) > 0 { - req := &ec2.RevokeSecurityGroupEgressInput{ + input := &ec2.RevokeSecurityGroupEgressInput{ GroupId: group.GroupId, IpPermissions: group.IpPermissionsEgress, } - _, err = conn.RevokeSecurityGroupEgress(req) - if err != nil { - return fmt.Errorf("error revoking Security Group (%s) rules: %w", aws.StringValue(group.GroupId), err) + if _, err := conn.RevokeSecurityGroupEgress(input); err != nil { + return fmt.Errorf("revoking Security Group (%s) egress rules: %w", id, err) } } @@ -677,100 +602,99 @@ func SecurityGroupIPPermGather(groupId string, permissions []*ec2.IpPermission, return rules } -func resourceSecurityGroupUpdateRules( - d *schema.ResourceData, ruleset string, - meta interface{}, group *ec2.SecurityGroup) error { +func updateSecurityGroupRules(conn *ec2.EC2, d *schema.ResourceData, ruleType string, group *ec2.SecurityGroup) error { + if !d.HasChange(ruleType) { + return nil + } - if d.HasChange(ruleset) { - o, n := d.GetChange(ruleset) - if o == nil { - o = new(schema.Set) - } - if n == nil { - n = new(schema.Set) - } + o, n := d.GetChange(ruleType) + if o == nil { + o = new(schema.Set) + } + if n == nil { + n = new(schema.Set) + } - os := SecurityGroupExpandRules(o.(*schema.Set)) - ns := SecurityGroupExpandRules(n.(*schema.Set)) + os := SecurityGroupExpandRules(o.(*schema.Set)) + ns := SecurityGroupExpandRules(n.(*schema.Set)) - remove, err := ExpandIPPerms(group, SecurityGroupCollapseRules(ruleset, os.Difference(ns).List())) - if err != nil { - return err - } - add, err := ExpandIPPerms(group, SecurityGroupCollapseRules(ruleset, ns.Difference(os).List())) - if err != nil { - return err - } + del, err := ExpandIPPerms(group, SecurityGroupCollapseRules(ruleType, os.Difference(ns).List())) - // TODO: We need to handle partial state better in the in-between - // in this update. + if err != nil { + return err + } - // TODO: It'd be nicer to authorize before removing, but then we have - // to deal with complicated unrolling to get individual CIDR blocks - // to avoid authorizing already authorized sources. Removing before - // adding is easier here, and Terraform should be fast enough to - // not have service issues. + add, err := ExpandIPPerms(group, SecurityGroupCollapseRules(ruleType, ns.Difference(os).List())) + + if err != nil { + return err + } - if len(remove) > 0 || len(add) > 0 { - conn := meta.(*conns.AWSClient).EC2Conn + // TODO: We need to handle partial state better in the in-between + // in this update. - var err error - if len(remove) > 0 { - log.Printf("[DEBUG] Revoking security group %#v %s rule: %#v", - group, ruleset, remove) + // TODO: It'd be nicer to authorize before removing, but then we have + // to deal with complicated unrolling to get individual CIDR blocks + // to avoid authorizing already authorized sources. Removing before + // adding is easier here, and Terraform should be fast enough to + // not have service issues. - if ruleset == "egress" { - req := &ec2.RevokeSecurityGroupEgressInput{ - GroupId: group.GroupId, - IpPermissions: remove, - } - _, err = conn.RevokeSecurityGroupEgress(req) - } else { - req := &ec2.RevokeSecurityGroupIngressInput{ - GroupId: group.GroupId, - IpPermissions: remove, - } - if aws.StringValue(group.VpcId) == "" { - req.GroupId = nil - req.GroupName = group.GroupName - } - _, err = conn.RevokeSecurityGroupIngress(req) - } + isVPC := aws.StringValue(group.VpcId) != "" - if err != nil { - return fmt.Errorf("error revoking Security Group (%s) rules: %w", ruleset, err) - } + if len(del) > 0 { + if ruleType == securityGroupRuleTypeEgress { + input := &ec2.RevokeSecurityGroupEgressInput{ + GroupId: group.GroupId, + IpPermissions: del, } - if len(add) > 0 { - log.Printf("[DEBUG] Authorizing security group %#v %s rule: %#v", - group, ruleset, add) - // Authorize the new rules - if ruleset == "egress" { - req := &ec2.AuthorizeSecurityGroupEgressInput{ - GroupId: group.GroupId, - IpPermissions: add, - } - _, err = conn.AuthorizeSecurityGroupEgress(req) - } else { - req := &ec2.AuthorizeSecurityGroupIngressInput{ - GroupId: group.GroupId, - IpPermissions: add, - } - if aws.StringValue(group.VpcId) == "" { - req.GroupId = nil - req.GroupName = group.GroupName - } + _, err = conn.RevokeSecurityGroupEgress(input) + } else { + input := &ec2.RevokeSecurityGroupIngressInput{ + IpPermissions: del, + } - _, err = conn.AuthorizeSecurityGroupIngress(req) - } + if isVPC { + input.GroupId = group.GroupId + } else { + input.GroupName = group.GroupName + } - if err != nil { - return fmt.Errorf("error authorizing Security Group (%s) rules: %w", ruleset, err) - } + _, err = conn.RevokeSecurityGroupIngress(input) + } + + if err != nil { + return fmt.Errorf("revoking Security Group (%s) rules: %w", ruleType, err) + } + } + + if len(add) > 0 { + if ruleType == securityGroupRuleTypeEgress { + input := &ec2.AuthorizeSecurityGroupEgressInput{ + GroupId: group.GroupId, + IpPermissions: add, + } + + _, err = conn.AuthorizeSecurityGroupEgress(input) + } else { + input := &ec2.AuthorizeSecurityGroupIngressInput{ + IpPermissions: add, } + + if isVPC { + input.GroupId = group.GroupId + } else { + input.GroupName = group.GroupName + } + + _, err = conn.AuthorizeSecurityGroupIngress(input) + } + + if err != nil { + return fmt.Errorf("authorizing Security Group (%s) rules: %w", ruleType, err) } } + return nil } @@ -1302,7 +1226,7 @@ func ProtocolForValue(v string) string { return "-1" } // if it's a name like tcp, return that - if _, ok := sgProtocolIntegers()[protocol]; ok { + if _, ok := securityGroupProtocolIntegers[protocol]; ok { return protocol } // convert to int, look for that value @@ -1314,7 +1238,7 @@ func ProtocolForValue(v string) string { return protocol } - for k, v := range sgProtocolIntegers() { + for k, v := range securityGroupProtocolIntegers { if p == v { // guard against protocolIntegers sometime in the future not having lower // case ids in the map @@ -1333,19 +1257,17 @@ func ProtocolForValue(v string) string { // http://docs.aws.amazon.com/fr_fr/AWSEC2/latest/APIReference/API_IpPermission.html // Similar to protocolIntegers() used by Network ACLs, but explicitly only // supports "tcp", "udp", "icmp", "icmpv6", and "all" -func sgProtocolIntegers() map[string]int { - return map[string]int{ - "icmpv6": 58, - "udp": 17, - "tcp": 6, - "icmp": 1, - "all": -1, - } +var securityGroupProtocolIntegers = map[string]int{ + "icmpv6": 58, + "udp": 17, + "tcp": 6, + "icmp": 1, + "all": -1, } // The AWS Lambda service creates ENIs behind the scenes and keeps these around for a while // which would prevent SGs attached to such ENIs from being destroyed -func deleteLingeringLambdaENIs(conn *ec2.EC2, filterName, resourceId string, timeout time.Duration) error { +func deleteLingeringLambdaENIs(conn *ec2.EC2, filterName, resourceID string, timeout time.Duration) error { // AWS Lambda service team confirms P99 deletion time of ~35 minutes. Buffer for safety. if minimumTimeout := 45 * time.Minute; timeout < minimumTimeout { timeout = minimumTimeout @@ -1353,13 +1275,13 @@ func deleteLingeringLambdaENIs(conn *ec2.EC2, filterName, resourceId string, tim networkInterfaces, err := FindNetworkInterfaces(conn, &ec2.DescribeNetworkInterfacesInput{ Filters: BuildAttributeFilterList(map[string]string{ - filterName: resourceId, + filterName: resourceID, "description": "AWS Lambda VPC ENI*", }), }) if err != nil { - return fmt.Errorf("error listing EC2 Network Interfaces: %w", err) + return fmt.Errorf("listing EC2 Network Interfaces: %w", err) } for _, v := range networkInterfaces { @@ -1373,24 +1295,20 @@ func deleteLingeringLambdaENIs(conn *ec2.EC2, filterName, resourceId string, tim } if err != nil { - return fmt.Errorf("error waiting for Lambda ENI (%s) to become available for detachment: %w", networkInterfaceID, err) + return fmt.Errorf("waiting for Lambda ENI (%s) to become available after use: %w", networkInterfaceID, err) } v = networkInterface } if v.Attachment != nil { - err = DetachNetworkInterface(conn, networkInterfaceID, aws.StringValue(v.Attachment.AttachmentId), timeout) - - if err != nil { - return fmt.Errorf("error detaching Lambda ENI (%s): %w", networkInterfaceID, err) + if err := DetachNetworkInterface(conn, networkInterfaceID, aws.StringValue(v.Attachment.AttachmentId), timeout); err != nil { + return err } } - err = DeleteNetworkInterface(conn, networkInterfaceID) - - if err != nil { - return fmt.Errorf("error deleting Lambda ENI (%s): %w", networkInterfaceID, err) + if err := DeleteNetworkInterface(conn, networkInterfaceID); err != nil { + return err } } diff --git a/internal/service/ec2/vpc_security_group_data_source.go b/internal/service/ec2/vpc_security_group_data_source.go index d61b68e9778c..7fe5d86c1edc 100644 --- a/internal/service/ec2/vpc_security_group_data_source.go +++ b/internal/service/ec2/vpc_security_group_data_source.go @@ -1,7 +1,6 @@ package ec2 import ( - "errors" "fmt" "github.com/aws/aws-sdk-go/aws" @@ -18,33 +17,29 @@ func DataSourceSecurityGroup() *schema.Resource { Read: dataSourceSecurityGroupRead, Schema: map[string]*schema.Schema{ - "vpc_id": { + "arn": { Type: schema.TypeString, - Optional: true, Computed: true, }, - "name": { + "description": { Type: schema.TypeString, - Optional: true, Computed: true, }, "filter": CustomFiltersSchema(), - "id": { Type: schema.TypeString, Optional: true, Computed: true, }, - - "arn": { + "name": { Type: schema.TypeString, + Optional: true, Computed: true, }, - "tags": tftags.TagsSchemaComputed(), - - "description": { + "vpc_id": { Type: schema.TypeString, + Optional: true, Computed: true, }, }, @@ -55,48 +50,39 @@ func dataSourceSecurityGroupRead(d *schema.ResourceData, meta interface{}) error conn := meta.(*conns.AWSClient).EC2Conn ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig - req := &ec2.DescribeSecurityGroupsInput{} + input := &ec2.DescribeSecurityGroupsInput{ + Filters: BuildAttributeFilterList( + map[string]string{ + "group-name": d.Get("name").(string), + "vpc-id": d.Get("vpc_id").(string), + }, + ), + } - if id, ok := d.GetOk("id"); ok { - req.GroupIds = []*string{aws.String(id.(string))} + if v, ok := d.GetOk("id"); ok { + input.GroupIds = aws.StringSlice([]string{v.(string)}) } - req.Filters = BuildAttributeFilterList( - map[string]string{ - "group-name": d.Get("name").(string), - "vpc-id": d.Get("vpc_id").(string), - }, - ) - req.Filters = append(req.Filters, BuildTagFilterList( + input.Filters = append(input.Filters, BuildTagFilterList( Tags(tftags.New(d.Get("tags").(map[string]interface{}))), )...) - req.Filters = append(req.Filters, BuildCustomFilterList( + + input.Filters = append(input.Filters, BuildCustomFilterList( d.Get("filter").(*schema.Set), )...) - if len(req.Filters) == 0 { + + if len(input.Filters) == 0 { // Don't send an empty filters list; the EC2 API won't accept it. - req.Filters = nil + input.Filters = nil } - sg, err := FindSecurityGroup(conn, req) - if errors.Is(err, tfresource.ErrEmptyResult) { - return fmt.Errorf("no matching SecurityGroup found") - } - if errors.Is(err, tfresource.ErrTooManyResults) { - return fmt.Errorf("multiple Security Groups matched; use additional constraints to reduce matches to a single Security Group") - } + sg, err := FindSecurityGroup(conn, input) + if err != nil { - return err + return tfresource.SingularDataSourceFindError("EC2 Security Group", err) } d.SetId(aws.StringValue(sg.GroupId)) - d.Set("name", sg.GroupName) - d.Set("description", sg.Description) - d.Set("vpc_id", sg.VpcId) - - if err := d.Set("tags", KeyValueTags(sg.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig).Map()); err != nil { - return fmt.Errorf("error setting tags: %w", err) - } arn := arn.ARN{ Partition: meta.(*conns.AWSClient).Partition, @@ -106,6 +92,13 @@ func dataSourceSecurityGroupRead(d *schema.ResourceData, meta interface{}) error Resource: fmt.Sprintf("security-group/%s", *sg.GroupId), }.String() d.Set("arn", arn) + d.Set("description", sg.Description) + d.Set("name", sg.GroupName) + d.Set("vpc_id", sg.VpcId) + + if err := d.Set("tags", KeyValueTags(sg.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig).Map()); err != nil { + return fmt.Errorf("setting tags: %w", err) + } return nil } diff --git a/internal/service/ec2/vpc_security_group_data_source_test.go b/internal/service/ec2/vpc_security_group_data_source_test.go index 7b239a54a1a2..06563a28acf6 100644 --- a/internal/service/ec2/vpc_security_group_data_source_test.go +++ b/internal/service/ec2/vpc_security_group_data_source_test.go @@ -2,128 +2,64 @@ package ec2_test import ( "fmt" - "strings" "testing" "github.com/aws/aws-sdk-go/service/ec2" sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "github.com/hashicorp/terraform-provider-aws/internal/acctest" ) func TestAccVPCSecurityGroupDataSource_basic(t *testing.T) { - rInt := sdkacctest.RandInt() + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), ProviderFactories: acctest.ProviderFactories, Steps: []resource.TestStep{ { - Config: testAccVPCSecurityGroupDataSourceConfig_basic(rInt), - Check: resource.ComposeTestCheckFunc( + Config: testAccVPCSecurityGroupDataSourceConfig_basic(rName), + Check: resource.ComposeAggregateTestCheckFunc( testAccSecurityGroupCheckDataSource("data.aws_security_group.by_id"), - resource.TestCheckResourceAttr("data.aws_security_group.by_id", "description", "sg description"), testAccSecurityGroupCheckDataSource("data.aws_security_group.by_tag"), testAccSecurityGroupCheckDataSource("data.aws_security_group.by_filter"), testAccSecurityGroupCheckDataSource("data.aws_security_group.by_name"), - testAccSecurityGroupCheckDefaultDataSource("data.aws_security_group.default_by_name"), ), }, }, }) } -func testAccSecurityGroupCheckDataSource(name string) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[name] - if !ok { - return fmt.Errorf("root module has no resource called %s", name) - } - - SGRs, ok := s.RootModule().Resources["aws_security_group.test"] - if !ok { - return fmt.Errorf("can't find aws_security_group.test in state") - } - vpcRs, ok := s.RootModule().Resources["aws_vpc.test"] - if !ok { - return fmt.Errorf("can't find aws_vpc.test in state") - } - attr := rs.Primary.Attributes - - if attr["id"] != SGRs.Primary.Attributes["id"] { - return fmt.Errorf( - "id is %s; want %s", - attr["id"], - SGRs.Primary.Attributes["id"], - ) - } - - if attr["vpc_id"] != vpcRs.Primary.Attributes["id"] { - return fmt.Errorf( - "vpc_id is %s; want %s", - attr["vpc_id"], - vpcRs.Primary.Attributes["id"], - ) - } +func testAccSecurityGroupCheckDataSource(dataSourceName string) resource.TestCheckFunc { + resourceName := "aws_security_group.test" - if attr["tags.Name"] != "tf-acctest" { - return fmt.Errorf("bad Name tag %s", attr["tags.Name"]) - } - - if !strings.Contains(attr["arn"], attr["id"]) { - return fmt.Errorf("bad ARN %s", attr["arn"]) - } - - return nil - } + return resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrPair(dataSourceName, "arn", resourceName, "arn"), + resource.TestCheckResourceAttrPair(dataSourceName, "description", resourceName, "description"), + resource.TestCheckResourceAttrPair(dataSourceName, "name", resourceName, "name"), + resource.TestCheckResourceAttrPair(dataSourceName, "tags.%", resourceName, "tags.%"), + resource.TestCheckResourceAttrPair(dataSourceName, "vpc_id", resourceName, "vpc_id"), + ) } -func testAccSecurityGroupCheckDefaultDataSource(name string) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[name] - if !ok { - return fmt.Errorf("root module has no resource called %s", name) - } - - vpcRs, ok := s.RootModule().Resources["aws_vpc.test"] - if !ok { - return fmt.Errorf("can't find aws_vpc.test in state") - } - attr := rs.Primary.Attributes - - if attr["id"] != vpcRs.Primary.Attributes["default_security_group_id"] { - return fmt.Errorf( - "id is %s; want %s", - attr["id"], - vpcRs.Primary.Attributes["default_security_group_id"], - ) - } - - return nil - } -} - -func testAccVPCSecurityGroupDataSourceConfig_basic(rInt int) string { +func testAccVPCSecurityGroupDataSourceConfig_basic(rName string) string { return fmt.Sprintf(` resource "aws_vpc" "test" { cidr_block = "172.16.0.0/16" tags = { - Name = "terraform-testacc-security-group-data-source" + Name = %[1]q } } resource "aws_security_group" "test" { vpc_id = aws_vpc.test.id - name = "test-%d" + name = %[1]q tags = { - Name = "tf-acctest" - Seed = "%d" + Name = %[1]q } - - description = "sg description" } data "aws_security_group" "by_id" { @@ -134,14 +70,9 @@ data "aws_security_group" "by_name" { name = aws_security_group.test.name } -data "aws_security_group" "default_by_name" { - vpc_id = aws_vpc.test.id - name = "default" -} - data "aws_security_group" "by_tag" { tags = { - Seed = aws_security_group.test.tags["Seed"] + Name = aws_security_group.test.tags["Name"] } } @@ -151,5 +82,5 @@ data "aws_security_group" "by_filter" { values = [aws_security_group.test.name] } } -`, rInt, rInt) +`, rName) } diff --git a/internal/service/ec2/vpc_security_group_migrate.go b/internal/service/ec2/vpc_security_group_migrate.go index 4cc9e74dc9b9..a83fe4313490 100644 --- a/internal/service/ec2/vpc_security_group_migrate.go +++ b/internal/service/ec2/vpc_security_group_migrate.go @@ -7,14 +7,13 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) -func SecurityGroupMigrateState( - v int, is *terraform.InstanceState, meta interface{}) (*terraform.InstanceState, error) { +func SecurityGroupMigrateState(v int, is *terraform.InstanceState, meta interface{}) (*terraform.InstanceState, error) { switch v { case 0: - log.Println("[INFO] Found AWS SecurityGroup State v0; migrating to v1") + log.Println("[INFO] Found Security Group State v0; migrating to v1") return migrateSecurityGroupStateV0toV1(is) default: - return is, fmt.Errorf("Unexpected schema version: %d", v) + return is, fmt.Errorf("unexpected schema version: %d", v) } } @@ -30,5 +29,6 @@ func migrateSecurityGroupStateV0toV1(is *terraform.InstanceState) (*terraform.In is.Attributes["revoke_rules_on_delete"] = "false" log.Printf("[DEBUG] Attributes after migration: %#v", is.Attributes) + return is, nil } diff --git a/internal/service/ec2/vpc_security_group_rule.go b/internal/service/ec2/vpc_security_group_rule.go index 1d114b450423..1ac65e8871db 100644 --- a/internal/service/ec2/vpc_security_group_rule.go +++ b/internal/service/ec2/vpc_security_group_rule.go @@ -2,6 +2,7 @@ package ec2 import ( "bytes" + "context" "fmt" "log" "sort" @@ -28,49 +29,36 @@ func ResourceSecurityGroupRule() *schema.Resource { Read: resourceSecurityGroupRuleRead, Update: resourceSecurityGroupRuleUpdate, Delete: resourceSecurityGroupRuleDelete, + Importer: &schema.ResourceImporter{ - State: func(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { - importParts, err := validateSecurityGroupRuleImportString(d.Id()) - if err != nil { - return nil, err - } - if err := populateSecurityGroupRuleFromImport(d, importParts); err != nil { - return nil, err - } - return []*schema.ResourceData{d}, nil - }, + StateContext: resourceSecurityGroupRuleImport, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(5 * time.Minute), }, SchemaVersion: 2, MigrateState: SecurityGroupRuleMigrateState, Schema: map[string]*schema.Schema{ - "type": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - Description: "Type of rule, ingress (inbound) or egress (outbound).", - ValidateFunc: validation.StringInSlice([]string{ - "ingress", - "egress", - }, false), - }, - - "from_port": { - Type: schema.TypeInt, - Required: true, + "cidr_blocks": { + Type: schema.TypeList, + Optional: true, ForceNew: true, - // Support existing configurations that have non-zero from_port and to_port defined with all protocols - DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { - protocol := ProtocolForValue(d.Get("protocol").(string)) - if protocol == "-1" && old == "0" { - return true - } - return false + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: verify.ValidIPv4CIDRNetworkAddress, }, + ConflictsWith: []string{"source_security_group_id", "self"}, + AtLeastOneOf: []string{"cidr_blocks", "ipv6_cidr_blocks", "prefix_list_ids", "self", "source_security_group_id"}, }, - - "to_port": { + "description": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validSecurityGroupRuleDescription, + }, + "from_port": { Type: schema.TypeInt, Required: true, ForceNew: true, @@ -83,69 +71,69 @@ func ResourceSecurityGroupRule() *schema.Resource { return false }, }, - - "protocol": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - StateFunc: ProtocolStateFunc, - }, - - "cidr_blocks": { - Type: schema.TypeList, - Optional: true, - ForceNew: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateFunc: verify.ValidCIDRNetworkAddress, - }, - ConflictsWith: []string{"source_security_group_id", "self"}, - }, - "ipv6_cidr_blocks": { Type: schema.TypeList, Optional: true, ForceNew: true, Elem: &schema.Schema{ Type: schema.TypeString, - ValidateFunc: verify.ValidCIDRNetworkAddress, + ValidateFunc: verify.ValidIPv6CIDRNetworkAddress, }, ConflictsWith: []string{"source_security_group_id", "self"}, + AtLeastOneOf: []string{"cidr_blocks", "ipv6_cidr_blocks", "prefix_list_ids", "self", "source_security_group_id"}, }, - "prefix_list_ids": { - Type: schema.TypeList, - Optional: true, - ForceNew: true, - Elem: &schema.Schema{Type: schema.TypeString}, + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + AtLeastOneOf: []string{"cidr_blocks", "ipv6_cidr_blocks", "prefix_list_ids", "self", "source_security_group_id"}, + }, + "protocol": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + StateFunc: ProtocolStateFunc, }, - "security_group_id": { Type: schema.TypeString, Required: true, ForceNew: true, }, - + "self": { + Type: schema.TypeBool, + Optional: true, + Default: false, + ForceNew: true, + ConflictsWith: []string{"cidr_blocks", "ipv6_cidr_blocks", "source_security_group_id"}, + AtLeastOneOf: []string{"cidr_blocks", "ipv6_cidr_blocks", "prefix_list_ids", "self", "source_security_group_id"}, + }, "source_security_group_id": { Type: schema.TypeString, Optional: true, ForceNew: true, Computed: true, ConflictsWith: []string{"cidr_blocks", "ipv6_cidr_blocks", "self"}, + AtLeastOneOf: []string{"cidr_blocks", "ipv6_cidr_blocks", "prefix_list_ids", "self", "source_security_group_id"}, }, - - "self": { - Type: schema.TypeBool, - Optional: true, - Default: false, - ForceNew: true, - ConflictsWith: []string{"cidr_blocks", "ipv6_cidr_blocks", "source_security_group_id"}, + "to_port": { + Type: schema.TypeInt, + Required: true, + ForceNew: true, + // Support existing configurations that have non-zero from_port and to_port defined with all protocols + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + protocol := ProtocolForValue(d.Get("protocol").(string)) + if protocol == "-1" && old == "0" { + return true + } + return false + }, }, - - "description": { + "type": { Type: schema.TypeString, - Optional: true, - ValidateFunc: validSecurityGroupRuleDescription, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice(securityGroupRuleType_Values(), false), }, }, } @@ -153,187 +141,138 @@ func ResourceSecurityGroupRule() *schema.Resource { func resourceSecurityGroupRuleCreate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*conns.AWSClient).EC2Conn - sg_id := d.Get("security_group_id").(string) + securityGroupID := d.Get("security_group_id").(string) - conns.GlobalMutexKV.Lock(sg_id) - defer conns.GlobalMutexKV.Unlock(sg_id) + conns.GlobalMutexKV.Lock(securityGroupID) + defer conns.GlobalMutexKV.Unlock(securityGroupID) - sg, err := FindSecurityGroupByID(conn, sg_id) - if err != nil { - return err - } + sg, err := FindSecurityGroupByID(conn, securityGroupID) - perm, err := expandIPPerm(d, sg) if err != nil { - return err - } - - // Verify that either 'cidr_blocks', 'self', or 'source_security_group_id' is set - // If they are not set the AWS API will silently fail. This causes TF to hit a timeout - // at 5-minutes waiting for the security group rule to appear, when it was never actually - // created. - if err := validSecurityGroupRule(d); err != nil { - return err + return fmt.Errorf("reading Security Group (%s): %w", securityGroupID, err) } + ipPermission := expandIpPermission(d, sg) ruleType := d.Get("type").(string) isVPC := aws.StringValue(sg.VpcId) != "" + id := SecurityGroupRuleCreateID(securityGroupID, ruleType, ipPermission) - var autherr error switch ruleType { - case "ingress": - log.Printf("[DEBUG] Authorizing security group %s %s rule: %s", - sg_id, "Ingress", perm) - - req := &ec2.AuthorizeSecurityGroupIngressInput{ - GroupId: sg.GroupId, - IpPermissions: []*ec2.IpPermission{perm}, + case securityGroupRuleTypeIngress: + input := &ec2.AuthorizeSecurityGroupIngressInput{ + IpPermissions: []*ec2.IpPermission{ipPermission}, } - if !isVPC { - req.GroupId = nil - req.GroupName = sg.GroupName + if isVPC { + input.GroupId = sg.GroupId + } else { + input.GroupName = sg.GroupName } - _, autherr = conn.AuthorizeSecurityGroupIngress(req) - - case "egress": - log.Printf("[DEBUG] Authorizing security group %s %s rule: %#v", - sg_id, "Egress", perm) + _, err = conn.AuthorizeSecurityGroupIngress(input) - req := &ec2.AuthorizeSecurityGroupEgressInput{ + case securityGroupRuleTypeEgress: + input := &ec2.AuthorizeSecurityGroupEgressInput{ GroupId: sg.GroupId, - IpPermissions: []*ec2.IpPermission{perm}, + IpPermissions: []*ec2.IpPermission{ipPermission}, } - _, autherr = conn.AuthorizeSecurityGroupEgress(req) - - default: - return fmt.Errorf("Security Group Rule must be type 'ingress' or type 'egress'") + _, err = conn.AuthorizeSecurityGroupEgress(input) } - if tfawserr.ErrCodeEquals(autherr, errCodeInvalidPermissionDuplicate) { + if tfawserr.ErrCodeEquals(err, errCodeInvalidPermissionDuplicate) { return fmt.Errorf(`[WARN] A duplicate Security Group rule was found on (%s). This may be a side effect of a now-fixed Terraform issue causing two security groups with identical attributes but different source_security_group_ids to overwrite each other in the state. See https://github.com/hashicorp/terraform/pull/2376 for more -information and instructions for recovery. Error: %w`, sg_id, autherr) - } - if autherr != nil { - return fmt.Errorf("Error authorizing security group rule type %s: %w", ruleType, autherr) +information and instructions for recovery. Error: %w`, securityGroupID, err) } - var rules []*ec2.IpPermission - id := IPPermissionIDHash(sg_id, ruleType, perm) - log.Printf("[DEBUG] Computed group rule ID %s", id) + if err != nil { + return fmt.Errorf("authorizing Security Group (%s) Rule (%s): %w", securityGroupID, id, err) + } - err = resource.Retry(5*time.Minute, func() *resource.RetryError { - sg, err := FindSecurityGroupByID(conn, sg_id) + _, err = tfresource.RetryWhenNotFound(d.Timeout(schema.TimeoutCreate), func() (interface{}, error) { + sg, err := FindSecurityGroupByID(conn, securityGroupID) if err != nil { - log.Printf("[DEBUG] Error finding Security Group (%s) for Rule (%s): %s", sg_id, id, err) - return resource.NonRetryableError(err) + return nil, err } - switch ruleType { - case "ingress": + var rules []*ec2.IpPermission + + if ruleType == securityGroupRuleTypeIngress { rules = sg.IpPermissions - default: + } else { rules = sg.IpPermissionsEgress } - rule := findRuleMatch(perm, rules, isVPC) + rule, _ := findRuleMatch(ipPermission, rules, isVPC) + if rule == nil { - log.Printf("[DEBUG] Unable to find matching %s Security Group Rule (%s) for Group %s", - ruleType, id, sg_id) - return resource.RetryableError(fmt.Errorf("No match found")) + return nil, &resource.NotFoundError{} } - log.Printf("[DEBUG] Found rule for Security Group Rule (%s): %s", id, rule) - return nil + return rule, nil }) - if tfresource.TimedOut(err) { - sg, err := FindSecurityGroupByID(conn, sg_id) - if err != nil { - return fmt.Errorf("Error finding security group: %w", err) - } - switch ruleType { - case "ingress": - rules = sg.IpPermissions - default: - rules = sg.IpPermissionsEgress - } - - rule := findRuleMatch(perm, rules, isVPC) - if rule == nil { - return fmt.Errorf("Error finding matching security group rule: %w", err) - } - } if err != nil { - return fmt.Errorf("Error finding matching %s Security Group Rule (%s) for Group %s", ruleType, id, sg_id) + return fmt.Errorf("waiting for Security Group (%s) Rule (%s) create: %w", securityGroupID, id, err) } d.SetId(id) + return nil } func resourceSecurityGroupRuleRead(d *schema.ResourceData, meta interface{}) error { conn := meta.(*conns.AWSClient).EC2Conn - sg_id := d.Get("security_group_id").(string) - sg, err := FindSecurityGroupByID(conn, sg_id) + securityGroupID := d.Get("security_group_id").(string) + ruleType := d.Get("type").(string) + + sg, err := FindSecurityGroupByID(conn, securityGroupID) + if !d.IsNewResource() && tfresource.NotFound(err) { - log.Printf("[WARN] Security Group (%s) not found, removing Rule (%s) from state", sg_id, d.Id()) + log.Printf("[WARN] Security Group (%s) not found, removing from state", securityGroupID) d.SetId("") return nil } + if err != nil { - return fmt.Errorf("error finding Security Group (%s) for Rule (%s): %w", sg_id, d.Id(), err) + return fmt.Errorf("reading Security Group (%s): %w", securityGroupID, err) } + ipPermission := expandIpPermission(d, sg) isVPC := aws.StringValue(sg.VpcId) != "" - var rule *ec2.IpPermission var rules []*ec2.IpPermission - ruleType := d.Get("type").(string) - switch ruleType { - case "ingress": + + if ruleType == securityGroupRuleTypeIngress { rules = sg.IpPermissions - default: + } else { rules = sg.IpPermissionsEgress } - log.Printf("[DEBUG] Rules %v", rules) - p, err := expandIPPerm(d, sg) - if err != nil { - return err - } + rule, description := findRuleMatch(ipPermission, rules, isVPC) - if !d.IsNewResource() && len(rules) == 0 { - log.Printf("[WARN] No %s rules were found for Security Group (%s) looking for Security Group Rule (%s)", ruleType, aws.StringValue(sg.GroupName), d.Id()) - d.SetId("") - return nil - } - - rule = findRuleMatch(p, rules, isVPC) + if rule == nil { + if !d.IsNewResource() { + log.Printf("[WARN] Security Group (%s) Rule (%s) not found, removing from state", securityGroupID, d.Id()) + d.SetId("") + return nil + } - if !d.IsNewResource() && rule == nil { - log.Printf("[DEBUG] Unable to find matching %s Security Group Rule (%s) for Group %s", ruleType, d.Id(), sg_id) - d.SetId("") - return nil + // Shouldn't reach here as we aren't called from resourceSecurityGroupRuleCreate. + return fmt.Errorf("reading Security Group (%s) Rule (%s): %w", securityGroupID, d.Id(), &resource.NotFoundError{}) } - log.Printf("[DEBUG] Found rule for Security Group Rule (%s): %s", d.Id(), rule) - + flattenIpPermission(d, ipPermission, isVPC) + d.Set("description", description) d.Set("type", ruleType) - setFromIPPerm(d, sg, p) - - d.Set("description", descriptionFromIPPerm(d, rule)) - - if strings.Contains(d.Id(), "_") { + if strings.Contains(d.Id(), securityGroupRuleIDSeparator) { // import so fix the id - id := IPPermissionIDHash(sg_id, ruleType, p) + id := SecurityGroupRuleCreateID(securityGroupID, ruleType, ipPermission) d.SetId(id) } @@ -344,8 +283,46 @@ func resourceSecurityGroupRuleUpdate(d *schema.ResourceData, meta interface{}) e conn := meta.(*conns.AWSClient).EC2Conn if d.HasChange("description") { - if err := resourceSecurityGroupRuleDescriptionUpdate(conn, d); err != nil { - return err + securityGroupID := d.Get("security_group_id").(string) + + conns.GlobalMutexKV.Lock(securityGroupID) + defer conns.GlobalMutexKV.Unlock(securityGroupID) + + sg, err := FindSecurityGroupByID(conn, securityGroupID) + + if err != nil { + return fmt.Errorf("reading Security Group (%s): %w", securityGroupID, err) + } + + ipPermission := expandIpPermission(d, sg) + ruleType := d.Get("type").(string) + isVPC := aws.StringValue(sg.VpcId) != "" + + switch ruleType { + case securityGroupRuleTypeIngress: + input := &ec2.UpdateSecurityGroupRuleDescriptionsIngressInput{ + IpPermissions: []*ec2.IpPermission{ipPermission}, + } + + if isVPC { + input.GroupId = sg.GroupId + } else { + input.GroupName = sg.GroupName + } + + _, err = conn.UpdateSecurityGroupRuleDescriptionsIngress(input) + + case securityGroupRuleTypeEgress: + input := &ec2.UpdateSecurityGroupRuleDescriptionsEgressInput{ + GroupId: sg.GroupId, + IpPermissions: []*ec2.IpPermission{ipPermission}, + } + + _, err = conn.UpdateSecurityGroupRuleDescriptionsEgress(input) + } + + if err != nil { + return fmt.Errorf("updating Security Group (%s) Rule (%s) description: %w", securityGroupID, d.Id(), err) } } @@ -354,73 +331,156 @@ func resourceSecurityGroupRuleUpdate(d *schema.ResourceData, meta interface{}) e func resourceSecurityGroupRuleDelete(d *schema.ResourceData, meta interface{}) error { conn := meta.(*conns.AWSClient).EC2Conn - sg_id := d.Get("security_group_id").(string) + securityGroupID := d.Get("security_group_id").(string) - conns.GlobalMutexKV.Lock(sg_id) - defer conns.GlobalMutexKV.Unlock(sg_id) + conns.GlobalMutexKV.Lock(securityGroupID) + defer conns.GlobalMutexKV.Unlock(securityGroupID) - sg, err := FindSecurityGroupByID(conn, sg_id) - if err != nil { - return err - } + sg, err := FindSecurityGroupByID(conn, securityGroupID) - perm, err := expandIPPerm(d, sg) if err != nil { - return err + return fmt.Errorf("reading Security Group (%s): %w", securityGroupID, err) } + + ipPermission := expandIpPermission(d, sg) ruleType := d.Get("type").(string) + isVPC := aws.StringValue(sg.VpcId) != "" + switch ruleType { - case "ingress": - log.Printf("[DEBUG] Revoking rule (%s) from security group %s:\n%s", - "ingress", sg_id, perm) - req := &ec2.RevokeSecurityGroupIngressInput{ - GroupId: sg.GroupId, - IpPermissions: []*ec2.IpPermission{perm}, + case securityGroupRuleTypeIngress: + input := &ec2.RevokeSecurityGroupIngressInput{ + IpPermissions: []*ec2.IpPermission{ipPermission}, } - _, err = conn.RevokeSecurityGroupIngress(req) - - if err != nil { - return fmt.Errorf("Error revoking security group %s rules: %w", sg_id, err) + if isVPC { + input.GroupId = sg.GroupId + } else { + input.GroupName = sg.GroupName } - case "egress": - log.Printf("[DEBUG] Revoking security group %#v %s rule: %#v", sg_id, "egress", perm) - req := &ec2.RevokeSecurityGroupEgressInput{ + _, err = conn.RevokeSecurityGroupIngress(input) + + case securityGroupRuleTypeEgress: + input := &ec2.RevokeSecurityGroupEgressInput{ GroupId: sg.GroupId, - IpPermissions: []*ec2.IpPermission{perm}, + IpPermissions: []*ec2.IpPermission{ipPermission}, } - _, err = conn.RevokeSecurityGroupEgress(req) + _, err = conn.RevokeSecurityGroupEgress(input) + } - if err != nil { - return fmt.Errorf("Error revoking security group %s rules: %w", sg_id, err) - } + if tfawserr.ErrCodeEquals(err, errCodeInvalidPermissionNotFound) { + return nil + } + + if err != nil { + return fmt.Errorf("revoking Security Group (%s) Rule (%s): %w", securityGroupID, d.Id(), err) } return nil } -// ByGroupPair implements sort.Interface for []*ec2.UserIDGroupPairs based on -// GroupID or GroupName field (only one should be set). -type ByGroupPair []*ec2.UserIdGroupPair +func resourceSecurityGroupRuleImport(_ context.Context, d *schema.ResourceData, _ interface{}) ([]*schema.ResourceData, error) { + invalidIDError := func(msg string) error { + return fmt.Errorf("unexpected format for ID (%q), expected SECURITYGROUPID_TYPE_PROTOCOL_FROMPORT_TOPORT_SOURCE[_SOURCE]*: %s", d.Id(), msg) + } -func (b ByGroupPair) Len() int { return len(b) } -func (b ByGroupPair) Swap(i, j int) { b[i], b[j] = b[j], b[i] } -func (b ByGroupPair) Less(i, j int) bool { - if b[i].GroupId != nil && b[j].GroupId != nil { - return aws.StringValue(b[i].GroupId) < aws.StringValue(b[j].GroupId) + // example: sg-09a093729ef9382a6_ingress_tcp_8000_8000_10.0.3.0/24 + // example: sg-09a093729ef9382a6_ingress_92_0_65536_10.0.3.0/24_10.0.4.0/24 + // example: sg-09a093729ef9382a6_egress_tcp_8000_8000_10.0.3.0/24 + // example: sg-09a093729ef9382a6_egress_tcp_8000_8000_pl-34800000 + // example: sg-09a093729ef9382a6_ingress_all_0_65536_sg-08123412342323 + // example: sg-09a093729ef9382a6_ingress_tcp_100_121_10.1.0.0/16_2001:db8::/48_10.2.0.0/16_2002:db8::/48 + parts := strings.Split(d.Id(), securityGroupRuleIDSeparator) + + if len(parts) < 6 { + return nil, invalidIDError("too few parts") } - if b[i].GroupName != nil && b[j].GroupName != nil { - return aws.StringValue(b[i].GroupName) < aws.StringValue(b[j].GroupName) + + securityGroupID := parts[0] + ruleType := parts[1] + protocol := parts[2] + fromPort := parts[3] + toPort := parts[4] + sources := parts[5:] + + if !strings.HasPrefix(securityGroupID, "sg-") { + return nil, invalidIDError("invalid Security Group ID") } - //lintignore:R009 - panic("mismatched security group rules, may be a terraform bug") + if ruleType != securityGroupRuleTypeIngress && ruleType != securityGroupRuleTypeEgress { + return nil, invalidIDError("expecting 'ingress' or 'egress'") + } + + if _, ok := securityGroupProtocolIntegers[protocol]; !ok { + if _, err := strconv.Atoi(protocol); err != nil { + return nil, invalidIDError("protocol must be tcp/udp/icmp/icmpv6/all or a number") + } + } + + protocolName := ProtocolForValue(protocol) + if protocolName == "icmp" || protocolName == "icmpv6" { + if v, err := strconv.Atoi(fromPort); err != nil || v < -1 || v > 255 { + return nil, invalidIDError("invalid icmp type") + } else if v, err := strconv.Atoi(toPort); err != nil || v < -1 || v > 255 { + return nil, invalidIDError("invalid icmp code") + } + } else { + if p1, err := strconv.Atoi(fromPort); err != nil { + return nil, invalidIDError("invalid from_port") + } else if p2, err := strconv.Atoi(toPort); err != nil { + return nil, invalidIDError("invalid to_port") + } else if p2 < p1 { + return nil, invalidIDError("to_port lower than from_port") + } + } + + for _, v := range sources { + // will be properly validated later + if v != "self" && !strings.Contains(v, "sg-") && !strings.Contains(v, "pl-") && !strings.Contains(v, ":") && !strings.Contains(v, ".") { + return nil, invalidIDError("source must be cidr, ipv6cidr, prefix list, 'self', or a Security Group ID") + } + } + + d.Set("security_group_id", securityGroupID) + d.Set("type", ruleType) + d.Set("protocol", protocolName) + if v, err := strconv.Atoi(fromPort); err == nil { + d.Set("from_port", v) + } + if v, err := strconv.Atoi(toPort); err == nil { + d.Set("to_port", v) + } + d.Set("self", false) + + var cidrBlocks, ipv6CIDRBlocks, prefixListIDs []string + + for _, v := range sources { + switch { + case v == "self": + d.Set("self", true) + case strings.Contains(v, "sg-"): + d.Set("source_security_group_id", v) + case strings.Contains(v, ":"): + ipv6CIDRBlocks = append(ipv6CIDRBlocks, v) + case strings.Contains(v, "pl-"): + prefixListIDs = append(prefixListIDs, v) + default: + cidrBlocks = append(cidrBlocks, v) + } + } + + d.Set("cidr_blocks", cidrBlocks) + d.Set("ipv6_cidr_blocks", ipv6CIDRBlocks) + d.Set("prefix_list_ids", prefixListIDs) + + return []*schema.ResourceData{d}, nil } -func findRuleMatch(p *ec2.IpPermission, rules []*ec2.IpPermission, isVPC bool) *ec2.IpPermission { +func findRuleMatch(p *ec2.IpPermission, rules []*ec2.IpPermission, isVPC bool) (*ec2.IpPermission, string) { var rule *ec2.IpPermission + var description string + for _, r := range rules { if p.ToPort != nil && r.ToPort != nil && aws.Int64Value(p.ToPort) != aws.Int64Value(r.ToPort) { continue @@ -435,13 +495,17 @@ func findRuleMatch(p *ec2.IpPermission, rules []*ec2.IpPermission, isVPC bool) * } remaining := len(p.IpRanges) - for _, ip := range p.IpRanges { - for _, rip := range r.IpRanges { - if ip.CidrIp == nil || rip.CidrIp == nil { + for _, v1 := range p.IpRanges { + for _, v2 := range r.IpRanges { + if v1.CidrIp == nil || v2.CidrIp == nil { continue } - if aws.StringValue(ip.CidrIp) == aws.StringValue(rip.CidrIp) { + if aws.StringValue(v1.CidrIp) == aws.StringValue(v2.CidrIp) { remaining-- + + if v := aws.StringValue(v2.Description); v != "" && description == "" { + description = v + } } } } @@ -451,13 +515,17 @@ func findRuleMatch(p *ec2.IpPermission, rules []*ec2.IpPermission, isVPC bool) * } remaining = len(p.Ipv6Ranges) - for _, ipv6 := range p.Ipv6Ranges { - for _, ipv6ip := range r.Ipv6Ranges { - if ipv6.CidrIpv6 == nil || ipv6ip.CidrIpv6 == nil { + for _, v1 := range p.Ipv6Ranges { + for _, v2 := range r.Ipv6Ranges { + if v1.CidrIpv6 == nil || v2.CidrIpv6 == nil { continue } - if aws.StringValue(ipv6.CidrIpv6) == aws.StringValue(ipv6ip.CidrIpv6) { + if aws.StringValue(v1.CidrIpv6) == aws.StringValue(v2.CidrIpv6) { remaining-- + + if v := aws.StringValue(v2.Description); v != "" && description == "" { + description = v + } } } } @@ -467,13 +535,17 @@ func findRuleMatch(p *ec2.IpPermission, rules []*ec2.IpPermission, isVPC bool) * } remaining = len(p.PrefixListIds) - for _, pl := range p.PrefixListIds { - for _, rpl := range r.PrefixListIds { - if pl.PrefixListId == nil || rpl.PrefixListId == nil { + for _, v1 := range p.PrefixListIds { + for _, v2 := range r.PrefixListIds { + if v1.PrefixListId == nil || v2.PrefixListId == nil { continue } - if aws.StringValue(pl.PrefixListId) == aws.StringValue(rpl.PrefixListId) { + if aws.StringValue(v1.PrefixListId) == aws.StringValue(v2.PrefixListId) { remaining-- + + if v := aws.StringValue(v2.Description); v != "" && description == "" { + description = v + } } } } @@ -483,38 +555,70 @@ func findRuleMatch(p *ec2.IpPermission, rules []*ec2.IpPermission, isVPC bool) * } remaining = len(p.UserIdGroupPairs) - for _, ip := range p.UserIdGroupPairs { - for _, rip := range r.UserIdGroupPairs { + for _, v1 := range p.UserIdGroupPairs { + for _, v2 := range r.UserIdGroupPairs { if isVPC { - if ip.GroupId == nil || rip.GroupId == nil { + if v1.GroupId == nil || v2.GroupId == nil { continue } - if aws.StringValue(ip.GroupId) == aws.StringValue(rip.GroupId) { + if aws.StringValue(v1.GroupId) == aws.StringValue(v2.GroupId) { remaining-- + + if v := aws.StringValue(v2.Description); v != "" && description == "" { + description = v + } } } else { - if ip.GroupName == nil || rip.GroupName == nil { + if v1.GroupName == nil || v2.GroupName == nil { continue } - if aws.StringValue(ip.GroupName) == aws.StringValue(rip.GroupName) { + if aws.StringValue(v1.GroupName) == aws.StringValue(v2.GroupName) { remaining-- + + if v := aws.StringValue(v2.Description); v != "" && description == "" { + description = v + } } } } } if remaining > 0 { + description = "" + continue } rule = r } - return rule + + return rule, description +} + +const securityGroupRuleIDSeparator = "_" + +// byGroupPair implements sort.Interface for []*ec2.UserIDGroupPairs based on +// GroupID or GroupName field (only one should be set). +type byGroupPair []*ec2.UserIdGroupPair + +func (b byGroupPair) Len() int { return len(b) } +func (b byGroupPair) Swap(i, j int) { b[i], b[j] = b[j], b[i] } +func (b byGroupPair) Less(i, j int) bool { + if b[i].GroupId != nil && b[j].GroupId != nil { + return aws.StringValue(b[i].GroupId) < aws.StringValue(b[j].GroupId) + } + if b[i].GroupName != nil && b[j].GroupName != nil { + return aws.StringValue(b[i].GroupName) < aws.StringValue(b[j].GroupName) + } + + //lintignore:R009 + panic("mismatched security group rules, may be a terraform bug") } -func IPPermissionIDHash(sg_id, ruleType string, ip *ec2.IpPermission) string { +func SecurityGroupRuleCreateID(securityGroupID, ruleType string, ip *ec2.IpPermission) string { var buf bytes.Buffer - buf.WriteString(fmt.Sprintf("%s-", sg_id)) + + buf.WriteString(fmt.Sprintf("%s-", securityGroupID)) if aws.Int64Value(ip.FromPort) > 0 { buf.WriteString(fmt.Sprintf("%d-", *ip.FromPort)) } @@ -563,7 +667,7 @@ func IPPermissionIDHash(sg_id, ruleType string, ip *ec2.IpPermission) string { } if len(ip.UserIdGroupPairs) > 0 { - sort.Sort(ByGroupPair(ip.UserIdGroupPairs)) + sort.Sort(byGroupPair(ip.UserIdGroupPairs)) for _, pair := range ip.UserIdGroupPairs { if pair.GroupId != nil { buf.WriteString(fmt.Sprintf("%s-", *pair.GroupId)) @@ -581,436 +685,156 @@ func IPPermissionIDHash(sg_id, ruleType string, ip *ec2.IpPermission) string { return fmt.Sprintf("sgrule-%d", create.StringHashcode(buf.String())) } -func expandIPPerm(d *schema.ResourceData, sg *ec2.SecurityGroup) (*ec2.IpPermission, error) { - var perm ec2.IpPermission - - protocol := ProtocolForValue(d.Get("protocol").(string)) - perm.IpProtocol = aws.String(protocol) - - // InvalidParameterValue: When protocol is ALL, you cannot specify from-port. - if protocol != "-1" { - perm.FromPort = aws.Int64(int64(d.Get("from_port").(int))) - perm.ToPort = aws.Int64(int64(d.Get("to_port").(int))) +func expandIpPermission(d *schema.ResourceData, sg *ec2.SecurityGroup) *ec2.IpPermission { // nosemgrep:caps5-in-func-name + apiObject := &ec2.IpPermission{ + IpProtocol: aws.String(ProtocolForValue(d.Get("protocol").(string))), } - // build a group map that behaves like a set - groups := make(map[string]bool) - if raw, ok := d.GetOk("source_security_group_id"); ok { - groups[raw.(string)] = true + // InvalidParameterValue: When protocol is ALL, you cannot specify from-port. + if v := aws.StringValue(apiObject.IpProtocol); v != "-1" { + apiObject.FromPort = aws.Int64(int64(d.Get("from_port").(int))) + apiObject.ToPort = aws.Int64(int64(d.Get("to_port").(int))) } - if _, ok := d.GetOk("self"); ok { - if aws.StringValue(sg.VpcId) != "" { - groups[*sg.GroupId] = true - } else { - groups[*sg.GroupName] = true + if v, ok := d.GetOk("cidr_blocks"); ok && len(v.([]interface{})) > 0 { + for _, v := range v.([]interface{}) { + apiObject.IpRanges = append(apiObject.IpRanges, &ec2.IpRange{ + CidrIp: aws.String(v.(string)), + }) } } - description := d.Get("description").(string) - - if len(groups) > 0 { - perm.UserIdGroupPairs = make([]*ec2.UserIdGroupPair, len(groups)) - // build string list of group name/ids - var gl []string - for k := range groups { - gl = append(gl, k) - } - - for i, name := range gl { - ownerId, id := "", name - if items := strings.Split(id, "/"); len(items) > 1 { - ownerId, id = items[0], items[1] - } - - perm.UserIdGroupPairs[i] = &ec2.UserIdGroupPair{ - GroupId: aws.String(id), - UserId: aws.String(ownerId), - } - - if aws.StringValue(sg.VpcId) == "" { - perm.UserIdGroupPairs[i].GroupId = nil - perm.UserIdGroupPairs[i].GroupName = aws.String(id) - perm.UserIdGroupPairs[i].UserId = nil - } - - if description != "" { - perm.UserIdGroupPairs[i].Description = aws.String(description) - } + if v, ok := d.GetOk("ipv6_cidr_blocks"); ok && len(v.([]interface{})) > 0 { + for _, v := range v.([]interface{}) { + apiObject.Ipv6Ranges = append(apiObject.Ipv6Ranges, &ec2.Ipv6Range{ + CidrIpv6: aws.String(v.(string)), + }) } } - if raw, ok := d.GetOk("cidr_blocks"); ok { - list := raw.([]interface{}) - perm.IpRanges = make([]*ec2.IpRange, len(list)) - for i, v := range list { - cidrIP, ok := v.(string) - if !ok { - return nil, fmt.Errorf("empty element found in cidr_blocks - consider using the compact function") - } - perm.IpRanges[i] = &ec2.IpRange{CidrIp: aws.String(cidrIP)} - - if description != "" { - perm.IpRanges[i].Description = aws.String(description) - } + if v, ok := d.GetOk("prefix_list_ids"); ok && len(v.([]interface{})) > 0 { + for _, v := range v.([]interface{}) { + apiObject.PrefixListIds = append(apiObject.PrefixListIds, &ec2.PrefixListId{ + PrefixListId: aws.String(v.(string)), + }) } } - 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)} + var self string + vpc := aws.StringValue(sg.VpcId) != "" - if description != "" { - perm.Ipv6Ranges[i].Description = aws.String(description) - } - } - } - - if raw, ok := d.GetOk("prefix_list_ids"); ok { - list := raw.([]interface{}) - perm.PrefixListIds = make([]*ec2.PrefixListId, len(list)) - for i, v := range list { - prefixListID, ok := v.(string) - if !ok { - return nil, fmt.Errorf("empty element found in prefix_list_ids - consider using the compact function") - } - perm.PrefixListIds[i] = &ec2.PrefixListId{PrefixListId: aws.String(prefixListID)} - - if description != "" { - perm.PrefixListIds[i].Description = aws.String(description) - } + if _, ok := d.GetOk("self"); ok { + if vpc { + self = aws.StringValue(sg.GroupId) + apiObject.UserIdGroupPairs = append(apiObject.UserIdGroupPairs, &ec2.UserIdGroupPair{ + GroupId: aws.String(self), + }) + } else { + self = aws.StringValue(sg.GroupName) + apiObject.UserIdGroupPairs = append(apiObject.UserIdGroupPairs, &ec2.UserIdGroupPair{ + GroupName: aws.String(self), + }) } } - return &perm, nil -} - -func setFromIPPerm(d *schema.ResourceData, sg *ec2.SecurityGroup, rule *ec2.IpPermission) { - isVPC := aws.StringValue(sg.VpcId) != "" - - d.Set("from_port", rule.FromPort) - d.Set("to_port", rule.ToPort) - d.Set("protocol", rule.IpProtocol) - - var cb []string - for _, c := range rule.IpRanges { - cb = append(cb, *c.CidrIp) - } - 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) - } - d.Set("prefix_list_ids", pl) - - if len(rule.UserIdGroupPairs) > 0 { - s := rule.UserIdGroupPairs[0] - - if isVPC { - if existingSourceSgId, ok := d.GetOk("source_security_group_id"); ok { - sgIdComponents := strings.Split(existingSourceSgId.(string), "/") - hasAccountIdPrefix := len(sgIdComponents) == 2 - - if hasAccountIdPrefix && s.UserId != nil { - // then ensure on refresh that we preserve the account id prefix - d.Set("source_security_group_id", fmt.Sprintf("%s/%s", aws.StringValue(s.UserId), aws.StringValue(s.GroupId))) + if v, ok := d.GetOk("source_security_group_id"); ok { + if v := v.(string); v != self { + if vpc { + // [OwnerID/]SecurityGroupID. + if parts := strings.Split(v, "/"); len(parts) == 1 { + apiObject.UserIdGroupPairs = append(apiObject.UserIdGroupPairs, &ec2.UserIdGroupPair{ + GroupId: aws.String(v), + }) } else { - d.Set("source_security_group_id", s.GroupId) + apiObject.UserIdGroupPairs = append(apiObject.UserIdGroupPairs, &ec2.UserIdGroupPair{ + GroupId: aws.String(parts[1]), + UserId: aws.String(parts[0]), + }) } } else { - d.Set("source_security_group_id", s.GroupId) + apiObject.UserIdGroupPairs = append(apiObject.UserIdGroupPairs, &ec2.UserIdGroupPair{ + GroupName: aws.String(v), + }) } - } else { - d.Set("source_security_group_id", s.GroupName) } } -} -func descriptionFromIPPerm(d *schema.ResourceData, rule *ec2.IpPermission) string { - // probe IpRanges - cidrIps := make(map[string]bool) - if raw, ok := d.GetOk("cidr_blocks"); ok { - for _, v := range raw.([]interface{}) { - cidrIps[v.(string)] = true - } - } - - if len(cidrIps) > 0 { - for _, c := range rule.IpRanges { - if _, ok := cidrIps[*c.CidrIp]; !ok { - continue - } + if v, ok := d.GetOk("description"); ok { + description := v.(string) - if desc := aws.StringValue(c.Description); desc != "" { - return desc - } + for _, v := range apiObject.IpRanges { + v.Description = aws.String(description) } - } - // probe Ipv6Ranges - cidrIpv6s := make(map[string]bool) - if raw, ok := d.GetOk("ipv6_cidr_blocks"); ok { - for _, v := range raw.([]interface{}) { - cidrIpv6s[v.(string)] = true + for _, v := range apiObject.Ipv6Ranges { + v.Description = aws.String(description) } - } - - if len(cidrIpv6s) > 0 { - for _, ip := range rule.Ipv6Ranges { - if _, ok := cidrIpv6s[*ip.CidrIpv6]; !ok { - continue - } - - if desc := aws.StringValue(ip.Description); desc != "" { - return desc - } - } - } - // probe PrefixListIds - listIds := make(map[string]bool) - if raw, ok := d.GetOk("prefix_list_ids"); ok { - for _, v := range raw.([]interface{}) { - listIds[v.(string)] = true + for _, v := range apiObject.PrefixListIds { + v.Description = aws.String(description) } - } - - if len(listIds) > 0 { - for _, p := range rule.PrefixListIds { - if _, ok := listIds[*p.PrefixListId]; !ok { - continue - } - if desc := aws.StringValue(p.Description); desc != "" { - return desc - } + for _, v := range apiObject.UserIdGroupPairs { + v.Description = aws.String(description) } } - // probe UserIdGroupPairs - if raw, ok := d.GetOk("source_security_group_id"); ok { - components := strings.Split(raw.(string), "/") - - switch len(components) { - case 2: - userId := components[0] - groupId := components[1] - - for _, gp := range rule.UserIdGroupPairs { - if aws.StringValue(gp.GroupId) != groupId || aws.StringValue(gp.UserId) != userId { - continue - } - - if desc := aws.StringValue(gp.Description); desc != "" { - return desc - } - } - case 1: - groupId := components[0] - for _, gp := range rule.UserIdGroupPairs { - if aws.StringValue(gp.GroupId) != groupId { - continue - } - - if desc := aws.StringValue(gp.Description); desc != "" { - return desc - } - } - } - } - - return "" + return apiObject } -// Validates that either 'cidr_blocks', 'ipv6_cidr_blocks', 'self', or 'source_security_group_id' is set -func validSecurityGroupRule(d *schema.ResourceData) error { - blocks, blocksOk := d.GetOk("cidr_blocks") - self, selfOk := d.GetOk("self") - if blocksOk && self.(bool) { - return fmt.Errorf("'self': conflicts with 'cidr_blocks' (%#v)", blocks) +func flattenIpPermission(d *schema.ResourceData, apiObject *ec2.IpPermission, isVPC bool) { // nosemgrep:caps5-in-func-name + if apiObject == nil { + return } - _, ipv6Ok := d.GetOk("ipv6_cidr_blocks") - _, sourceOk := d.GetOk("source_security_group_id") - _, prefixOk := d.GetOk("prefix_list_ids") - if !blocksOk && !sourceOk && !selfOk && !prefixOk && !ipv6Ok { - return fmt.Errorf( - "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 -} - -func resourceSecurityGroupRuleDescriptionUpdate(conn *ec2.EC2, d *schema.ResourceData) error { - sg_id := d.Get("security_group_id").(string) + d.Set("from_port", apiObject.FromPort) + d.Set("protocol", apiObject.IpProtocol) + d.Set("to_port", apiObject.ToPort) - conns.GlobalMutexKV.Lock(sg_id) - defer conns.GlobalMutexKV.Unlock(sg_id) - - sg, err := FindSecurityGroupByID(conn, sg_id) - if err != nil { - return err - } + if v := apiObject.IpRanges; len(v) > 0 { + var ipRanges []string - perm, err := expandIPPerm(d, sg) - if err != nil { - return err - } - ruleType := d.Get("type").(string) - switch ruleType { - case "ingress": - req := &ec2.UpdateSecurityGroupRuleDescriptionsIngressInput{ - GroupId: sg.GroupId, - IpPermissions: []*ec2.IpPermission{perm}, - } - - _, err = conn.UpdateSecurityGroupRuleDescriptionsIngress(req) - - if err != nil { - return fmt.Errorf("Error updating security group %s rule description: %w", sg_id, err) + for _, v := range v { + ipRanges = append(ipRanges, aws.StringValue(v.CidrIp)) } - case "egress": - req := &ec2.UpdateSecurityGroupRuleDescriptionsEgressInput{ - GroupId: sg.GroupId, - IpPermissions: []*ec2.IpPermission{perm}, - } - - _, err = conn.UpdateSecurityGroupRuleDescriptionsEgress(req) - if err != nil { - return fmt.Errorf("Error updating security group %s rule description: %w", sg_id, err) - } + d.Set("cidr_blocks", ipRanges) } - return nil -} + if v := apiObject.Ipv6Ranges; len(v) > 0 { + var ipv6Ranges []string -// validateSecurityGroupRuleImportString does minimal validation of import string without going to AWS -func validateSecurityGroupRuleImportString(importStr string) ([]string, error) { - // example: sg-09a093729ef9382a6_ingress_tcp_8000_8000_10.0.3.0/24 - // example: sg-09a093729ef9382a6_ingress_92_0_65536_10.0.3.0/24_10.0.4.0/24 - // example: sg-09a093729ef9382a6_egress_tcp_8000_8000_10.0.3.0/24 - // example: sg-09a093729ef9382a6_egress_tcp_8000_8000_pl-34800000 - // example: sg-09a093729ef9382a6_ingress_all_0_65536_sg-08123412342323 - // example: sg-09a093729ef9382a6_ingress_tcp_100_121_10.1.0.0/16_2001:db8::/48_10.2.0.0/16_2002:db8::/48 - - log.Printf("[DEBUG] Validating import string %s", importStr) - - importParts := strings.Split(strings.ToLower(importStr), "_") - errStr := "unexpected format of import string (%q), expected SECURITYGROUPID_TYPE_PROTOCOL_FROMPORT_TOPORT_SOURCE[_SOURCE]*: %s" - if len(importParts) < 6 { - return nil, fmt.Errorf(errStr, importStr, "too few parts") - } - - sgID := importParts[0] - ruleType := importParts[1] - protocol := importParts[2] - fromPort := importParts[3] - toPort := importParts[4] - sources := importParts[5:] - - if !strings.HasPrefix(sgID, "sg-") { - return nil, fmt.Errorf(errStr, importStr, "invalid security group ID") - } - - if ruleType != "ingress" && ruleType != "egress" { - return nil, fmt.Errorf(errStr, importStr, "expecting 'ingress' or 'egress'") - } - - if _, ok := sgProtocolIntegers()[protocol]; !ok { - if _, err := strconv.Atoi(protocol); err != nil { - return nil, fmt.Errorf(errStr, importStr, "protocol must be tcp/udp/icmp/icmpv6/all or a number") - } - } - - protocolName := ProtocolForValue(protocol) - if protocolName == "icmp" || protocolName == "icmpv6" { - if itype, err := strconv.Atoi(fromPort); err != nil || itype < -1 || itype > 255 { - return nil, fmt.Errorf(errStr, importStr, "invalid icmp type") - } else if icode, err := strconv.Atoi(toPort); err != nil || icode < -1 || icode > 255 { - return nil, fmt.Errorf(errStr, importStr, "invalid icmp code") + for _, v := range v { + ipv6Ranges = append(ipv6Ranges, aws.StringValue(v.CidrIpv6)) } - } else { - if p1, err := strconv.Atoi(fromPort); err != nil { - return nil, fmt.Errorf(errStr, importStr, "invalid from_port") - } else if p2, err := strconv.Atoi(toPort); err != nil { - return nil, fmt.Errorf(errStr, importStr, "invalid to_port") - } else if p2 < p1 { - return nil, fmt.Errorf(errStr, importStr, "to_port lower than from_port") - } - } - for _, source := range sources { - // will be properly validated later - if source != "self" && !strings.Contains(source, "sg-") && !strings.Contains(source, "pl-") && !strings.Contains(source, ":") && !strings.Contains(source, ".") { - return nil, fmt.Errorf(errStr, importStr, "source must be cidr, ipv6cidr, prefix list, 'self', or a sg ID") - } + d.Set("ipv6_cidr_blocks", ipv6Ranges) } - log.Printf("[DEBUG] Validated import string %s", importStr) - return importParts, nil -} - -func populateSecurityGroupRuleFromImport(d *schema.ResourceData, importParts []string) error { - log.Printf("[DEBUG] Populating resource data on import: %v", importParts) + if v := apiObject.PrefixListIds; len(v) > 0 { + var prefixListIDs []string - sgID := importParts[0] - ruleType := importParts[1] - protocol := importParts[2] - fromPort, err := strconv.Atoi(importParts[3]) - if err != nil { - return err - } - toPort, err := strconv.Atoi(importParts[4]) - if err != nil { - return err - } - sources := importParts[5:] - - d.Set("security_group_id", sgID) + for _, v := range v { + prefixListIDs = append(prefixListIDs, aws.StringValue(v.PrefixListId)) + } - if ruleType == "ingress" { - d.Set("type", ruleType) - } else { - d.Set("type", "egress") + d.Set("prefix_list_ids", prefixListIDs) } - d.Set("protocol", ProtocolForValue(protocol)) - d.Set("from_port", fromPort) - d.Set("to_port", toPort) + if v := apiObject.UserIdGroupPairs; len(v) > 0 { + v := v[0] - d.Set("self", false) - var cidrs []string - var prefixList []string - var ipv6cidrs []string - for _, source := range sources { - if source == "self" { - d.Set("self", true) - } else if strings.Contains(source, "sg-") { - d.Set("source_security_group_id", source) - } else if strings.Contains(source, "pl-") { - prefixList = append(prefixList, source) - } else if strings.Contains(source, ":") { - ipv6cidrs = append(ipv6cidrs, source) + if isVPC { + if old, ok := d.GetOk("source_security_group_id"); ok { + // [OwnerID/]SecurityGroupID. + if parts := strings.Split(old.(string), "/"); len(parts) == 1 || aws.StringValue(v.UserId) == "" { + d.Set("source_security_group_id", v.GroupId) + } else { + d.Set("source_security_group_id", strings.Join([]string{aws.StringValue(v.UserId), aws.StringValue(v.GroupId)}, "/")) + } + } } else { - cidrs = append(cidrs, source) + d.Set("source_security_group_id", v.GroupName) } } - d.Set("ipv6_cidr_blocks", ipv6cidrs) - d.Set("cidr_blocks", cidrs) - d.Set("prefix_list_ids", prefixList) - - return nil } diff --git a/internal/service/ec2/vpc_security_group_rule_migrate.go b/internal/service/ec2/vpc_security_group_rule_migrate.go index 832081e340f7..5e06fb82b62d 100644 --- a/internal/service/ec2/vpc_security_group_rule_migrate.go +++ b/internal/service/ec2/vpc_security_group_rule_migrate.go @@ -41,7 +41,7 @@ func migrateSGRuleStateV0toV1(is *terraform.InstanceState) (*terraform.InstanceS } log.Printf("[DEBUG] Attributes before migration: %#v", is.Attributes) - newID := IPPermissionIDHash(is.Attributes["security_group_id"], is.Attributes["type"], perm) + newID := SecurityGroupRuleCreateID(is.Attributes["security_group_id"], is.Attributes["type"], perm) is.Attributes["id"] = newID is.ID = newID log.Printf("[DEBUG] Attributes after migration: %#v, new id: %s", is.Attributes, newID) diff --git a/internal/service/ec2/vpc_security_group_rule_test.go b/internal/service/ec2/vpc_security_group_rule_test.go index 939286c4faa6..db96922c9daa 100644 --- a/internal/service/ec2/vpc_security_group_rule_test.go +++ b/internal/service/ec2/vpc_security_group_rule_test.go @@ -1,9 +1,7 @@ package ec2_test import ( - "bytes" "fmt" - "log" "regexp" "strconv" "strings" @@ -13,14 +11,13 @@ import ( "github.com/aws/aws-sdk-go/service/ec2" sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "github.com/hashicorp/terraform-provider-aws/internal/acctest" - "github.com/hashicorp/terraform-provider-aws/internal/conns" tfec2 "github.com/hashicorp/terraform-provider-aws/internal/service/ec2" - "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) -func TestIPPermissionIDHash(t *testing.T) { +func TestSecurityGroupRuleCreateID(t *testing.T) { simple := &ec2.IpPermission{ IpProtocol: aws.String("tcp"), FromPort: aws.Int64(80), @@ -106,7 +103,7 @@ func TestIPPermissionIDHash(t *testing.T) { } for _, tc := range cases { - actual := tfec2.IPPermissionIDHash("sg-12345", tc.Type, tc.Input) + actual := tfec2.SecurityGroupRuleCreateID("sg-12345", tc.Type, tc.Input) if actual != tc.Output { t.Errorf("input: %s - %s\noutput: %s", tc.Type, tc.Input, actual) } @@ -115,43 +112,38 @@ func TestIPPermissionIDHash(t *testing.T) { func TestAccVPCSecurityGroupRule_Ingress_vpc(t *testing.T) { var group ec2.SecurityGroup - rInt := sdkacctest.RandInt() - - 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 aws.Int64Value(rule.FromPort) != int64(80) { - return fmt.Errorf("Wrong Security Group port setting, expected %d, got %d", - 80, aws.Int64Value(rule.FromPort)) - } - - return nil - } + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_security_group_rule.test" + sgResourceName := "aws_security_group.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), ProviderFactories: acctest.ProviderFactories, - CheckDestroy: testAccCheckSecurityGroupRuleDestroy, + CheckDestroy: testAccCheckSecurityGroupDestroy, Steps: []resource.TestStep{ { - Config: testAccVPCSecurityGroupRuleConfig_ingress(rInt), - Check: resource.ComposeTestCheckFunc( - testAccCheckSecurityGroupRuleExists("aws_security_group.web", &group), - testAccCheckSecurityGroupRuleAttributes("aws_security_group_rule.ingress_1", &group, nil, "ingress"), - resource.TestCheckResourceAttr( - "aws_security_group_rule.ingress_1", "from_port", "80"), - testRuleCount, + Config: testAccVPCSecurityGroupRuleConfig_ingress(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckSecurityGroupExists(sgResourceName, &group), + resource.TestCheckResourceAttr(resourceName, "cidr_blocks.#", "1"), + resource.TestCheckResourceAttr(resourceName, "cidr_blocks.0", "10.0.0.0/8"), + resource.TestCheckNoResourceAttr(resourceName, "description"), + resource.TestCheckResourceAttr(resourceName, "from_port", "80"), + resource.TestCheckResourceAttr(resourceName, "ipv6_cidr_blocks.#", "0"), + resource.TestCheckResourceAttr(resourceName, "protocol", "tcp"), + resource.TestCheckResourceAttr(resourceName, "prefix_list_ids.#", "0"), + resource.TestCheckResourceAttrPair(resourceName, "security_group_id", sgResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "self", "false"), + resource.TestCheckNoResourceAttr(resourceName, "source_security_group_id"), + resource.TestCheckResourceAttr(resourceName, "to_port", "8000"), + resource.TestCheckResourceAttr(resourceName, "type", "ingress"), ), }, { - ResourceName: "aws_security_group_rule.ingress_1", + ResourceName: resourceName, ImportState: true, - ImportStateIdFunc: testAccSecurityGroupRuleImportStateIdFunc("aws_security_group_rule.ingress_1"), + ImportStateIdFunc: testAccSecurityGroupRuleImportStateIdFunc(resourceName), ImportStateVerify: true, }, }, @@ -160,33 +152,31 @@ func TestAccVPCSecurityGroupRule_Ingress_vpc(t *testing.T) { func TestAccVPCSecurityGroupRule_IngressSourceWithAccount_id(t *testing.T) { var group ec2.SecurityGroup - - rInt := sdkacctest.RandInt() - - ruleName := "aws_security_group_rule.allow_self" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_security_group_rule.test" + sgResourceName := "aws_security_group.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), ProviderFactories: acctest.ProviderFactories, - CheckDestroy: testAccCheckSecurityGroupRuleDestroy, + CheckDestroy: testAccCheckSecurityGroupDestroy, Steps: []resource.TestStep{ { - Config: testAccVPCSecurityGroupRuleConfig_ingressSourceAccountID(rInt), - Check: resource.ComposeTestCheckFunc( - testAccCheckSecurityGroupRuleExists("aws_security_group.web", &group), - resource.TestCheckResourceAttrPair( - ruleName, "security_group_id", "aws_security_group.web", "id"), - resource.TestMatchResourceAttr( - ruleName, "source_security_group_id", regexp.MustCompile("^[0-9]{12}/sg-[0-9a-z]{17}$")), - resource.TestCheckResourceAttr( - ruleName, "description", "some description"), - resource.TestCheckResourceAttr( - ruleName, "from_port", "0"), - resource.TestCheckResourceAttr( - ruleName, "to_port", "0"), - resource.TestCheckResourceAttr( - ruleName, "protocol", "-1"), + Config: testAccVPCSecurityGroupRuleConfig_ingressSourceAccountID(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckSecurityGroupExists(sgResourceName, &group), + resource.TestCheckResourceAttr(resourceName, "cidr_blocks.#", "0"), + resource.TestCheckResourceAttr(resourceName, "description", "some description"), + resource.TestCheckResourceAttr(resourceName, "from_port", "0"), + resource.TestCheckResourceAttr(resourceName, "ipv6_cidr_blocks.#", "0"), + resource.TestCheckResourceAttr(resourceName, "protocol", "-1"), + resource.TestCheckResourceAttr(resourceName, "prefix_list_ids.#", "0"), + resource.TestCheckResourceAttrPair(resourceName, "security_group_id", sgResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "self", "false"), + resource.TestMatchResourceAttr(resourceName, "source_security_group_id", regexp.MustCompile("^[0-9]{12}/sg-[0-9a-z]{17}$")), + resource.TestCheckResourceAttr(resourceName, "to_port", "0"), + resource.TestCheckResourceAttr(resourceName, "type", "ingress"), ), }, }, @@ -195,42 +185,38 @@ func TestAccVPCSecurityGroupRule_IngressSourceWithAccount_id(t *testing.T) { func TestAccVPCSecurityGroupRule_Ingress_protocol(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, aws.Int64Value(rule.FromPort)) - } - - return nil - } + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_security_group_rule.test" + sgResourceName := "aws_security_group.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), ProviderFactories: acctest.ProviderFactories, - CheckDestroy: testAccCheckSecurityGroupRuleDestroy, + CheckDestroy: testAccCheckSecurityGroupDestroy, Steps: []resource.TestStep{ { - Config: testAccVPCSecurityGroupRuleConfig_ingressProtocol, - Check: resource.ComposeTestCheckFunc( - testAccCheckSecurityGroupRuleExists("aws_security_group.web", &group), - testAccCheckSecurityGroupRuleAttributes("aws_security_group_rule.ingress_1", &group, nil, "ingress"), - resource.TestCheckResourceAttr( - "aws_security_group_rule.ingress_1", "from_port", "80"), - testRuleCount, + Config: testAccVPCSecurityGroupRuleConfig_ingressProtocol(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckSecurityGroupExists(sgResourceName, &group), + resource.TestCheckResourceAttr(resourceName, "cidr_blocks.#", "1"), + resource.TestCheckResourceAttr(resourceName, "cidr_blocks.0", "10.0.0.0/8"), + resource.TestCheckNoResourceAttr(resourceName, "description"), + resource.TestCheckResourceAttr(resourceName, "from_port", "80"), + resource.TestCheckResourceAttr(resourceName, "ipv6_cidr_blocks.#", "0"), + resource.TestCheckResourceAttr(resourceName, "protocol", "tcp"), + resource.TestCheckResourceAttr(resourceName, "prefix_list_ids.#", "0"), + resource.TestCheckResourceAttrPair(resourceName, "security_group_id", sgResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "self", "false"), + resource.TestCheckNoResourceAttr(resourceName, "source_security_group_id"), + resource.TestCheckResourceAttr(resourceName, "to_port", "8000"), + resource.TestCheckResourceAttr(resourceName, "type", "ingress"), ), }, { - ResourceName: "aws_security_group_rule.ingress_1", + ResourceName: resourceName, ImportState: true, - ImportStateIdFunc: testAccSecurityGroupRuleImportStateIdFunc("aws_security_group_rule.ingress_1"), + ImportStateIdFunc: testAccSecurityGroupRuleImportStateIdFunc(resourceName), ImportStateVerify: true, }, }, @@ -239,6 +225,7 @@ func TestAccVPCSecurityGroupRule_Ingress_protocol(t *testing.T) { func TestAccVPCSecurityGroupRule_Ingress_icmpv6(t *testing.T) { var group ec2.SecurityGroup + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_security_group_rule.test" sgResourceName := "aws_security_group.test" @@ -246,18 +233,24 @@ func TestAccVPCSecurityGroupRule_Ingress_icmpv6(t *testing.T) { PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), ProviderFactories: acctest.ProviderFactories, - CheckDestroy: testAccCheckSecurityGroupRuleDestroy, + CheckDestroy: testAccCheckSecurityGroupDestroy, Steps: []resource.TestStep{ { - Config: testAccVPCSecurityGroupRuleConfig_ingressIcmpv6, - Check: resource.ComposeTestCheckFunc( - testAccCheckSecurityGroupRuleExists(sgResourceName, &group), + Config: testAccVPCSecurityGroupRuleConfig_ingressIcmpv6(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckSecurityGroupExists(sgResourceName, &group), + resource.TestCheckResourceAttr(resourceName, "cidr_blocks.#", "0"), + resource.TestCheckNoResourceAttr(resourceName, "description"), resource.TestCheckResourceAttr(resourceName, "from_port", "-1"), - resource.TestCheckResourceAttr(resourceName, "to_port", "-1"), - resource.TestCheckResourceAttr(resourceName, "protocol", "icmpv6"), - resource.TestCheckResourceAttr(resourceName, "type", "ingress"), resource.TestCheckResourceAttr(resourceName, "ipv6_cidr_blocks.#", "1"), resource.TestCheckResourceAttr(resourceName, "ipv6_cidr_blocks.0", "::/0"), + resource.TestCheckResourceAttr(resourceName, "protocol", "icmpv6"), + resource.TestCheckResourceAttr(resourceName, "prefix_list_ids.#", "0"), + resource.TestCheckResourceAttrPair(resourceName, "security_group_id", sgResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "self", "false"), + resource.TestCheckNoResourceAttr(resourceName, "source_security_group_id"), + resource.TestCheckResourceAttr(resourceName, "to_port", "-1"), + resource.TestCheckResourceAttr(resourceName, "type", "ingress"), ), }, { @@ -272,45 +265,38 @@ func TestAccVPCSecurityGroupRule_Ingress_icmpv6(t *testing.T) { func TestAccVPCSecurityGroupRule_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, aws.Int64Value(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 - } + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_security_group_rule.test" + sgResourceName := "aws_security_group.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), ProviderFactories: acctest.ProviderFactories, - CheckDestroy: testAccCheckSecurityGroupRuleDestroy, + CheckDestroy: testAccCheckSecurityGroupDestroy, Steps: []resource.TestStep{ { - Config: testAccVPCSecurityGroupRuleConfig_ingressIPv6, - Check: resource.ComposeTestCheckFunc( - testAccCheckSecurityGroupRuleExists("aws_security_group.web", &group), - testRuleCount, + Config: testAccVPCSecurityGroupRuleConfig_ingressIPv6(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckSecurityGroupExists(sgResourceName, &group), + resource.TestCheckResourceAttr(resourceName, "cidr_blocks.#", "0"), + resource.TestCheckNoResourceAttr(resourceName, "description"), + resource.TestCheckResourceAttr(resourceName, "from_port", "80"), + resource.TestCheckResourceAttr(resourceName, "ipv6_cidr_blocks.#", "1"), + resource.TestCheckResourceAttr(resourceName, "ipv6_cidr_blocks.0", "::/0"), + resource.TestCheckResourceAttr(resourceName, "protocol", "tcp"), + resource.TestCheckResourceAttr(resourceName, "prefix_list_ids.#", "0"), + resource.TestCheckResourceAttrPair(resourceName, "security_group_id", sgResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "self", "false"), + resource.TestCheckNoResourceAttr(resourceName, "source_security_group_id"), + resource.TestCheckResourceAttr(resourceName, "to_port", "8000"), + resource.TestCheckResourceAttr(resourceName, "type", "ingress"), ), }, { - ResourceName: "aws_security_group_rule.ingress_1", + ResourceName: resourceName, ImportState: true, - ImportStateIdFunc: testAccSecurityGroupRuleImportStateIdFunc("aws_security_group_rule.ingress_1"), + ImportStateIdFunc: testAccSecurityGroupRuleImportStateIdFunc(resourceName), ImportStateVerify: true, }, }, @@ -319,90 +305,38 @@ func TestAccVPCSecurityGroupRule_Ingress_ipv6(t *testing.T) { func TestAccVPCSecurityGroupRule_Ingress_classic(t *testing.T) { var group ec2.SecurityGroup - rInt := sdkacctest.RandInt() - - 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, aws.Int64Value(rule.FromPort)) - } - - return nil - } - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t) }, - ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), - ProviderFactories: acctest.ProviderFactories, - CheckDestroy: testAccCheckSecurityGroupRuleDestroy, - Steps: []resource.TestStep{ - { - Config: testAccVPCSecurityGroupRuleConfig_ingressClassic(rInt), - Check: resource.ComposeTestCheckFunc( - testAccCheckSecurityGroupRuleExists("aws_security_group.web", &group), - testAccCheckSecurityGroupRuleAttributes("aws_security_group_rule.ingress_1", &group, nil, "ingress"), - resource.TestCheckResourceAttr( - "aws_security_group_rule.ingress_1", "from_port", "80"), - testRuleCount, - ), - }, - { - ResourceName: "aws_security_group_rule.ingress_1", - ImportState: true, - ImportStateIdFunc: testAccSecurityGroupRuleImportStateIdFunc("aws_security_group_rule.ingress_1"), - ImportStateVerify: true, - }, - }, - }) -} - -func TestAccVPCSecurityGroupRule_multiIngress(t *testing.T) { - var group ec2.SecurityGroup - - testMultiRuleCount := func(*terraform.State) error { - if len(group.IpPermissions) != 2 { - return fmt.Errorf("Wrong Security Group rule count, expected %d, got %d", - 2, len(group.IpPermissions)) - } - - var rule *ec2.IpPermission - for _, r := range group.IpPermissions { - if *r.FromPort == int64(80) { - rule = r - } - } - - if *rule.ToPort != int64(8000) { - return fmt.Errorf("Wrong Security Group port 2 setting, expected %d, got %d", - 8000, aws.Int64Value(rule.ToPort)) - } - - return nil - } + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_security_group_rule.test" + sgResourceName := "aws_security_group.test" resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t) }, + PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckEC2Classic(t) }, ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), ProviderFactories: acctest.ProviderFactories, - CheckDestroy: testAccCheckSecurityGroupRuleDestroy, + CheckDestroy: testAccCheckSecurityGroupDestroy, Steps: []resource.TestStep{ { - Config: testAccVPCSecurityGroupRuleConfig_multiIngress, - Check: resource.ComposeTestCheckFunc( - testAccCheckSecurityGroupRuleExists("aws_security_group.web", &group), - testMultiRuleCount, + Config: testAccVPCSecurityGroupRuleConfig_ingressClassic(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckSecurityGroupEC2ClassicExists(sgResourceName, &group), + resource.TestCheckResourceAttr(resourceName, "cidr_blocks.#", "1"), + resource.TestCheckResourceAttr(resourceName, "cidr_blocks.0", "10.0.0.0/8"), + resource.TestCheckNoResourceAttr(resourceName, "description"), + resource.TestCheckResourceAttr(resourceName, "from_port", "80"), + resource.TestCheckResourceAttr(resourceName, "ipv6_cidr_blocks.#", "0"), + resource.TestCheckResourceAttr(resourceName, "protocol", "tcp"), + resource.TestCheckResourceAttr(resourceName, "prefix_list_ids.#", "0"), + resource.TestCheckResourceAttrPair(resourceName, "security_group_id", sgResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "self", "false"), + resource.TestCheckNoResourceAttr(resourceName, "source_security_group_id"), + resource.TestCheckResourceAttr(resourceName, "to_port", "8000"), + resource.TestCheckResourceAttr(resourceName, "type", "ingress"), ), }, { - ResourceName: "aws_security_group_rule.ingress_2", + ResourceName: resourceName, ImportState: true, - ImportStateIdFunc: testAccSecurityGroupRuleImportStateIdFunc("aws_security_group_rule.ingress_2"), + ImportStateIdFunc: testAccSecurityGroupRuleImportStateIdFunc(resourceName), ImportStateVerify: true, }, }, @@ -411,25 +345,38 @@ func TestAccVPCSecurityGroupRule_multiIngress(t *testing.T) { func TestAccVPCSecurityGroupRule_egress(t *testing.T) { var group ec2.SecurityGroup - rInt := sdkacctest.RandInt() + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_security_group_rule.test" + sgResourceName := "aws_security_group.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), ProviderFactories: acctest.ProviderFactories, - CheckDestroy: testAccCheckSecurityGroupRuleDestroy, + CheckDestroy: testAccCheckSecurityGroupDestroy, Steps: []resource.TestStep{ { - Config: testAccVPCSecurityGroupRuleConfig_egress(rInt), - Check: resource.ComposeTestCheckFunc( - testAccCheckSecurityGroupRuleExists("aws_security_group.web", &group), - testAccCheckSecurityGroupRuleAttributes("aws_security_group_rule.egress_1", &group, nil, "egress"), + Config: testAccVPCSecurityGroupRuleConfig_egress(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckSecurityGroupExists(sgResourceName, &group), + resource.TestCheckResourceAttr(resourceName, "cidr_blocks.#", "1"), + resource.TestCheckResourceAttr(resourceName, "cidr_blocks.0", "10.0.0.0/8"), + resource.TestCheckNoResourceAttr(resourceName, "description"), + resource.TestCheckResourceAttr(resourceName, "from_port", "80"), + resource.TestCheckResourceAttr(resourceName, "ipv6_cidr_blocks.#", "0"), + resource.TestCheckResourceAttr(resourceName, "protocol", "tcp"), + resource.TestCheckResourceAttr(resourceName, "prefix_list_ids.#", "0"), + resource.TestCheckResourceAttrPair(resourceName, "security_group_id", sgResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "self", "false"), + resource.TestCheckNoResourceAttr(resourceName, "source_security_group_id"), + resource.TestCheckResourceAttr(resourceName, "to_port", "8000"), + resource.TestCheckResourceAttr(resourceName, "type", "egress"), ), }, { - ResourceName: "aws_security_group_rule.egress_1", + ResourceName: resourceName, ImportState: true, - ImportStateIdFunc: testAccSecurityGroupRuleImportStateIdFunc("aws_security_group_rule.egress_1"), + ImportStateIdFunc: testAccSecurityGroupRuleImportStateIdFunc(resourceName), ImportStateVerify: true, }, }, @@ -438,23 +385,37 @@ func TestAccVPCSecurityGroupRule_egress(t *testing.T) { func TestAccVPCSecurityGroupRule_selfReference(t *testing.T) { var group ec2.SecurityGroup + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_security_group_rule.test" + sgResourceName := "aws_security_group.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), ProviderFactories: acctest.ProviderFactories, - CheckDestroy: testAccCheckSecurityGroupRuleDestroy, + CheckDestroy: testAccCheckSecurityGroupDestroy, Steps: []resource.TestStep{ { - Config: testAccVPCSecurityGroupRuleConfig_selfReference, - Check: resource.ComposeTestCheckFunc( - testAccCheckSecurityGroupRuleExists("aws_security_group.web", &group), + Config: testAccVPCSecurityGroupRuleConfig_selfReference(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckSecurityGroupExists(sgResourceName, &group), + resource.TestCheckResourceAttr(resourceName, "cidr_blocks.#", "0"), + resource.TestCheckNoResourceAttr(resourceName, "description"), + resource.TestCheckResourceAttr(resourceName, "from_port", "0"), + resource.TestCheckResourceAttr(resourceName, "ipv6_cidr_blocks.#", "0"), + resource.TestCheckResourceAttr(resourceName, "protocol", "-1"), + resource.TestCheckResourceAttr(resourceName, "prefix_list_ids.#", "0"), + resource.TestCheckResourceAttrPair(resourceName, "security_group_id", sgResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "self", "true"), + resource.TestCheckNoResourceAttr(resourceName, "source_security_group_id"), + resource.TestCheckResourceAttr(resourceName, "to_port", "0"), + resource.TestCheckResourceAttr(resourceName, "type", "ingress"), ), }, { - ResourceName: "aws_security_group_rule.self", + ResourceName: resourceName, ImportState: true, - ImportStateIdFunc: testAccSecurityGroupRuleImportStateIdFunc("aws_security_group_rule.self"), + ImportStateIdFunc: testAccSecurityGroupRuleImportStateIdFunc(resourceName), ImportStateVerify: true, }, }, @@ -462,35 +423,37 @@ func TestAccVPCSecurityGroupRule_selfReference(t *testing.T) { } func TestAccVPCSecurityGroupRule_expectInvalidTypeError(t *testing.T) { - rInt := sdkacctest.RandInt() + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), ProviderFactories: acctest.ProviderFactories, - CheckDestroy: testAccCheckSecurityGroupRuleDestroy, + CheckDestroy: testAccCheckSecurityGroupDestroy, Steps: []resource.TestStep{ { - Config: testAccVPCSecurityGroupRuleConfig_expectInvalidType(rInt), - ExpectError: regexp.MustCompile(`expected type to be one of \[ingress egress\]`), + Config: testAccVPCSecurityGroupRuleConfig_expectInvalidType(rName), + ExpectError: regexp.MustCompile(`expected type to be one of \[egress ingress\]`), }, }, }) } func TestAccVPCSecurityGroupRule_expectInvalidCIDR(t *testing.T) { - rInt := sdkacctest.RandInt() + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), ProviderFactories: acctest.ProviderFactories, - CheckDestroy: testAccCheckSecurityGroupRuleDestroy, + CheckDestroy: testAccCheckSecurityGroupDestroy, Steps: []resource.TestStep{ { - Config: testAccVPCSecurityGroupRuleConfig_invalidIPv4CIDR(rInt), + Config: testAccVPCSecurityGroupRuleConfig_invalidIPv4CIDR(rName), ExpectError: regexp.MustCompile("invalid CIDR address: 1.2.3.4/33"), }, { - Config: testAccVPCSecurityGroupRuleConfig_invalidIPv6CIDR(rInt), + Config: testAccVPCSecurityGroupRuleConfig_invalidIPv6CIDR(rName), ExpectError: regexp.MustCompile("invalid CIDR address: ::/244"), }, }, @@ -499,60 +462,48 @@ func TestAccVPCSecurityGroupRule_expectInvalidCIDR(t *testing.T) { // testing partial match implementation func TestAccVPCSecurityGroupRule_PartialMatching_basic(t *testing.T) { - var group ec2.SecurityGroup - rInt := sdkacctest.RandInt() - - p := ec2.IpPermission{ - FromPort: aws.Int64(80), - ToPort: aws.Int64(80), - IpProtocol: aws.String("tcp"), - IpRanges: []*ec2.IpRange{ - {CidrIp: aws.String("10.0.2.0/24")}, - {CidrIp: aws.String("10.0.3.0/24")}, - {CidrIp: aws.String("10.0.4.0/24")}, - }, - } - - o := ec2.IpPermission{ - FromPort: aws.Int64(80), - ToPort: aws.Int64(80), - IpProtocol: aws.String("tcp"), - IpRanges: []*ec2.IpRange{ - {CidrIp: aws.String("10.0.5.0/24")}, - }, - } + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resource1Name := "aws_security_group_rule.test1" + resource2Name := "aws_security_group_rule.test2" + resource3Name := "aws_security_group_rule.test3" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), ProviderFactories: acctest.ProviderFactories, - CheckDestroy: testAccCheckSecurityGroupRuleDestroy, + CheckDestroy: testAccCheckSecurityGroupDestroy, Steps: []resource.TestStep{ { - Config: testAccVPCSecurityGroupRuleConfig_partialMatching(rInt), - Check: resource.ComposeTestCheckFunc( - testAccCheckSecurityGroupRuleExists("aws_security_group.web", &group), - testAccCheckSecurityGroupRuleAttributes("aws_security_group_rule.ingress", &group, &p, "ingress"), - testAccCheckSecurityGroupRuleAttributes("aws_security_group_rule.other", &group, &o, "ingress"), - testAccCheckSecurityGroupRuleAttributes("aws_security_group_rule.nat_ingress", &group, &o, "ingress"), + Config: testAccVPCSecurityGroupRuleConfig_partialMatching(rName), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resource1Name, "cidr_blocks.#", "3"), + resource.TestCheckResourceAttr(resource1Name, "cidr_blocks.0", "10.0.2.0/24"), + resource.TestCheckResourceAttr(resource1Name, "cidr_blocks.1", "10.0.3.0/24"), + resource.TestCheckResourceAttr(resource1Name, "cidr_blocks.2", "10.0.4.0/24"), + resource.TestCheckResourceAttr(resource2Name, "cidr_blocks.#", "1"), + resource.TestCheckResourceAttr(resource2Name, "cidr_blocks.0", "10.0.5.0/24"), + resource.TestCheckResourceAttr(resource3Name, "cidr_blocks.#", "3"), + resource.TestCheckResourceAttr(resource3Name, "cidr_blocks.0", "10.0.2.0/24"), + resource.TestCheckResourceAttr(resource3Name, "cidr_blocks.1", "10.0.3.0/24"), + resource.TestCheckResourceAttr(resource3Name, "cidr_blocks.2", "10.0.4.0/24"), ), }, { - ResourceName: "aws_security_group_rule.ingress", + ResourceName: resource1Name, ImportState: true, - ImportStateIdFunc: testAccSecurityGroupRuleImportStateIdFunc("aws_security_group_rule.ingress"), + ImportStateIdFunc: testAccSecurityGroupRuleImportStateIdFunc(resource1Name), ImportStateVerify: true, }, { - ResourceName: "aws_security_group_rule.other", + ResourceName: resource2Name, ImportState: true, - ImportStateIdFunc: testAccSecurityGroupRuleImportStateIdFunc("aws_security_group_rule.other"), + ImportStateIdFunc: testAccSecurityGroupRuleImportStateIdFunc(resource2Name), ImportStateVerify: true, }, { - ResourceName: "aws_security_group_rule.nat_ingress", + ResourceName: resource3Name, ImportState: true, - ImportStateIdFunc: testAccSecurityGroupRuleImportStateIdFunc("aws_security_group_rule.nat_ingress"), + ImportStateIdFunc: testAccSecurityGroupRuleImportStateIdFunc(resource3Name), ImportStateVerify: true, }, }, @@ -560,50 +511,38 @@ func TestAccVPCSecurityGroupRule_PartialMatching_basic(t *testing.T) { } func TestAccVPCSecurityGroupRule_PartialMatching_source(t *testing.T) { - var group ec2.SecurityGroup - var nat ec2.SecurityGroup - var p ec2.IpPermission - rInt := sdkacctest.RandInt() - - // This function creates the expected IPPermission with the group id from an - // external security group, needed because Security Group IDs are generated on - // AWS side and can't be known ahead of time. - setupSG := func(*terraform.State) error { - if nat.GroupId == nil { - return fmt.Errorf("Error: nat group has nil GroupID") - } - - p = ec2.IpPermission{ - FromPort: aws.Int64(80), - ToPort: aws.Int64(80), - IpProtocol: aws.String("tcp"), - UserIdGroupPairs: []*ec2.UserIdGroupPair{ - {GroupId: nat.GroupId}, - }, - } - - return nil - } + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resource1Name := "aws_security_group_rule.test1" + resource2Name := "aws_security_group_rule.test2" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), ProviderFactories: acctest.ProviderFactories, - CheckDestroy: testAccCheckSecurityGroupRuleDestroy, + CheckDestroy: testAccCheckSecurityGroupDestroy, Steps: []resource.TestStep{ { - Config: testAccVPCSecurityGroupRuleConfig_partialMatchingSource(rInt), - Check: resource.ComposeTestCheckFunc( - testAccCheckSecurityGroupRuleExists("aws_security_group.web", &group), - testAccCheckSecurityGroupRuleExists("aws_security_group.nat", &nat), - setupSG, - testAccCheckSecurityGroupRuleAttributes("aws_security_group_rule.source_ingress", &group, &p, "ingress"), + Config: testAccVPCSecurityGroupRuleConfig_partialMatchingSource(rName), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resource1Name, "cidr_blocks.#", "0"), + resource.TestCheckResourceAttrSet(resource1Name, "source_security_group_id"), + resource.TestCheckResourceAttr(resource2Name, "cidr_blocks.#", "3"), + resource.TestCheckResourceAttr(resource2Name, "cidr_blocks.0", "10.0.2.0/24"), + resource.TestCheckResourceAttr(resource2Name, "cidr_blocks.1", "10.0.3.0/24"), + resource.TestCheckResourceAttr(resource2Name, "cidr_blocks.2", "10.0.4.0/24"), + resource.TestCheckNoResourceAttr(resource2Name, "source_security_group_id"), ), }, { - ResourceName: "aws_security_group_rule.source_ingress", + ResourceName: resource1Name, + ImportState: true, + ImportStateIdFunc: testAccSecurityGroupRuleImportStateIdFunc(resource1Name), + ImportStateVerify: true, + }, + { + ResourceName: resource2Name, ImportState: true, - ImportStateIdFunc: testAccSecurityGroupRuleImportStateIdFunc("aws_security_group_rule.source_ingress"), + ImportStateIdFunc: testAccSecurityGroupRuleImportStateIdFunc(resource2Name), ImportStateVerify: true, }, }, @@ -612,23 +551,37 @@ func TestAccVPCSecurityGroupRule_PartialMatching_source(t *testing.T) { func TestAccVPCSecurityGroupRule_issue5310(t *testing.T) { var group ec2.SecurityGroup + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_security_group_rule.test" + sgResourceName := "aws_security_group.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), ProviderFactories: acctest.ProviderFactories, - CheckDestroy: testAccCheckSecurityGroupRuleDestroy, + CheckDestroy: testAccCheckSecurityGroupDestroy, Steps: []resource.TestStep{ { - Config: testAccVPCSecurityGroupRuleConfig_issue5310, - Check: resource.ComposeTestCheckFunc( - testAccCheckSecurityGroupRuleExists("aws_security_group.issue_5310", &group), + Config: testAccVPCSecurityGroupRuleConfig_issue5310(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckSecurityGroupExists(sgResourceName, &group), + resource.TestCheckResourceAttr(resourceName, "cidr_blocks.#", "0"), + resource.TestCheckNoResourceAttr(resourceName, "description"), + resource.TestCheckResourceAttr(resourceName, "from_port", "0"), + resource.TestCheckResourceAttr(resourceName, "ipv6_cidr_blocks.#", "0"), + resource.TestCheckResourceAttr(resourceName, "protocol", "tcp"), + resource.TestCheckResourceAttr(resourceName, "prefix_list_ids.#", "0"), + resource.TestCheckResourceAttrPair(resourceName, "security_group_id", sgResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "self", "true"), + resource.TestCheckNoResourceAttr(resourceName, "source_security_group_id"), + resource.TestCheckResourceAttr(resourceName, "to_port", "65535"), + resource.TestCheckResourceAttr(resourceName, "type", "ingress"), ), }, { - ResourceName: "aws_security_group_rule.issue_5310", + ResourceName: resourceName, ImportState: true, - ImportStateIdFunc: testAccSecurityGroupRuleImportStateIdFunc("aws_security_group_rule.issue_5310"), + ImportStateIdFunc: testAccSecurityGroupRuleImportStateIdFunc(resourceName), ImportStateVerify: true, }, }, @@ -637,17 +590,21 @@ func TestAccVPCSecurityGroupRule_issue5310(t *testing.T) { func TestAccVPCSecurityGroupRule_race(t *testing.T) { var group ec2.SecurityGroup + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + sgResourceName := "aws_security_group.test" + n := 50 resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), ProviderFactories: acctest.ProviderFactories, - CheckDestroy: testAccCheckSecurityGroupRuleDestroy, + CheckDestroy: testAccCheckSecurityGroupDestroy, Steps: []resource.TestStep{ { - Config: testAccVPCSecurityGroupRuleConfig_race, + Config: testAccVPCSecurityGroupRuleConfig_race(rName, n), Check: resource.ComposeTestCheckFunc( - testAccCheckSecurityGroupRuleExists("aws_security_group.race", &group), + testAccCheckSecurityGroupExists(sgResourceName, &group), + testAccCheckSecurityGroupRuleCount(&group, n, n), ), }, }, @@ -656,24 +613,37 @@ func TestAccVPCSecurityGroupRule_race(t *testing.T) { func TestAccVPCSecurityGroupRule_selfSource(t *testing.T) { var group ec2.SecurityGroup - rInt := sdkacctest.RandInt() + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_security_group_rule.test" + sgResourceName := "aws_security_group.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), ProviderFactories: acctest.ProviderFactories, - CheckDestroy: testAccCheckSecurityGroupRuleDestroy, + CheckDestroy: testAccCheckSecurityGroupDestroy, Steps: []resource.TestStep{ { - Config: testAccVPCSecurityGroupRuleConfig_selfInSource(rInt), - Check: resource.ComposeTestCheckFunc( - testAccCheckSecurityGroupRuleExists("aws_security_group.web", &group), + Config: testAccVPCSecurityGroupRuleConfig_selfInSource(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckSecurityGroupExists(sgResourceName, &group), + resource.TestCheckResourceAttr(resourceName, "cidr_blocks.#", "0"), + resource.TestCheckNoResourceAttr(resourceName, "description"), + resource.TestCheckResourceAttr(resourceName, "from_port", "0"), + resource.TestCheckResourceAttr(resourceName, "ipv6_cidr_blocks.#", "0"), + resource.TestCheckResourceAttr(resourceName, "protocol", "-1"), + resource.TestCheckResourceAttr(resourceName, "prefix_list_ids.#", "0"), + resource.TestCheckResourceAttrPair(resourceName, "security_group_id", sgResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "self", "false"), + resource.TestCheckResourceAttrPair(resourceName, "source_security_group_id", sgResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "to_port", "0"), + resource.TestCheckResourceAttr(resourceName, "type", "ingress"), ), }, { - ResourceName: "aws_security_group_rule.allow_self", + ResourceName: resourceName, ImportState: true, - ImportStateIdFunc: testAccSecurityGroupRuleImportStateIdFunc("aws_security_group_rule.allow_self"), + ImportStateIdFunc: testAccSecurityGroupRuleImportStateIdFunc(resourceName), ImportStateVerify: true, }, }, @@ -682,61 +652,39 @@ func TestAccVPCSecurityGroupRule_selfSource(t *testing.T) { func TestAccVPCSecurityGroupRule_prefixListEgress(t *testing.T) { var group ec2.SecurityGroup - var endpoint ec2.VpcEndpoint - var p ec2.IpPermission - - // This function creates the expected IPPermission with the prefix list ID from - // the VPC Endpoint created in the test - setupSG := func(*terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).EC2Conn - prefixListInput := &ec2.DescribePrefixListsInput{ - Filters: []*ec2.Filter{ - {Name: aws.String("prefix-list-name"), Values: []*string{endpoint.ServiceName}}, - }, - } - - log.Printf("[DEBUG] Reading VPC Endpoint prefix list: %s", prefixListInput) - prefixListsOutput, err := conn.DescribePrefixLists(prefixListInput) - - if err != nil { - return fmt.Errorf("error reading VPC Endpoint prefix list: %w", err) - } - - if len(prefixListsOutput.PrefixLists) != 1 { - return fmt.Errorf("unexpected multiple prefix lists associated with the service: %s", prefixListsOutput) - } - - p = ec2.IpPermission{ - IpProtocol: aws.String("-1"), - PrefixListIds: []*ec2.PrefixListId{ - {PrefixListId: prefixListsOutput.PrefixLists[0].PrefixListId}, - }, - } - - return nil - } + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_security_group_rule.test" + sgResourceName := "aws_security_group.test" + vpceResourceName := "aws_vpc_endpoint.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), ProviderFactories: acctest.ProviderFactories, - CheckDestroy: testAccCheckSecurityGroupRuleDestroy, + CheckDestroy: testAccCheckSecurityGroupDestroy, Steps: []resource.TestStep{ { - Config: testAccVPCSecurityGroupRuleConfig_prefixListEgress, - Check: resource.ComposeTestCheckFunc( - testAccCheckSecurityGroupRuleExists("aws_security_group.egress", &group), - // lookup info on the VPC Endpoint created, to populate the expected - // IP Perm - testAccCheckVPCEndpointExists("aws_vpc_endpoint.s3_endpoint", &endpoint), - setupSG, - testAccCheckSecurityGroupRuleAttributes("aws_security_group_rule.egress_1", &group, &p, "egress"), + Config: testAccVPCSecurityGroupRuleConfig_prefixListEgress(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckSecurityGroupExists(sgResourceName, &group), + resource.TestCheckResourceAttr(resourceName, "cidr_blocks.#", "0"), + resource.TestCheckNoResourceAttr(resourceName, "description"), + resource.TestCheckResourceAttr(resourceName, "from_port", "0"), + resource.TestCheckResourceAttr(resourceName, "ipv6_cidr_blocks.#", "0"), + resource.TestCheckResourceAttr(resourceName, "protocol", "-1"), + resource.TestCheckResourceAttr(resourceName, "prefix_list_ids.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "prefix_list_ids.0", vpceResourceName, "prefix_list_id"), + resource.TestCheckResourceAttrPair(resourceName, "security_group_id", sgResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "self", "false"), + resource.TestCheckNoResourceAttr(resourceName, "source_security_group_id"), + resource.TestCheckResourceAttr(resourceName, "to_port", "0"), + resource.TestCheckResourceAttr(resourceName, "type", "egress"), ), }, { - ResourceName: "aws_security_group_rule.egress_1", + ResourceName: resourceName, ImportState: true, - ImportStateIdFunc: testAccSecurityGroupRuleImportStateIdFunc("aws_security_group_rule.egress_1"), + ImportStateIdFunc: testAccSecurityGroupRuleImportStateIdFunc(resourceName), ImportStateVerify: true, }, }, @@ -745,26 +693,38 @@ func TestAccVPCSecurityGroupRule_prefixListEgress(t *testing.T) { func TestAccVPCSecurityGroupRule_ingressDescription(t *testing.T) { var group ec2.SecurityGroup - rInt := sdkacctest.RandInt() + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_security_group_rule.test" + sgResourceName := "aws_security_group.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), ProviderFactories: acctest.ProviderFactories, - CheckDestroy: testAccCheckSecurityGroupRuleDestroy, + CheckDestroy: testAccCheckSecurityGroupDestroy, Steps: []resource.TestStep{ { - Config: testAccVPCSecurityGroupRuleConfig_ingressDescription(rInt), - Check: resource.ComposeTestCheckFunc( - testAccCheckSecurityGroupRuleExists("aws_security_group.web", &group), - testAccCheckSecurityGroupRuleAttributes("aws_security_group_rule.ingress_1", &group, nil, "ingress"), - resource.TestCheckResourceAttr("aws_security_group_rule.ingress_1", "description", "TF acceptance test ingress rule"), + Config: testAccVPCSecurityGroupRuleConfig_ingressDescription(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckSecurityGroupExists(sgResourceName, &group), + resource.TestCheckResourceAttr(resourceName, "cidr_blocks.#", "1"), + resource.TestCheckResourceAttr(resourceName, "cidr_blocks.0", "10.0.0.0/8"), + resource.TestCheckResourceAttr(resourceName, "description", "TF acceptance test ingress rule"), + resource.TestCheckResourceAttr(resourceName, "from_port", "80"), + resource.TestCheckResourceAttr(resourceName, "ipv6_cidr_blocks.#", "0"), + resource.TestCheckResourceAttr(resourceName, "protocol", "tcp"), + resource.TestCheckResourceAttr(resourceName, "prefix_list_ids.#", "0"), + resource.TestCheckResourceAttrPair(resourceName, "security_group_id", sgResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "self", "false"), + resource.TestCheckNoResourceAttr(resourceName, "source_security_group_id"), + resource.TestCheckResourceAttr(resourceName, "to_port", "8000"), + resource.TestCheckResourceAttr(resourceName, "type", "ingress"), ), }, { - ResourceName: "aws_security_group_rule.ingress_1", + ResourceName: resourceName, ImportState: true, - ImportStateIdFunc: testAccSecurityGroupRuleImportStateIdFunc("aws_security_group_rule.ingress_1"), + ImportStateIdFunc: testAccSecurityGroupRuleImportStateIdFunc(resourceName), ImportStateVerify: true, }, }, @@ -773,26 +733,38 @@ func TestAccVPCSecurityGroupRule_ingressDescription(t *testing.T) { func TestAccVPCSecurityGroupRule_egressDescription(t *testing.T) { var group ec2.SecurityGroup - rInt := sdkacctest.RandInt() + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_security_group_rule.test" + sgResourceName := "aws_security_group.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), ProviderFactories: acctest.ProviderFactories, - CheckDestroy: testAccCheckSecurityGroupRuleDestroy, + CheckDestroy: testAccCheckSecurityGroupDestroy, Steps: []resource.TestStep{ { - Config: testAccVPCSecurityGroupRuleConfig_egressDescription(rInt), - Check: resource.ComposeTestCheckFunc( - testAccCheckSecurityGroupRuleExists("aws_security_group.web", &group), - testAccCheckSecurityGroupRuleAttributes("aws_security_group_rule.egress_1", &group, nil, "egress"), - resource.TestCheckResourceAttr("aws_security_group_rule.egress_1", "description", "TF acceptance test egress rule"), + Config: testAccVPCSecurityGroupRuleConfig_egressDescription(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckSecurityGroupExists(sgResourceName, &group), + resource.TestCheckResourceAttr(resourceName, "cidr_blocks.#", "1"), + resource.TestCheckResourceAttr(resourceName, "cidr_blocks.0", "10.0.0.0/8"), + resource.TestCheckResourceAttr(resourceName, "description", "TF acceptance test egress rule"), + resource.TestCheckResourceAttr(resourceName, "from_port", "80"), + resource.TestCheckResourceAttr(resourceName, "ipv6_cidr_blocks.#", "0"), + resource.TestCheckResourceAttr(resourceName, "protocol", "tcp"), + resource.TestCheckResourceAttr(resourceName, "prefix_list_ids.#", "0"), + resource.TestCheckResourceAttrPair(resourceName, "security_group_id", sgResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "self", "false"), + resource.TestCheckNoResourceAttr(resourceName, "source_security_group_id"), + resource.TestCheckResourceAttr(resourceName, "to_port", "8000"), + resource.TestCheckResourceAttr(resourceName, "type", "egress"), ), }, { - ResourceName: "aws_security_group_rule.egress_1", + ResourceName: resourceName, ImportState: true, - ImportStateIdFunc: testAccSecurityGroupRuleImportStateIdFunc("aws_security_group_rule.egress_1"), + ImportStateIdFunc: testAccSecurityGroupRuleImportStateIdFunc(resourceName), ImportStateVerify: true, }, }, @@ -801,35 +773,56 @@ func TestAccVPCSecurityGroupRule_egressDescription(t *testing.T) { func TestAccVPCSecurityGroupRule_IngressDescription_updates(t *testing.T) { var group ec2.SecurityGroup - rInt := sdkacctest.RandInt() + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_security_group_rule.test" + sgResourceName := "aws_security_group.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), ProviderFactories: acctest.ProviderFactories, - CheckDestroy: testAccCheckSecurityGroupRuleDestroy, + CheckDestroy: testAccCheckSecurityGroupDestroy, Steps: []resource.TestStep{ { - Config: testAccVPCSecurityGroupRuleConfig_ingressDescription(rInt), - Check: resource.ComposeTestCheckFunc( - testAccCheckSecurityGroupRuleExists("aws_security_group.web", &group), - testAccCheckSecurityGroupRuleAttributes("aws_security_group_rule.ingress_1", &group, nil, "ingress"), - resource.TestCheckResourceAttr("aws_security_group_rule.ingress_1", "description", "TF acceptance test ingress rule"), + Config: testAccVPCSecurityGroupRuleConfig_ingressDescription(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckSecurityGroupExists(sgResourceName, &group), + resource.TestCheckResourceAttr(resourceName, "cidr_blocks.#", "1"), + resource.TestCheckResourceAttr(resourceName, "cidr_blocks.0", "10.0.0.0/8"), + resource.TestCheckResourceAttr(resourceName, "description", "TF acceptance test ingress rule"), + resource.TestCheckResourceAttr(resourceName, "from_port", "80"), + resource.TestCheckResourceAttr(resourceName, "ipv6_cidr_blocks.#", "0"), + resource.TestCheckResourceAttr(resourceName, "protocol", "tcp"), + resource.TestCheckResourceAttr(resourceName, "prefix_list_ids.#", "0"), + resource.TestCheckResourceAttrPair(resourceName, "security_group_id", sgResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "self", "false"), + resource.TestCheckNoResourceAttr(resourceName, "source_security_group_id"), + resource.TestCheckResourceAttr(resourceName, "to_port", "8000"), + resource.TestCheckResourceAttr(resourceName, "type", "ingress"), ), }, - { - Config: testAccVPCSecurityGroupRuleConfig_ingressUpdateDescription(rInt), - Check: resource.ComposeTestCheckFunc( - testAccCheckSecurityGroupRuleExists("aws_security_group.web", &group), - testAccCheckSecurityGroupRuleAttributes("aws_security_group_rule.ingress_1", &group, nil, "ingress"), - resource.TestCheckResourceAttr("aws_security_group_rule.ingress_1", "description", "TF acceptance test ingress rule updated"), + Config: testAccVPCSecurityGroupRuleConfig_ingressUpdateDescription(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckSecurityGroupExists(sgResourceName, &group), + resource.TestCheckResourceAttr(resourceName, "cidr_blocks.#", "1"), + resource.TestCheckResourceAttr(resourceName, "cidr_blocks.0", "10.0.0.0/8"), + resource.TestCheckResourceAttr(resourceName, "description", "TF acceptance test ingress rule updated"), + resource.TestCheckResourceAttr(resourceName, "from_port", "80"), + resource.TestCheckResourceAttr(resourceName, "ipv6_cidr_blocks.#", "0"), + resource.TestCheckResourceAttr(resourceName, "protocol", "tcp"), + resource.TestCheckResourceAttr(resourceName, "prefix_list_ids.#", "0"), + resource.TestCheckResourceAttrPair(resourceName, "security_group_id", sgResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "self", "false"), + resource.TestCheckNoResourceAttr(resourceName, "source_security_group_id"), + resource.TestCheckResourceAttr(resourceName, "to_port", "8000"), + resource.TestCheckResourceAttr(resourceName, "type", "ingress"), ), }, { - ResourceName: "aws_security_group_rule.ingress_1", + ResourceName: resourceName, ImportState: true, - ImportStateIdFunc: testAccSecurityGroupRuleImportStateIdFunc("aws_security_group_rule.ingress_1"), + ImportStateIdFunc: testAccSecurityGroupRuleImportStateIdFunc(resourceName), ImportStateVerify: true, }, }, @@ -838,35 +831,56 @@ func TestAccVPCSecurityGroupRule_IngressDescription_updates(t *testing.T) { func TestAccVPCSecurityGroupRule_EgressDescription_updates(t *testing.T) { var group ec2.SecurityGroup - rInt := sdkacctest.RandInt() + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_security_group_rule.test" + sgResourceName := "aws_security_group.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), ProviderFactories: acctest.ProviderFactories, - CheckDestroy: testAccCheckSecurityGroupRuleDestroy, + CheckDestroy: testAccCheckSecurityGroupDestroy, Steps: []resource.TestStep{ { - Config: testAccVPCSecurityGroupRuleConfig_egressDescription(rInt), - Check: resource.ComposeTestCheckFunc( - testAccCheckSecurityGroupRuleExists("aws_security_group.web", &group), - testAccCheckSecurityGroupRuleAttributes("aws_security_group_rule.egress_1", &group, nil, "egress"), - resource.TestCheckResourceAttr("aws_security_group_rule.egress_1", "description", "TF acceptance test egress rule"), + Config: testAccVPCSecurityGroupRuleConfig_egressDescription(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckSecurityGroupExists(sgResourceName, &group), + resource.TestCheckResourceAttr(resourceName, "cidr_blocks.#", "1"), + resource.TestCheckResourceAttr(resourceName, "cidr_blocks.0", "10.0.0.0/8"), + resource.TestCheckResourceAttr(resourceName, "description", "TF acceptance test egress rule"), + resource.TestCheckResourceAttr(resourceName, "from_port", "80"), + resource.TestCheckResourceAttr(resourceName, "ipv6_cidr_blocks.#", "0"), + resource.TestCheckResourceAttr(resourceName, "protocol", "tcp"), + resource.TestCheckResourceAttr(resourceName, "prefix_list_ids.#", "0"), + resource.TestCheckResourceAttrPair(resourceName, "security_group_id", sgResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "self", "false"), + resource.TestCheckNoResourceAttr(resourceName, "source_security_group_id"), + resource.TestCheckResourceAttr(resourceName, "to_port", "8000"), + resource.TestCheckResourceAttr(resourceName, "type", "egress"), ), }, - { - Config: testAccVPCSecurityGroupRuleConfig_egressUpdateDescription(rInt), - Check: resource.ComposeTestCheckFunc( - testAccCheckSecurityGroupRuleExists("aws_security_group.web", &group), - testAccCheckSecurityGroupRuleAttributes("aws_security_group_rule.egress_1", &group, nil, "egress"), - resource.TestCheckResourceAttr("aws_security_group_rule.egress_1", "description", "TF acceptance test egress rule updated"), + Config: testAccVPCSecurityGroupRuleConfig_egressUpdateDescription(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckSecurityGroupExists(sgResourceName, &group), + resource.TestCheckResourceAttr(resourceName, "cidr_blocks.#", "1"), + resource.TestCheckResourceAttr(resourceName, "cidr_blocks.0", "10.0.0.0/8"), + resource.TestCheckResourceAttr(resourceName, "description", "TF acceptance test egress rule updated"), + resource.TestCheckResourceAttr(resourceName, "from_port", "80"), + resource.TestCheckResourceAttr(resourceName, "ipv6_cidr_blocks.#", "0"), + resource.TestCheckResourceAttr(resourceName, "protocol", "tcp"), + resource.TestCheckResourceAttr(resourceName, "prefix_list_ids.#", "0"), + resource.TestCheckResourceAttrPair(resourceName, "security_group_id", sgResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "self", "false"), + resource.TestCheckNoResourceAttr(resourceName, "source_security_group_id"), + resource.TestCheckResourceAttr(resourceName, "to_port", "8000"), + resource.TestCheckResourceAttr(resourceName, "type", "egress"), ), }, { - ResourceName: "aws_security_group_rule.egress_1", + ResourceName: resourceName, ImportState: true, - ImportStateIdFunc: testAccSecurityGroupRuleImportStateIdFunc("aws_security_group_rule.egress_1"), + ImportStateIdFunc: testAccSecurityGroupRuleImportStateIdFunc(resourceName), ImportStateVerify: true, }, }, @@ -876,38 +890,31 @@ func TestAccVPCSecurityGroupRule_EgressDescription_updates(t *testing.T) { func TestAccVPCSecurityGroupRule_Description_allPorts(t *testing.T) { var group ec2.SecurityGroup rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - securityGroupResourceName := "aws_security_group.test" resourceName := "aws_security_group_rule.test" - - rule1 := ec2.IpPermission{ - IpProtocol: aws.String("-1"), - IpRanges: []*ec2.IpRange{ - {CidrIp: aws.String("0.0.0.0/0"), Description: aws.String("description1")}, - }, - } - - rule2 := ec2.IpPermission{ - IpProtocol: aws.String("-1"), - IpRanges: []*ec2.IpRange{ - {CidrIp: aws.String("0.0.0.0/0"), Description: aws.String("description2")}, - }, - } + sgResourceName := "aws_security_group.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), ProviderFactories: acctest.ProviderFactories, - CheckDestroy: testAccCheckSecurityGroupRuleDestroy, + CheckDestroy: testAccCheckSecurityGroupDestroy, Steps: []resource.TestStep{ { Config: testAccVPCSecurityGroupRuleConfig_descriptionAllPorts(rName, "description1"), - Check: resource.ComposeTestCheckFunc( - testAccCheckSecurityGroupRuleExists(securityGroupResourceName, &group), - testAccCheckSecurityGroupRuleAttributes(resourceName, &group, &rule1, "ingress"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckSecurityGroupExists(sgResourceName, &group), + resource.TestCheckResourceAttr(resourceName, "cidr_blocks.#", "1"), + resource.TestCheckResourceAttr(resourceName, "cidr_blocks.0", "0.0.0.0/0"), resource.TestCheckResourceAttr(resourceName, "description", "description1"), resource.TestCheckResourceAttr(resourceName, "from_port", "0"), + resource.TestCheckResourceAttr(resourceName, "ipv6_cidr_blocks.#", "0"), resource.TestCheckResourceAttr(resourceName, "protocol", "-1"), + resource.TestCheckResourceAttr(resourceName, "prefix_list_ids.#", "0"), + resource.TestCheckResourceAttrPair(resourceName, "security_group_id", sgResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "self", "false"), + resource.TestCheckNoResourceAttr(resourceName, "source_security_group_id"), resource.TestCheckResourceAttr(resourceName, "to_port", "0"), + resource.TestCheckResourceAttr(resourceName, "type", "ingress"), ), }, { @@ -918,13 +925,20 @@ func TestAccVPCSecurityGroupRule_Description_allPorts(t *testing.T) { }, { Config: testAccVPCSecurityGroupRuleConfig_descriptionAllPorts(rName, "description2"), - Check: resource.ComposeTestCheckFunc( - testAccCheckSecurityGroupRuleExists(securityGroupResourceName, &group), - testAccCheckSecurityGroupRuleAttributes(resourceName, &group, &rule2, "ingress"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckSecurityGroupExists(sgResourceName, &group), + resource.TestCheckResourceAttr(resourceName, "cidr_blocks.#", "1"), + resource.TestCheckResourceAttr(resourceName, "cidr_blocks.0", "0.0.0.0/0"), resource.TestCheckResourceAttr(resourceName, "description", "description2"), resource.TestCheckResourceAttr(resourceName, "from_port", "0"), + resource.TestCheckResourceAttr(resourceName, "ipv6_cidr_blocks.#", "0"), resource.TestCheckResourceAttr(resourceName, "protocol", "-1"), + resource.TestCheckResourceAttr(resourceName, "prefix_list_ids.#", "0"), + resource.TestCheckResourceAttrPair(resourceName, "security_group_id", sgResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "self", "false"), + resource.TestCheckNoResourceAttr(resourceName, "source_security_group_id"), resource.TestCheckResourceAttr(resourceName, "to_port", "0"), + resource.TestCheckResourceAttr(resourceName, "type", "ingress"), ), }, }, @@ -934,38 +948,31 @@ func TestAccVPCSecurityGroupRule_Description_allPorts(t *testing.T) { func TestAccVPCSecurityGroupRule_DescriptionAllPorts_nonZeroPorts(t *testing.T) { var group ec2.SecurityGroup rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - securityGroupResourceName := "aws_security_group.test" resourceName := "aws_security_group_rule.test" - - rule1 := ec2.IpPermission{ - IpProtocol: aws.String("-1"), - IpRanges: []*ec2.IpRange{ - {CidrIp: aws.String("0.0.0.0/0"), Description: aws.String("description1")}, - }, - } - - rule2 := ec2.IpPermission{ - IpProtocol: aws.String("-1"), - IpRanges: []*ec2.IpRange{ - {CidrIp: aws.String("0.0.0.0/0"), Description: aws.String("description2")}, - }, - } + sgResourceName := "aws_security_group.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), ProviderFactories: acctest.ProviderFactories, - CheckDestroy: testAccCheckSecurityGroupRuleDestroy, + CheckDestroy: testAccCheckSecurityGroupDestroy, Steps: []resource.TestStep{ { Config: testAccVPCSecurityGroupRuleConfig_descriptionAllPortsNonZeroPorts(rName, "description1"), - Check: resource.ComposeTestCheckFunc( - testAccCheckSecurityGroupRuleExists(securityGroupResourceName, &group), - testAccCheckSecurityGroupRuleAttributes(resourceName, &group, &rule1, "ingress"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckSecurityGroupExists(sgResourceName, &group), + resource.TestCheckResourceAttr(resourceName, "cidr_blocks.#", "1"), + resource.TestCheckResourceAttr(resourceName, "cidr_blocks.0", "0.0.0.0/0"), resource.TestCheckResourceAttr(resourceName, "description", "description1"), resource.TestCheckResourceAttr(resourceName, "from_port", "-1"), + resource.TestCheckResourceAttr(resourceName, "ipv6_cidr_blocks.#", "0"), resource.TestCheckResourceAttr(resourceName, "protocol", "-1"), + resource.TestCheckResourceAttr(resourceName, "prefix_list_ids.#", "0"), + resource.TestCheckResourceAttrPair(resourceName, "security_group_id", sgResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "self", "false"), + resource.TestCheckNoResourceAttr(resourceName, "source_security_group_id"), resource.TestCheckResourceAttr(resourceName, "to_port", "-1"), + resource.TestCheckResourceAttr(resourceName, "type", "ingress"), ), }, { @@ -976,13 +983,20 @@ func TestAccVPCSecurityGroupRule_DescriptionAllPorts_nonZeroPorts(t *testing.T) }, { Config: testAccVPCSecurityGroupRuleConfig_descriptionAllPortsNonZeroPorts(rName, "description2"), - Check: resource.ComposeTestCheckFunc( - testAccCheckSecurityGroupRuleExists(securityGroupResourceName, &group), - testAccCheckSecurityGroupRuleAttributes(resourceName, &group, &rule2, "ingress"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckSecurityGroupExists(sgResourceName, &group), + resource.TestCheckResourceAttr(resourceName, "cidr_blocks.#", "1"), + resource.TestCheckResourceAttr(resourceName, "cidr_blocks.0", "0.0.0.0/0"), resource.TestCheckResourceAttr(resourceName, "description", "description2"), resource.TestCheckResourceAttr(resourceName, "from_port", "0"), + resource.TestCheckResourceAttr(resourceName, "ipv6_cidr_blocks.#", "0"), resource.TestCheckResourceAttr(resourceName, "protocol", "-1"), + resource.TestCheckResourceAttr(resourceName, "prefix_list_ids.#", "0"), + resource.TestCheckResourceAttrPair(resourceName, "security_group_id", sgResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "self", "false"), + resource.TestCheckNoResourceAttr(resourceName, "source_security_group_id"), resource.TestCheckResourceAttr(resourceName, "to_port", "0"), + resource.TestCheckResourceAttr(resourceName, "type", "ingress"), ), }, }, @@ -993,388 +1007,459 @@ func TestAccVPCSecurityGroupRule_DescriptionAllPorts_nonZeroPorts(t *testing.T) func TestAccVPCSecurityGroupRule_MultipleRuleSearching_allProtocolCrash(t *testing.T) { var group ec2.SecurityGroup rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - securityGroupResourceName := "aws_security_group.test" - resourceName1 := "aws_security_group_rule.test1" - resourceName2 := "aws_security_group_rule.test2" - - rule1 := ec2.IpPermission{ - IpProtocol: aws.String("-1"), - IpRanges: []*ec2.IpRange{ - {CidrIp: aws.String("10.0.0.0/8")}, - }, - } - - rule2 := ec2.IpPermission{ - FromPort: aws.Int64(443), - ToPort: aws.Int64(443), - IpProtocol: aws.String("tcp"), - IpRanges: []*ec2.IpRange{ - {CidrIp: aws.String("172.168.0.0/16")}, - }, - } + resource1Name := "aws_security_group_rule.test1" + resource2Name := "aws_security_group_rule.test2" + sgResourceName := "aws_security_group.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), ProviderFactories: acctest.ProviderFactories, - CheckDestroy: testAccCheckSecurityGroupRuleDestroy, + CheckDestroy: testAccCheckSecurityGroupDestroy, Steps: []resource.TestStep{ { Config: testAccVPCSecurityGroupRuleConfig_multipleSearchingAllProtocolCrash(rName), - Check: resource.ComposeTestCheckFunc( - testAccCheckSecurityGroupRuleExists(securityGroupResourceName, &group), - testAccCheckSecurityGroupRuleAttributes(resourceName1, &group, &rule1, "ingress"), - testAccCheckSecurityGroupRuleAttributes(resourceName2, &group, &rule2, "ingress"), - resource.TestCheckResourceAttr(resourceName1, "from_port", "0"), - resource.TestCheckResourceAttr(resourceName1, "protocol", "-1"), - resource.TestCheckResourceAttr(resourceName1, "to_port", "65535"), - resource.TestCheckResourceAttr(resourceName2, "from_port", "443"), - resource.TestCheckResourceAttr(resourceName2, "protocol", "tcp"), - resource.TestCheckResourceAttr(resourceName2, "to_port", "443"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckSecurityGroupExists(sgResourceName, &group), + resource.TestCheckResourceAttr(resource1Name, "cidr_blocks.#", "1"), + resource.TestCheckResourceAttr(resource1Name, "cidr_blocks.0", "10.0.0.0/8"), + resource.TestCheckNoResourceAttr(resource1Name, "description"), + resource.TestCheckResourceAttr(resource1Name, "from_port", "0"), + resource.TestCheckResourceAttr(resource1Name, "ipv6_cidr_blocks.#", "0"), + resource.TestCheckResourceAttr(resource1Name, "protocol", "-1"), + resource.TestCheckResourceAttr(resource1Name, "prefix_list_ids.#", "0"), + resource.TestCheckResourceAttrPair(resource1Name, "security_group_id", sgResourceName, "id"), + resource.TestCheckResourceAttr(resource1Name, "self", "false"), + resource.TestCheckNoResourceAttr(resource1Name, "source_security_group_id"), + resource.TestCheckResourceAttr(resource1Name, "to_port", "65535"), + resource.TestCheckResourceAttr(resource1Name, "type", "ingress"), + resource.TestCheckResourceAttr(resource2Name, "cidr_blocks.#", "1"), + resource.TestCheckResourceAttr(resource2Name, "cidr_blocks.0", "172.168.0.0/16"), + resource.TestCheckNoResourceAttr(resource2Name, "description"), + resource.TestCheckResourceAttr(resource2Name, "from_port", "443"), + resource.TestCheckResourceAttr(resource2Name, "ipv6_cidr_blocks.#", "0"), + resource.TestCheckResourceAttr(resource2Name, "protocol", "tcp"), + resource.TestCheckResourceAttr(resource2Name, "prefix_list_ids.#", "0"), + resource.TestCheckResourceAttrPair(resource2Name, "security_group_id", sgResourceName, "id"), + resource.TestCheckResourceAttr(resource2Name, "self", "false"), + resource.TestCheckNoResourceAttr(resource2Name, "source_security_group_id"), + resource.TestCheckResourceAttr(resource2Name, "to_port", "443"), + resource.TestCheckResourceAttr(resource2Name, "type", "ingress"), ), }, + { + ResourceName: resource1Name, + ImportState: true, + ImportStateIdFunc: testAccSecurityGroupRuleImportStateIdFunc(resource1Name), + ImportStateVerify: true, + }, + { + ResourceName: resource2Name, + ImportState: true, + ImportStateIdFunc: testAccSecurityGroupRuleImportStateIdFunc(resource2Name), + ImportStateVerify: true, + }, }, }) } func TestAccVPCSecurityGroupRule_multiDescription(t *testing.T) { - var group ec2.SecurityGroup - var nat ec2.SecurityGroup - rInt := sdkacctest.RandInt() - - rule1 := ec2.IpPermission{ - FromPort: aws.Int64(22), - ToPort: aws.Int64(22), - IpProtocol: aws.String("tcp"), - IpRanges: []*ec2.IpRange{ - {CidrIp: aws.String("0.0.0.0/0"), Description: aws.String("CIDR Description")}, - }, - } - - rule2 := ec2.IpPermission{ - FromPort: aws.Int64(22), - ToPort: aws.Int64(22), - IpProtocol: aws.String("tcp"), - Ipv6Ranges: []*ec2.Ipv6Range{ - {CidrIpv6: aws.String("::/0"), Description: aws.String("IPv6 CIDR Description")}, - }, - } - - var rule3 ec2.IpPermission - - // This function creates the expected IPPermission with the group id from an - // external security group, needed because Security Group IDs are generated on - // AWS side and can't be known ahead of time. - setupSG := func(*terraform.State) error { - if nat.GroupId == nil { - return fmt.Errorf("Error: nat group has nil GroupID") - } - - rule3 = ec2.IpPermission{ - FromPort: aws.Int64(22), - ToPort: aws.Int64(22), - IpProtocol: aws.String("tcp"), - UserIdGroupPairs: []*ec2.UserIdGroupPair{ - {GroupId: nat.GroupId, Description: aws.String("NAT SG Description")}, - }, - } - - return nil - } - - var endpoint ec2.VpcEndpoint - var rule4 ec2.IpPermission - - // This function creates the expected IPPermission with the prefix list ID from - // the VPC Endpoint created in the test - setupPL := func(*terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).EC2Conn - prefixListInput := &ec2.DescribePrefixListsInput{ - Filters: []*ec2.Filter{ - {Name: aws.String("prefix-list-name"), Values: []*string{endpoint.ServiceName}}, - }, - } - - log.Printf("[DEBUG] Reading VPC Endpoint prefix list: %s", prefixListInput) - prefixListsOutput, err := conn.DescribePrefixLists(prefixListInput) - - if err != nil { - return fmt.Errorf("error reading VPC Endpoint prefix list: %w", err) - } - - if len(prefixListsOutput.PrefixLists) != 1 { - return fmt.Errorf("unexpected multiple prefix lists associated with the service: %s", prefixListsOutput) - } - - rule4 = ec2.IpPermission{ - FromPort: aws.Int64(22), - ToPort: aws.Int64(22), - IpProtocol: aws.String("tcp"), - PrefixListIds: []*ec2.PrefixListId{ - {PrefixListId: prefixListsOutput.PrefixLists[0].PrefixListId, Description: aws.String("Prefix List Description")}, - }, - } - - return nil - } + var group1, group2 ec2.SecurityGroup + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resource1Name := "aws_security_group_rule.test1" + resource2Name := "aws_security_group_rule.test2" + resource3Name := "aws_security_group_rule.test3" + resource4Name := "aws_security_group_rule.test4" + sg1ResourceName := "aws_security_group.test.0" + sg2ResourceName := "aws_security_group.test.1" + vpceResourceName := "aws_vpc_endpoint.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), ProviderFactories: acctest.ProviderFactories, - CheckDestroy: testAccCheckSecurityGroupRuleDestroy, + CheckDestroy: testAccCheckSecurityGroupDestroy, Steps: []resource.TestStep{ { - Config: testAccVPCSecurityGroupRuleConfig_multidescription(rInt, "ingress"), - Check: resource.ComposeTestCheckFunc( - testAccCheckSecurityGroupRuleExists("aws_security_group.worker", &group), - testAccCheckSecurityGroupRuleExists("aws_security_group.nat", &nat), - testAccCheckVPCEndpointExists("aws_vpc_endpoint.s3_endpoint", &endpoint), - - testAccCheckSecurityGroupRuleAttributes("aws_security_group_rule.rule_1", &group, &rule1, "ingress"), - resource.TestCheckResourceAttr("aws_security_group_rule.rule_1", "description", "CIDR Description"), - - testAccCheckSecurityGroupRuleAttributes("aws_security_group_rule.rule_2", &group, &rule2, "ingress"), - resource.TestCheckResourceAttr("aws_security_group_rule.rule_2", "description", "IPv6 CIDR Description"), - - setupSG, - testAccCheckSecurityGroupRuleAttributes("aws_security_group_rule.rule_3", &group, &rule3, "ingress"), - resource.TestCheckResourceAttr("aws_security_group_rule.rule_3", "description", "NAT SG Description"), + Config: testAccVPCSecurityGroupRuleConfig_multiDescription(rName, "ingress"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckSecurityGroupExists(sg1ResourceName, &group1), + testAccCheckSecurityGroupExists(sg2ResourceName, &group2), + resource.TestCheckResourceAttr(resource1Name, "cidr_blocks.#", "1"), + resource.TestCheckResourceAttr(resource1Name, "cidr_blocks.0", "0.0.0.0/0"), + resource.TestCheckResourceAttr(resource1Name, "description", "CIDR Description"), + resource.TestCheckResourceAttr(resource1Name, "from_port", "22"), + resource.TestCheckResourceAttr(resource1Name, "ipv6_cidr_blocks.#", "0"), + resource.TestCheckResourceAttr(resource1Name, "protocol", "tcp"), + resource.TestCheckResourceAttr(resource1Name, "prefix_list_ids.#", "0"), + resource.TestCheckResourceAttrPair(resource1Name, "security_group_id", sg1ResourceName, "id"), + resource.TestCheckResourceAttr(resource1Name, "self", "false"), + resource.TestCheckNoResourceAttr(resource1Name, "source_security_group_id"), + resource.TestCheckResourceAttr(resource1Name, "to_port", "22"), + resource.TestCheckResourceAttr(resource1Name, "type", "ingress"), + resource.TestCheckResourceAttr(resource2Name, "cidr_blocks.#", "0"), + resource.TestCheckResourceAttr(resource2Name, "description", "IPv6 CIDR Description"), + resource.TestCheckResourceAttr(resource2Name, "from_port", "22"), + resource.TestCheckResourceAttr(resource2Name, "ipv6_cidr_blocks.#", "1"), + resource.TestCheckResourceAttr(resource2Name, "ipv6_cidr_blocks.0", "::/0"), + resource.TestCheckResourceAttr(resource2Name, "protocol", "tcp"), + resource.TestCheckResourceAttr(resource2Name, "prefix_list_ids.#", "0"), + resource.TestCheckResourceAttrPair(resource2Name, "security_group_id", sg1ResourceName, "id"), + resource.TestCheckResourceAttr(resource2Name, "self", "false"), + resource.TestCheckNoResourceAttr(resource2Name, "source_security_group_id"), + resource.TestCheckResourceAttr(resource2Name, "to_port", "22"), + resource.TestCheckResourceAttr(resource2Name, "type", "ingress"), + resource.TestCheckResourceAttr(resource3Name, "cidr_blocks.#", "0"), + resource.TestCheckResourceAttr(resource3Name, "description", "Third Description"), + resource.TestCheckResourceAttr(resource3Name, "from_port", "22"), + resource.TestCheckResourceAttr(resource3Name, "ipv6_cidr_blocks.#", "0"), + resource.TestCheckResourceAttr(resource3Name, "protocol", "tcp"), + resource.TestCheckResourceAttr(resource3Name, "prefix_list_ids.#", "0"), + resource.TestCheckResourceAttrPair(resource3Name, "security_group_id", sg1ResourceName, "id"), + resource.TestCheckResourceAttr(resource3Name, "self", "false"), + resource.TestCheckResourceAttrPair(resource3Name, "source_security_group_id", sg2ResourceName, "id"), + resource.TestCheckResourceAttr(resource3Name, "to_port", "22"), + resource.TestCheckResourceAttr(resource3Name, "type", "ingress"), ), }, { - ResourceName: "aws_security_group_rule.rule_1", + ResourceName: resource1Name, ImportState: true, - ImportStateIdFunc: testAccSecurityGroupRuleImportStateIdFunc("aws_security_group_rule.rule_1"), + ImportStateIdFunc: testAccSecurityGroupRuleImportStateIdFunc(resource1Name), ImportStateVerify: true, }, { - ResourceName: "aws_security_group_rule.rule_2", + ResourceName: resource2Name, ImportState: true, - ImportStateIdFunc: testAccSecurityGroupRuleImportStateIdFunc("aws_security_group_rule.rule_2"), + ImportStateIdFunc: testAccSecurityGroupRuleImportStateIdFunc(resource2Name), ImportStateVerify: true, }, { - ResourceName: "aws_security_group_rule.rule_3", + ResourceName: resource3Name, ImportState: true, - ImportStateIdFunc: testAccSecurityGroupRuleImportStateIdFunc("aws_security_group_rule.rule_3"), + ImportStateIdFunc: testAccSecurityGroupRuleImportStateIdFunc(resource3Name), ImportStateVerify: true, }, { - Config: testAccVPCSecurityGroupRuleConfig_multidescription(rInt, "egress"), - Check: resource.ComposeTestCheckFunc( - testAccCheckSecurityGroupRuleExists("aws_security_group.worker", &group), - testAccCheckSecurityGroupRuleExists("aws_security_group.nat", &nat), - testAccCheckVPCEndpointExists("aws_vpc_endpoint.s3_endpoint", &endpoint), - - testAccCheckSecurityGroupRuleAttributes("aws_security_group_rule.rule_1", &group, &rule1, "egress"), - resource.TestCheckResourceAttr("aws_security_group_rule.rule_1", "description", "CIDR Description"), - - testAccCheckSecurityGroupRuleAttributes("aws_security_group_rule.rule_2", &group, &rule2, "egress"), - resource.TestCheckResourceAttr("aws_security_group_rule.rule_2", "description", "IPv6 CIDR Description"), - - setupSG, - testAccCheckSecurityGroupRuleAttributes("aws_security_group_rule.rule_3", &group, &rule3, "egress"), - resource.TestCheckResourceAttr("aws_security_group_rule.rule_3", "description", "NAT SG Description"), - - setupPL, - testAccCheckSecurityGroupRuleAttributes("aws_security_group_rule.rule_4", &group, &rule4, "egress"), - resource.TestCheckResourceAttr("aws_security_group_rule.rule_4", "description", "Prefix List Description"), + Config: testAccVPCSecurityGroupRuleConfig_multiDescription(rName, "egress"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckSecurityGroupExists(sg1ResourceName, &group1), + testAccCheckSecurityGroupExists(sg2ResourceName, &group2), + resource.TestCheckResourceAttr(resource1Name, "cidr_blocks.#", "1"), + resource.TestCheckResourceAttr(resource1Name, "cidr_blocks.0", "0.0.0.0/0"), + resource.TestCheckResourceAttr(resource1Name, "description", "CIDR Description"), + resource.TestCheckResourceAttr(resource1Name, "from_port", "22"), + resource.TestCheckResourceAttr(resource1Name, "ipv6_cidr_blocks.#", "0"), + resource.TestCheckResourceAttr(resource1Name, "protocol", "tcp"), + resource.TestCheckResourceAttr(resource1Name, "prefix_list_ids.#", "0"), + resource.TestCheckResourceAttrPair(resource1Name, "security_group_id", sg1ResourceName, "id"), + resource.TestCheckResourceAttr(resource1Name, "self", "false"), + resource.TestCheckNoResourceAttr(resource1Name, "source_security_group_id"), + resource.TestCheckResourceAttr(resource1Name, "to_port", "22"), + resource.TestCheckResourceAttr(resource1Name, "type", "egress"), + resource.TestCheckResourceAttr(resource2Name, "cidr_blocks.#", "0"), + resource.TestCheckResourceAttr(resource2Name, "description", "IPv6 CIDR Description"), + resource.TestCheckResourceAttr(resource2Name, "from_port", "22"), + resource.TestCheckResourceAttr(resource2Name, "ipv6_cidr_blocks.#", "1"), + resource.TestCheckResourceAttr(resource2Name, "ipv6_cidr_blocks.0", "::/0"), + resource.TestCheckResourceAttr(resource2Name, "protocol", "tcp"), + resource.TestCheckResourceAttr(resource2Name, "prefix_list_ids.#", "0"), + resource.TestCheckResourceAttrPair(resource2Name, "security_group_id", sg1ResourceName, "id"), + resource.TestCheckResourceAttr(resource2Name, "self", "false"), + resource.TestCheckNoResourceAttr(resource2Name, "source_security_group_id"), + resource.TestCheckResourceAttr(resource2Name, "to_port", "22"), + resource.TestCheckResourceAttr(resource2Name, "type", "egress"), + resource.TestCheckResourceAttr(resource3Name, "cidr_blocks.#", "0"), + resource.TestCheckResourceAttr(resource3Name, "description", "Third Description"), + resource.TestCheckResourceAttr(resource3Name, "from_port", "22"), + resource.TestCheckResourceAttr(resource3Name, "ipv6_cidr_blocks.#", "0"), + resource.TestCheckResourceAttr(resource3Name, "protocol", "tcp"), + resource.TestCheckResourceAttr(resource3Name, "prefix_list_ids.#", "0"), + resource.TestCheckResourceAttrPair(resource3Name, "security_group_id", sg1ResourceName, "id"), + resource.TestCheckResourceAttr(resource3Name, "self", "false"), + resource.TestCheckResourceAttrPair(resource3Name, "source_security_group_id", sg2ResourceName, "id"), + resource.TestCheckResourceAttr(resource3Name, "to_port", "22"), + resource.TestCheckResourceAttr(resource3Name, "type", "egress"), + resource.TestCheckResourceAttr(resource4Name, "cidr_blocks.#", "0"), + resource.TestCheckResourceAttr(resource4Name, "description", "Prefix List Description"), + resource.TestCheckResourceAttr(resource4Name, "from_port", "22"), + resource.TestCheckResourceAttr(resource4Name, "ipv6_cidr_blocks.#", "0"), + resource.TestCheckResourceAttr(resource4Name, "protocol", "tcp"), + resource.TestCheckResourceAttr(resource4Name, "prefix_list_ids.#", "1"), + resource.TestCheckResourceAttrPair(resource4Name, "prefix_list_ids.0", vpceResourceName, "prefix_list_id"), + resource.TestCheckResourceAttrPair(resource4Name, "security_group_id", sg1ResourceName, "id"), + resource.TestCheckResourceAttr(resource4Name, "self", "false"), + resource.TestCheckNoResourceAttr(resource4Name, "source_security_group_id"), + resource.TestCheckResourceAttr(resource4Name, "to_port", "22"), + resource.TestCheckResourceAttr(resource4Name, "type", "egress"), ), }, { - ResourceName: "aws_security_group_rule.rule_1", + ResourceName: resource1Name, ImportState: true, - ImportStateIdFunc: testAccSecurityGroupRuleImportStateIdFunc("aws_security_group_rule.rule_1"), + ImportStateIdFunc: testAccSecurityGroupRuleImportStateIdFunc(resource1Name), ImportStateVerify: true, }, { - ResourceName: "aws_security_group_rule.rule_2", + ResourceName: resource2Name, ImportState: true, - ImportStateIdFunc: testAccSecurityGroupRuleImportStateIdFunc("aws_security_group_rule.rule_2"), + ImportStateIdFunc: testAccSecurityGroupRuleImportStateIdFunc(resource2Name), ImportStateVerify: true, }, { - ResourceName: "aws_security_group_rule.rule_3", + ResourceName: resource3Name, ImportState: true, - ImportStateIdFunc: testAccSecurityGroupRuleImportStateIdFunc("aws_security_group_rule.rule_3"), + ImportStateIdFunc: testAccSecurityGroupRuleImportStateIdFunc(resource3Name), ImportStateVerify: true, }, { - ResourceName: "aws_security_group_rule.rule_4", + ResourceName: resource4Name, ImportState: true, - ImportStateIdFunc: testAccSecurityGroupRuleImportStateIdFunc("aws_security_group_rule.rule_4"), + ImportStateIdFunc: testAccSecurityGroupRuleImportStateIdFunc(resource4Name), ImportStateVerify: true, }, }, }) } -func testAccCheckSecurityGroupRuleDestroy(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).EC2Conn - - for _, rs := range s.RootModule().Resources { - if rs.Type != "aws_security_group" { - continue - } - - _, err := tfec2.FindSecurityGroupByID(conn, rs.Primary.ID) - if tfresource.NotFound(err) { - continue - } - if err != nil { - return err - } - - return fmt.Errorf("Security Group (%s) still exists.", rs.Primary.ID) - } +func TestAccVPCSecurityGroupRule_Ingress_multipleIPv6(t *testing.T) { + var group ec2.SecurityGroup + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_security_group_rule.test" + sgResourceName := "aws_security_group.test" - return nil + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + ProviderFactories: acctest.ProviderFactories, + CheckDestroy: testAccCheckSecurityGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccVPCSecurityGroupRuleConfig_ingressMultipleIPv6(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckSecurityGroupExists(sgResourceName, &group), + resource.TestCheckResourceAttr(resourceName, "cidr_blocks.#", "0"), + resource.TestCheckNoResourceAttr(resourceName, "description"), + resource.TestCheckResourceAttr(resourceName, "from_port", "80"), + resource.TestCheckResourceAttr(resourceName, "ipv6_cidr_blocks.#", "2"), + resource.TestCheckResourceAttr(resourceName, "ipv6_cidr_blocks.0", "2001:db8:85a3::/64"), + resource.TestCheckResourceAttr(resourceName, "ipv6_cidr_blocks.1", "2001:db8:85a3:2::/64"), + resource.TestCheckResourceAttr(resourceName, "protocol", "tcp"), + resource.TestCheckResourceAttr(resourceName, "prefix_list_ids.#", "0"), + resource.TestCheckResourceAttrPair(resourceName, "security_group_id", sgResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "self", "false"), + resource.TestCheckNoResourceAttr(resourceName, "source_security_group_id"), + resource.TestCheckResourceAttr(resourceName, "to_port", "8000"), + resource.TestCheckResourceAttr(resourceName, "type", "ingress"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateIdFunc: testAccSecurityGroupRuleImportStateIdFunc(resourceName), + ImportStateVerify: true, + }, + }, + }) } -func testAccCheckSecurityGroupRuleExists(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 := acctest.Provider.Meta().(*conns.AWSClient).EC2Conn - - sg, err := tfec2.FindSecurityGroupByID(conn, rs.Primary.ID) - if err != nil { - return err - } - - *group = *sg +func TestAccVPCSecurityGroupRule_Ingress_multiplePrefixLists(t *testing.T) { + var group ec2.SecurityGroup + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_security_group_rule.test" + sgResourceName := "aws_security_group.test" - return nil - } + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); testAccPreCheckManagedPrefixList(t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + ProviderFactories: acctest.ProviderFactories, + CheckDestroy: testAccCheckSecurityGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccVPCSecurityGroupRuleConfig_ingressMultiplePrefixLists(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckSecurityGroupExists(sgResourceName, &group), + resource.TestCheckResourceAttr(resourceName, "cidr_blocks.#", "0"), + resource.TestCheckNoResourceAttr(resourceName, "description"), + resource.TestCheckResourceAttr(resourceName, "from_port", "80"), + resource.TestCheckResourceAttr(resourceName, "ipv6_cidr_blocks.#", "0"), + resource.TestCheckResourceAttr(resourceName, "protocol", "tcp"), + resource.TestCheckResourceAttr(resourceName, "prefix_list_ids.#", "2"), + resource.TestCheckResourceAttrPair(resourceName, "security_group_id", sgResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "self", "false"), + resource.TestCheckNoResourceAttr(resourceName, "source_security_group_id"), + resource.TestCheckResourceAttr(resourceName, "to_port", "8000"), + resource.TestCheckResourceAttr(resourceName, "type", "ingress"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateIdFunc: testAccSecurityGroupRuleImportStateIdFunc(resourceName), + ImportStateVerify: true, + }, + }, + }) } -func testAccCheckSecurityGroupRuleAttributes(n string, group *ec2.SecurityGroup, p *ec2.IpPermission, ruleType string) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[n] - if !ok { - return fmt.Errorf("Security Group Rule Not found: %s", n) - } +func TestAccVPCSecurityGroupRule_Ingress_peeredVPC(t *testing.T) { + var group ec2.SecurityGroup + var providers []*schema.Provider + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_security_group_rule.test" + sgResourceName := "aws_security_group.test" - if rs.Primary.ID == "" { - return fmt.Errorf("No Security Group Rule is set") - } + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckAlternateAccount(t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + ProviderFactories: acctest.FactoriesAlternate(&providers), + CheckDestroy: testAccCheckSecurityGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccVPCSecurityGroupRuleConfig_ingressPeeredVPC(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckSecurityGroupExists(sgResourceName, &group), + resource.TestCheckResourceAttr(resourceName, "cidr_blocks.#", "0"), + resource.TestCheckNoResourceAttr(resourceName, "description"), + resource.TestCheckResourceAttr(resourceName, "from_port", "80"), + resource.TestCheckResourceAttr(resourceName, "ipv6_cidr_blocks.#", "0"), + resource.TestCheckResourceAttr(resourceName, "protocol", "tcp"), + resource.TestCheckResourceAttr(resourceName, "prefix_list_ids.#", "0"), + resource.TestCheckResourceAttrPair(resourceName, "security_group_id", sgResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "self", "false"), + resource.TestCheckResourceAttrSet(resourceName, "source_security_group_id"), + resource.TestCheckResourceAttr(resourceName, "to_port", "8000"), + resource.TestCheckResourceAttr(resourceName, "type", "ingress"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateIdFunc: testAccSecurityGroupRuleImportStateIdFunc(resourceName), + ImportStateVerify: true, + }, + }, + }) +} - if p == nil { - 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")}}, - } - } +func TestAccVPCSecurityGroupRule_Ingress_ipv4AndIPv6(t *testing.T) { + var group ec2.SecurityGroup + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_security_group_rule.test" + sgResourceName := "aws_security_group.test" - var matchingRule *ec2.IpPermission - var rules []*ec2.IpPermission - if ruleType == "ingress" { - rules = group.IpPermissions - } else { - rules = group.IpPermissionsEgress - } + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + ProviderFactories: acctest.ProviderFactories, + CheckDestroy: testAccCheckSecurityGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccVPCSecurityGroupRuleConfig_ingressIPv4AndIPv6(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckSecurityGroupExists(sgResourceName, &group), + resource.TestCheckResourceAttr(resourceName, "cidr_blocks.#", "1"), + resource.TestCheckResourceAttr(resourceName, "cidr_blocks.0", "10.2.0.0/16"), + resource.TestCheckNoResourceAttr(resourceName, "description"), + resource.TestCheckResourceAttr(resourceName, "from_port", "80"), + resource.TestCheckResourceAttr(resourceName, "ipv6_cidr_blocks.#", "1"), + resource.TestCheckResourceAttr(resourceName, "ipv6_cidr_blocks.0", "2001:db8:85a3::/64"), + resource.TestCheckResourceAttr(resourceName, "protocol", "tcp"), + resource.TestCheckResourceAttr(resourceName, "prefix_list_ids.#", "0"), + resource.TestCheckResourceAttrPair(resourceName, "security_group_id", sgResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "self", "false"), + resource.TestCheckNoResourceAttr(resourceName, "source_security_group_id"), + resource.TestCheckResourceAttr(resourceName, "to_port", "8000"), + resource.TestCheckResourceAttr(resourceName, "type", "ingress"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateIdFunc: testAccSecurityGroupRuleImportStateIdFunc(resourceName), + ImportStateVerify: true, + }, + }, + }) +} - if len(rules) == 0 { - return fmt.Errorf("No IPPerms") - } +func TestAccVPCSecurityGroupRule_Ingress_prefixListAndSelf(t *testing.T) { + var group ec2.SecurityGroup + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_security_group_rule.test" + sgResourceName := "aws_security_group.test" - for _, r := range rules { - if p.ToPort != nil && r.ToPort != nil && *p.ToPort != *r.ToPort { - continue - } - - if p.FromPort != nil && r.FromPort != nil && *p.FromPort != *r.FromPort { - continue - } - - if p.IpProtocol != nil && r.IpProtocol != nil && *p.IpProtocol != *r.IpProtocol { - continue - } - - remaining := len(p.IpRanges) - for _, ip := range p.IpRanges { - for _, rip := range r.IpRanges { - if ip.CidrIp == nil || rip.CidrIp == nil { - continue - } - if *ip.CidrIp == *rip.CidrIp { - remaining-- - } - } - } - - if remaining > 0 { - continue - } - - remaining = len(p.Ipv6Ranges) - for _, ip := range p.Ipv6Ranges { - for _, rip := range r.Ipv6Ranges { - if ip.CidrIpv6 == nil || rip.CidrIpv6 == nil { - continue - } - if *ip.CidrIpv6 == *rip.CidrIpv6 { - remaining-- - } - } - } - - if remaining > 0 { - continue - } - - remaining = len(p.UserIdGroupPairs) - for _, ip := range p.UserIdGroupPairs { - for _, rip := range r.UserIdGroupPairs { - if ip.GroupId == nil || rip.GroupId == nil { - continue - } - if *ip.GroupId == *rip.GroupId { - remaining-- - } - } - } - - if remaining > 0 { - continue - } - - remaining = len(p.PrefixListIds) - for _, pip := range p.PrefixListIds { - for _, rpip := range r.PrefixListIds { - if pip.PrefixListId == nil || rpip.PrefixListId == nil { - continue - } - if *pip.PrefixListId == *rpip.PrefixListId { - remaining-- - } - } - } - - if remaining > 0 { - continue - } - - matchingRule = r - } + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); testAccPreCheckManagedPrefixList(t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + ProviderFactories: acctest.ProviderFactories, + CheckDestroy: testAccCheckSecurityGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccVPCSecurityGroupRuleConfig_prefixListAndSelf(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckSecurityGroupExists(sgResourceName, &group), + resource.TestCheckResourceAttr(resourceName, "cidr_blocks.#", "0"), + resource.TestCheckNoResourceAttr(resourceName, "description"), + resource.TestCheckResourceAttr(resourceName, "from_port", "80"), + resource.TestCheckResourceAttr(resourceName, "ipv6_cidr_blocks.#", "0"), + resource.TestCheckResourceAttr(resourceName, "protocol", "tcp"), + resource.TestCheckResourceAttr(resourceName, "prefix_list_ids.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "security_group_id", sgResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "self", "true"), + resource.TestCheckNoResourceAttr(resourceName, "source_security_group_id"), + resource.TestCheckResourceAttr(resourceName, "to_port", "8000"), + resource.TestCheckResourceAttr(resourceName, "type", "ingress"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateIdFunc: testAccSecurityGroupRuleImportStateIdFunc(resourceName), + ImportStateVerify: true, + }, + }, + }) +} - if matchingRule != nil { - log.Printf("[DEBUG] Matching rule found : %s", matchingRule) - return nil - } +func TestAccVPCSecurityGroupRule_Ingress_prefixListAndSource(t *testing.T) { + var group ec2.SecurityGroup + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_security_group_rule.test" + sg1ResourceName := "aws_security_group.test.0" + sg2ResourceName := "aws_security_group.test.1" - return fmt.Errorf("Error here\n\tlooking for %s, wasn't found in %s", p, rules) - } + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); testAccPreCheckManagedPrefixList(t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + ProviderFactories: acctest.ProviderFactories, + CheckDestroy: testAccCheckSecurityGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccVPCSecurityGroupRuleConfig_prefixListAndSource(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckSecurityGroupExists(sg1ResourceName, &group), + resource.TestCheckResourceAttr(resourceName, "cidr_blocks.#", "0"), + resource.TestCheckNoResourceAttr(resourceName, "description"), + resource.TestCheckResourceAttr(resourceName, "from_port", "80"), + resource.TestCheckResourceAttr(resourceName, "ipv6_cidr_blocks.#", "0"), + resource.TestCheckResourceAttr(resourceName, "protocol", "tcp"), + resource.TestCheckResourceAttr(resourceName, "prefix_list_ids.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "security_group_id", sg1ResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "self", "false"), + resource.TestCheckResourceAttrPair(resourceName, "source_security_group_id", sg2ResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "to_port", "8000"), + resource.TestCheckResourceAttr(resourceName, "type", "ingress"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateIdFunc: testAccSecurityGroupRuleImportStateIdFunc(resourceName), + ImportStateVerify: true, + }, + }, + }) } func testAccSecurityGroupRuleImportStateIdFunc(resourceName string) resource.ImportStateIdFunc { @@ -1441,36 +1526,45 @@ func testAccSecurityGroupRuleImportGetAttrs(attrs map[string]string, key string) return &values, nil } -func testAccVPCSecurityGroupRuleConfig_ingress(rInt int) string { +func testAccVPCSecurityGroupRuleConfig_ingress(rName string) string { return fmt.Sprintf(` -resource "aws_security_group" "web" { - name = "terraform_test_%d" - description = "Used in the terraform acceptance tests" +resource "aws_security_group" "test" { + name = %[1]q tags = { - Name = "tf-acc-test" + Name = %[1]q } } -resource "aws_security_group_rule" "ingress_1" { +resource "aws_security_group_rule" "test" { type = "ingress" protocol = "tcp" from_port = 80 to_port = 8000 cidr_blocks = ["10.0.0.0/8"] - security_group_id = aws_security_group.web.id + security_group_id = aws_security_group.test.id } -`, rInt) +`, rName) } -const testAccVPCSecurityGroupRuleConfig_ingressIcmpv6 = ` +func testAccVPCSecurityGroupRuleConfig_ingressIcmpv6(rName string) string { + return fmt.Sprintf(` resource "aws_vpc" "test" { cidr_block = "10.0.0.0/16" + + tags = { + Name = %[1]q + } } resource "aws_security_group" "test" { vpc_id = aws_vpc.test.id + name = %[1]q + + tags = { + Name = %[1]q + } } resource "aws_security_group_rule" "test" { @@ -1481,399 +1575,372 @@ resource "aws_security_group_rule" "test" { protocol = "icmpv6" ipv6_cidr_blocks = ["::/0"] } -` +`, rName) +} -const testAccVPCSecurityGroupRuleConfig_ingressIPv6 = ` -resource "aws_vpc" "tftest" { +func testAccVPCSecurityGroupRuleConfig_ingressIPv6(rName string) string { + return fmt.Sprintf(` +resource "aws_vpc" "test" { cidr_block = "10.0.0.0/16" tags = { - Name = "terraform-testacc-security-group-rule-ingress-ipv6" + Name = %[1]q } } -resource "aws_security_group" "web" { - vpc_id = aws_vpc.tftest.id +resource "aws_security_group" "test" { + vpc_id = aws_vpc.test.id + name = %[1]q tags = { - Name = "tf-acc-test" + Name = %[1]q } } -resource "aws_security_group_rule" "ingress_1" { +resource "aws_security_group_rule" "test" { type = "ingress" protocol = "6" from_port = 80 to_port = 8000 ipv6_cidr_blocks = ["::/0"] - security_group_id = aws_security_group.web.id + security_group_id = aws_security_group.test.id +} +`, rName) } -` -const testAccVPCSecurityGroupRuleConfig_ingressProtocol = ` -resource "aws_vpc" "tftest" { +func testAccVPCSecurityGroupRuleConfig_ingressProtocol(rName string) string { + return fmt.Sprintf(` +resource "aws_vpc" "test" { cidr_block = "10.0.0.0/16" tags = { - Name = "terraform-testacc-security-group-rule-ingress-protocol" + Name = %[1]q } } -resource "aws_security_group" "web" { - vpc_id = aws_vpc.tftest.id +resource "aws_security_group" "test" { + vpc_id = aws_vpc.test.id + name = %[1]q tags = { - Name = "tf-acc-test" + Name = %[1]q } } -resource "aws_security_group_rule" "ingress_1" { +resource "aws_security_group_rule" "test" { type = "ingress" protocol = "6" from_port = 80 to_port = 8000 cidr_blocks = ["10.0.0.0/8"] - security_group_id = aws_security_group.web.id + security_group_id = aws_security_group.test.id +} +`, rName) } -` -const testAccVPCSecurityGroupRuleConfig_issue5310 = ` -resource "aws_security_group" "issue_5310" { - name = "terraform-test-issue_5310" - description = "SG for test of issue 5310" +func testAccVPCSecurityGroupRuleConfig_issue5310(rName string) string { + return fmt.Sprintf(` +resource "aws_security_group" "test" { + name = %[1]q + + tags = { + Name = %[1]q + } } -resource "aws_security_group_rule" "issue_5310" { +resource "aws_security_group_rule" "test" { type = "ingress" from_port = 0 to_port = 65535 protocol = "tcp" - security_group_id = aws_security_group.issue_5310.id + security_group_id = aws_security_group.test.id self = true } -` +`, rName) +} -func testAccVPCSecurityGroupRuleConfig_ingressClassic(rInt int) string { - return fmt.Sprintf(` -resource "aws_security_group" "web" { - name = "terraform_test_%d" - description = "Used in the terraform acceptance tests" +func testAccVPCSecurityGroupRuleConfig_ingressClassic(rName string) string { + return acctest.ConfigCompose(acctest.ConfigEC2ClassicRegionProvider(), fmt.Sprintf(` +resource "aws_security_group" "test" { + name = %[1]q tags = { - Name = "tf-acc-test" + Name = %[1]q } } -resource "aws_security_group_rule" "ingress_1" { +resource "aws_security_group_rule" "test" { type = "ingress" protocol = "tcp" from_port = 80 to_port = 8000 cidr_blocks = ["10.0.0.0/8"] - security_group_id = aws_security_group.web.id + security_group_id = aws_security_group.test.id } -`, rInt) +`, rName)) } -func testAccVPCSecurityGroupRuleConfig_egress(rInt int) string { +func testAccVPCSecurityGroupRuleConfig_egress(rName string) string { return fmt.Sprintf(` -resource "aws_security_group" "web" { - name = "terraform_test_%d" - description = "Used in the terraform acceptance tests" +resource "aws_security_group" "test" { + name = %[1]q tags = { - Name = "tf-acc-test" + Name = %[1]q } } -resource "aws_security_group_rule" "egress_1" { +resource "aws_security_group_rule" "test" { type = "egress" protocol = "tcp" from_port = 80 to_port = 8000 cidr_blocks = ["10.0.0.0/8"] - security_group_id = aws_security_group.web.id -} -`, rInt) -} - -const testAccVPCSecurityGroupRuleConfig_multiIngress = ` -resource "aws_security_group" "web" { - name = "terraform_acceptance_test_example_2" - description = "Used in the terraform acceptance tests" -} - -resource "aws_security_group" "worker" { - name = "terraform_acceptance_test_example_worker" - description = "Used in the terraform acceptance tests" -} - -resource "aws_security_group_rule" "ingress_1" { - type = "ingress" - protocol = "tcp" - from_port = 22 - to_port = 22 - cidr_blocks = ["10.0.0.0/8"] - - security_group_id = aws_security_group.web.id + security_group_id = aws_security_group.test.id } - -resource "aws_security_group_rule" "ingress_2" { - type = "ingress" - protocol = "tcp" - from_port = 80 - to_port = 8000 - self = true - - security_group_id = aws_security_group.web.id +`, rName) } -` -func testAccVPCSecurityGroupRuleConfig_multidescription(rInt int, rType string) string { - var b bytes.Buffer - b.WriteString(fmt.Sprintf(` -resource "aws_vpc" "tf_sgrule_description_test" { +func testAccVPCSecurityGroupRuleConfig_multiDescription(rName, ruleType string) string { + config := fmt.Sprintf(` +resource "aws_vpc" "test" { cidr_block = "10.0.0.0/16" tags = { - Name = "terraform-testacc-security-group-rule-multi-desc" + Name = %[1]q } } -data "aws_region" "current" {} - -resource "aws_vpc_endpoint" "s3_endpoint" { - vpc_id = aws_vpc.tf_sgrule_description_test.id - service_name = "com.amazonaws.${data.aws_region.current.name}.s3" -} - -resource "aws_security_group" "worker" { - name = "terraform_test_%[1]d" - vpc_id = aws_vpc.tf_sgrule_description_test.id - description = "Used in the terraform acceptance tests" - - tags = { Name = "tf-sg-rule-description" } -} +resource "aws_security_group" "test" { + count = 2 -resource "aws_security_group" "nat" { - name = "terraform_test_%[1]d_nat" - vpc_id = aws_vpc.tf_sgrule_description_test.id - description = "Used in the terraform acceptance tests" + name = "%[1]s-${count.index}" + vpc_id = aws_vpc.test.id - tags = { Name = "tf-sg-rule-description" } + tags = { + Name = %[1]q + } } -resource "aws_security_group_rule" "rule_1" { - security_group_id = aws_security_group.worker.id +resource "aws_security_group_rule" "test1" { + security_group_id = aws_security_group.test[0].id description = "CIDR Description" - type = "%[2]s" + type = %[2]q protocol = "tcp" from_port = 22 to_port = 22 cidr_blocks = ["0.0.0.0/0"] } -resource "aws_security_group_rule" "rule_2" { - security_group_id = aws_security_group.worker.id +resource "aws_security_group_rule" "test2" { + security_group_id = aws_security_group.test[0].id description = "IPv6 CIDR Description" - type = "%[2]s" + type = %[2]q protocol = "tcp" from_port = 22 to_port = 22 ipv6_cidr_blocks = ["::/0"] } -resource "aws_security_group_rule" "rule_3" { - security_group_id = aws_security_group.worker.id - description = "NAT SG Description" - type = "%[2]s" +resource "aws_security_group_rule" "test3" { + security_group_id = aws_security_group.test[0].id + description = "Third Description" + type = %[2]q protocol = "tcp" from_port = 22 to_port = 22 - source_security_group_id = aws_security_group.nat.id + source_security_group_id = aws_security_group.test[1].id +} +`, rName, ruleType) + + if ruleType == "egress" { + config = acctest.ConfigCompose(config, fmt.Sprintf(` +data "aws_region" "current" {} + +resource "aws_vpc_endpoint" "test" { + vpc_id = aws_vpc.test.id + service_name = "com.amazonaws.${data.aws_region.current.name}.s3" + + tags = { + Name = %[1]q + } } -`, rInt, rType)) - if rType == "egress" { - b.WriteString(` -resource "aws_security_group_rule" "rule_4" { - security_group_id = aws_security_group.worker.id +resource "aws_security_group_rule" "test4" { + security_group_id = aws_security_group.test[0].id description = "Prefix List Description" - type = "egress" + type = %[2]q protocol = "tcp" from_port = 22 to_port = 22 - prefix_list_ids = [aws_vpc_endpoint.s3_endpoint.prefix_list_id] + prefix_list_ids = [aws_vpc_endpoint.test.prefix_list_id] } -`) +`, rName, ruleType)) } - return b.String() + return config } // check for GH-1985 regression -const testAccVPCSecurityGroupRuleConfig_selfReference = ` -resource "aws_vpc" "main" { +func testAccVPCSecurityGroupRuleConfig_selfReference(rName string) string { + return fmt.Sprintf(` +resource "aws_vpc" "test" { cidr_block = "10.0.0.0/16" tags = { - Name = "terraform-testacc-security-group-rule-self-ref" + Name = %[1]q } } -resource "aws_security_group" "web" { - name = "main" - vpc_id = aws_vpc.main.id +resource "aws_security_group" "test" { + name = %[1]q + vpc_id = aws_vpc.test.id tags = { - Name = "sg-self-test" + Name = %[1]q } } -resource "aws_security_group_rule" "self" { +resource "aws_security_group_rule" "test" { type = "ingress" protocol = "-1" from_port = 0 to_port = 0 self = true - security_group_id = aws_security_group.web.id + security_group_id = aws_security_group.test.id +} +`, rName) } -` -func testAccVPCSecurityGroupRuleConfig_partialMatching(rInt int) string { +func testAccVPCSecurityGroupRuleConfig_partialMatching(rName string) string { return fmt.Sprintf(` -resource "aws_vpc" "default" { +resource "aws_vpc" "test" { cidr_block = "10.0.0.0/16" tags = { - Name = "terraform-testacc-security-group-rule-partial-match" + Name = %[1]q } } -resource "aws_security_group" "web" { - name = "tf-other-%d" - vpc_id = aws_vpc.default.id - - tags = { - Name = "tf-other-sg" - } -} +resource "aws_security_group" "test" { + count = 2 -resource "aws_security_group" "nat" { - name = "tf-nat-%d" - vpc_id = aws_vpc.default.id + name = "%[1]s-${count.index}" + vpc_id = aws_vpc.test.id tags = { - Name = "tf-nat-sg" + Name = %[1]q } } -resource "aws_security_group_rule" "ingress" { +resource "aws_security_group_rule" "test1" { type = "ingress" from_port = 80 to_port = 80 protocol = "tcp" cidr_blocks = ["10.0.2.0/24", "10.0.3.0/24", "10.0.4.0/24"] - security_group_id = aws_security_group.web.id + security_group_id = aws_security_group.test[0].id } -resource "aws_security_group_rule" "other" { +resource "aws_security_group_rule" "test2" { type = "ingress" from_port = 80 to_port = 80 protocol = "tcp" cidr_blocks = ["10.0.5.0/24"] - security_group_id = aws_security_group.web.id + security_group_id = aws_security_group.test[0].id } # same a above, but different group, to guard against bad hashing -resource "aws_security_group_rule" "nat_ingress" { +resource "aws_security_group_rule" "test3" { type = "ingress" from_port = 80 to_port = 80 protocol = "tcp" cidr_blocks = ["10.0.2.0/24", "10.0.3.0/24", "10.0.4.0/24"] - security_group_id = aws_security_group.nat.id + security_group_id = aws_security_group.test[1].id } -`, rInt, rInt) +`, rName) } -func testAccVPCSecurityGroupRuleConfig_partialMatchingSource(rInt int) string { +func testAccVPCSecurityGroupRuleConfig_partialMatchingSource(rName string) string { return fmt.Sprintf(` -resource "aws_vpc" "default" { +resource "aws_vpc" "test" { cidr_block = "10.0.0.0/16" tags = { - Name = "terraform-testacc-security-group-rule-partial-match" + Name = %[1]q } } -resource "aws_security_group" "web" { - name = "tf-other-%d" - vpc_id = aws_vpc.default.id - - tags = { - Name = "tf-other-sg" - } -} +resource "aws_security_group" "test" { + count = 2 -resource "aws_security_group" "nat" { - name = "tf-nat-%d" - vpc_id = aws_vpc.default.id + name = "%[1]s-${count.index}" + vpc_id = aws_vpc.test.id tags = { - Name = "tf-nat-sg" + Name = %[1]q } } -resource "aws_security_group_rule" "source_ingress" { +resource "aws_security_group_rule" "test1" { type = "ingress" from_port = 80 to_port = 80 protocol = "tcp" - source_security_group_id = aws_security_group.nat.id - security_group_id = aws_security_group.web.id + source_security_group_id = aws_security_group.test[0].id + security_group_id = aws_security_group.test[1].id } -resource "aws_security_group_rule" "other_ingress" { +resource "aws_security_group_rule" "test2" { type = "ingress" from_port = 80 to_port = 80 protocol = "tcp" cidr_blocks = ["10.0.2.0/24", "10.0.3.0/24", "10.0.4.0/24"] - security_group_id = aws_security_group.web.id + security_group_id = aws_security_group.test[0].id } -`, rInt, rInt) +`, rName) } -const testAccVPCSecurityGroupRuleConfig_prefixListEgress = ` -resource "aws_vpc" "tf_sg_prefix_list_egress_test" { +func testAccVPCSecurityGroupRuleConfig_prefixListEgress(rName string) string { + return fmt.Sprintf(` +resource "aws_vpc" "test" { cidr_block = "10.0.0.0/16" tags = { - Name = "terraform-testacc-security-group-rule-prefix-list-egress" + Name = %[1]q } } -resource "aws_route_table" "default" { - vpc_id = aws_vpc.tf_sg_prefix_list_egress_test.id +resource "aws_route_table" "test" { + vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } } data "aws_region" "current" {} -resource "aws_vpc_endpoint" "s3_endpoint" { - vpc_id = aws_vpc.tf_sg_prefix_list_egress_test.id +resource "aws_vpc_endpoint" "test" { + vpc_id = aws_vpc.test.id service_name = "com.amazonaws.${data.aws_region.current.name}.s3" - route_table_ids = [aws_route_table.default.id] + route_table_ids = [aws_route_table.test.id] + + tags = { + Name = %[1]q + } policy = < 0 && + len(group.IpPermissions[0].UserIdGroupPairs) > 0 && + aws.StringValue(group.IpPermissions[0].UserIdGroupPairs[0].GroupId) == aws.StringValue(group.GroupId) { + return nil } - return nil + return fmt.Errorf("Security Group does not contain \"self\" rule: %#v", group) } resource.ParallelTest(t, resource.TestCase{ @@ -1238,11 +1338,10 @@ func TestAccVPCSecurityGroup_self(t *testing.T) { CheckDestroy: testAccCheckSecurityGroupDestroy, Steps: []resource.TestStep{ { - Config: testAccVPCSecurityGroupConfig_self, + Config: testAccVPCSecurityGroupConfig_self(rName), Check: resource.ComposeTestCheckFunc( testAccCheckSecurityGroupExists(resourceName, &group), - resource.TestCheckResourceAttr(resourceName, "name", "terraform_acceptance_test_example"), - resource.TestCheckResourceAttr(resourceName, "description", "Used in the terraform acceptance tests"), + resource.TestCheckResourceAttr(resourceName, "ingress.#", "1"), resource.TestCheckTypeSetElemNestedAttrs(resourceName, "ingress.*", map[string]string{ "protocol": "tcp", "from_port": "80", @@ -1264,6 +1363,7 @@ func TestAccVPCSecurityGroup_self(t *testing.T) { func TestAccVPCSecurityGroup_vpc(t *testing.T) { var group ec2.SecurityGroup + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_security_group.test" resource.ParallelTest(t, resource.TestCase{ @@ -1273,12 +1373,10 @@ func TestAccVPCSecurityGroup_vpc(t *testing.T) { CheckDestroy: testAccCheckSecurityGroupDestroy, Steps: []resource.TestStep{ { - Config: testAccVPCSecurityGroupConfig_vpc, + Config: testAccVPCSecurityGroupConfig_vpc(rName), Check: resource.ComposeTestCheckFunc( testAccCheckSecurityGroupExists(resourceName, &group), - testAccCheckSecurityGroupAttributes(&group), - resource.TestCheckResourceAttr(resourceName, "name", "terraform_acceptance_test_example"), - resource.TestCheckResourceAttr(resourceName, "description", "Used in the terraform acceptance tests"), + resource.TestCheckResourceAttr(resourceName, "ingress.#", "1"), resource.TestCheckTypeSetElemNestedAttrs(resourceName, "ingress.*", map[string]string{ "protocol": "tcp", "from_port": "80", @@ -1286,6 +1384,7 @@ func TestAccVPCSecurityGroup_vpc(t *testing.T) { "cidr_blocks.#": "1", "cidr_blocks.0": "10.0.0.0/8", }), + resource.TestCheckResourceAttr(resourceName, "egress.#", "1"), resource.TestCheckTypeSetElemNestedAttrs(resourceName, "egress.*", map[string]string{ "protocol": "tcp", "from_port": "80", @@ -1293,7 +1392,7 @@ func TestAccVPCSecurityGroup_vpc(t *testing.T) { "cidr_blocks.#": "1", "cidr_blocks.0": "10.0.0.0/8", }), - testAccSecurityGroupCheckVPCIDExists(&group), + resource.TestCheckResourceAttrSet(resourceName, "vpc_id"), ), }, { @@ -1308,6 +1407,7 @@ func TestAccVPCSecurityGroup_vpc(t *testing.T) { func TestAccVPCSecurityGroup_vpcNegOneIngress(t *testing.T) { var group ec2.SecurityGroup + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_security_group.test" resource.ParallelTest(t, resource.TestCase{ @@ -1317,12 +1417,10 @@ func TestAccVPCSecurityGroup_vpcNegOneIngress(t *testing.T) { CheckDestroy: testAccCheckSecurityGroupDestroy, Steps: []resource.TestStep{ { - Config: testAccVPCSecurityGroupConfig_negOneIngress, + Config: testAccVPCSecurityGroupConfig_vpcNegativeOneIngress(rName), Check: resource.ComposeTestCheckFunc( testAccCheckSecurityGroupExists(resourceName, &group), - testAccCheckSecurityGroupAttributesNegOneProtocol(&group), - resource.TestCheckResourceAttr(resourceName, "name", "terraform_acceptance_test_example"), - resource.TestCheckResourceAttr(resourceName, "description", "Used in the terraform acceptance tests"), + resource.TestCheckResourceAttr(resourceName, "ingress.#", "1"), resource.TestCheckTypeSetElemNestedAttrs(resourceName, "ingress.*", map[string]string{ "protocol": "-1", "from_port": "0", @@ -1330,7 +1428,7 @@ func TestAccVPCSecurityGroup_vpcNegOneIngress(t *testing.T) { "cidr_blocks.#": "1", "cidr_blocks.0": "10.0.0.0/8", }), - testAccSecurityGroupCheckVPCIDExists(&group), + resource.TestCheckResourceAttrSet(resourceName, "vpc_id"), ), }, { @@ -1345,6 +1443,7 @@ func TestAccVPCSecurityGroup_vpcNegOneIngress(t *testing.T) { func TestAccVPCSecurityGroup_vpcProtoNumIngress(t *testing.T) { var group ec2.SecurityGroup + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_security_group.test" resource.ParallelTest(t, resource.TestCase{ @@ -1354,11 +1453,10 @@ func TestAccVPCSecurityGroup_vpcProtoNumIngress(t *testing.T) { CheckDestroy: testAccCheckSecurityGroupDestroy, Steps: []resource.TestStep{ { - Config: testAccVPCSecurityGroupConfig_protoNumIngress, + Config: testAccVPCSecurityGroupConfig_vpcProtocolNumberIngress(rName), Check: resource.ComposeTestCheckFunc( testAccCheckSecurityGroupExists(resourceName, &group), - resource.TestCheckResourceAttr(resourceName, "name", "terraform_acceptance_test_example"), - resource.TestCheckResourceAttr(resourceName, "description", "Used in the terraform acceptance tests"), + resource.TestCheckResourceAttr(resourceName, "ingress.#", "1"), resource.TestCheckTypeSetElemNestedAttrs(resourceName, "ingress.*", map[string]string{ "protocol": "50", "from_port": "0", @@ -1366,7 +1464,7 @@ func TestAccVPCSecurityGroup_vpcProtoNumIngress(t *testing.T) { "cidr_blocks.#": "1", "cidr_blocks.0": "10.0.0.0/8", }), - testAccSecurityGroupCheckVPCIDExists(&group), + resource.TestCheckResourceAttrSet(resourceName, "vpc_id"), ), }, { @@ -1381,33 +1479,8 @@ func TestAccVPCSecurityGroup_vpcProtoNumIngress(t *testing.T) { func TestAccVPCSecurityGroup_multiIngress(t *testing.T) { var group ec2.SecurityGroup - resourceName := "aws_security_group.test" - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t) }, - ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), - ProviderFactories: acctest.ProviderFactories, - CheckDestroy: testAccCheckSecurityGroupDestroy, - Steps: []resource.TestStep{ - { - Config: testAccVPCSecurityGroupConfig_multiIngress, - Check: resource.ComposeTestCheckFunc( - testAccCheckSecurityGroupExists(resourceName, &group), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"revoke_rules_on_delete"}, - }, - }, - }) -} - -func TestAccVPCSecurityGroup_change(t *testing.T) { - var group ec2.SecurityGroup - resourceName := "aws_security_group.test" + resourceName := "aws_security_group.test1" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -1416,7 +1489,7 @@ func TestAccVPCSecurityGroup_change(t *testing.T) { CheckDestroy: testAccCheckSecurityGroupDestroy, Steps: []resource.TestStep{ { - Config: testAccVPCSecurityGroupConfig_basic, + Config: testAccVPCSecurityGroupConfig_multiIngress(rName), Check: resource.ComposeTestCheckFunc( testAccCheckSecurityGroupExists(resourceName, &group), ), @@ -1427,13 +1500,6 @@ func TestAccVPCSecurityGroup_change(t *testing.T) { ImportStateVerify: true, ImportStateVerifyIgnore: []string{"revoke_rules_on_delete"}, }, - { - Config: testAccVPCSecurityGroupConfig_change, - Check: resource.ComposeTestCheckFunc( - testAccCheckSecurityGroupExists(resourceName, &group), - testAccCheckSecurityGroupAttributesChanged(&group), - ), - }, }, }) } @@ -1441,6 +1507,7 @@ func TestAccVPCSecurityGroup_change(t *testing.T) { func TestAccVPCSecurityGroup_ruleDescription(t *testing.T) { var group ec2.SecurityGroup resourceName := "aws_security_group.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -1449,7 +1516,7 @@ func TestAccVPCSecurityGroup_ruleDescription(t *testing.T) { CheckDestroy: testAccCheckSecurityGroupDestroy, Steps: []resource.TestStep{ { - Config: testAccVPCSecurityGroupConfig_ruleDescription("Egress description", "Ingress description"), + Config: testAccVPCSecurityGroupConfig_ruleDescription(rName, "Egress description", "Ingress description"), Check: resource.ComposeTestCheckFunc( testAccCheckSecurityGroupExists(resourceName, &group), resource.TestCheckResourceAttr(resourceName, "egress.#", "1"), @@ -1487,7 +1554,7 @@ func TestAccVPCSecurityGroup_ruleDescription(t *testing.T) { }, // Change just the rule descriptions. { - Config: testAccVPCSecurityGroupConfig_ruleDescription("New egress description", "New ingress description"), + Config: testAccVPCSecurityGroupConfig_ruleDescription(rName, "New egress description", "New ingress description"), Check: resource.ComposeTestCheckFunc( testAccCheckSecurityGroupExists(resourceName, &group), resource.TestCheckResourceAttr(resourceName, "egress.#", "1"), @@ -1519,7 +1586,7 @@ func TestAccVPCSecurityGroup_ruleDescription(t *testing.T) { }, // Remove just the rule descriptions. { - Config: testAccVPCSecurityGroupConfig_emptyRuleDescription, + Config: testAccVPCSecurityGroupConfig_emptyRuleDescription(rName), Check: resource.ComposeTestCheckFunc( testAccCheckSecurityGroupExists(resourceName, &group), resource.TestCheckResourceAttr(resourceName, "egress.#", "1"), @@ -1551,50 +1618,25 @@ func TestAccVPCSecurityGroup_ruleDescription(t *testing.T) { } func TestAccVPCSecurityGroup_defaultEgressVPC(t *testing.T) { - resourceName := "aws_security_group.test" - - // VPC - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t) }, - ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), - ProviderFactories: acctest.ProviderFactories, - CheckDestroy: testAccCheckSecurityGroupDestroy, - Steps: []resource.TestStep{ - { - Config: testAccVPCSecurityGroupConfig_defaultEgress, - Check: resource.ComposeTestCheckFunc( - testAccCheckSecurityGroupExistsWithoutDefault(resourceName), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"revoke_rules_on_delete"}, - }, - }, - }) -} - -func TestAccVPCSecurityGroup_defaultEgressClassic(t *testing.T) { var group ec2.SecurityGroup resourceName := "aws_security_group.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckEC2Classic(t) }, + PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), ProviderFactories: acctest.ProviderFactories, - CheckDestroy: testAccCheckSecurityGroupClassicDestroy, + CheckDestroy: testAccCheckSecurityGroupDestroy, Steps: []resource.TestStep{ { - Config: testAccVPCSecurityGroupConfig_classic(rName), + Config: testAccVPCSecurityGroupConfig_defaultEgress(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckSecurityGroupClassicExists(resourceName, &group), + testAccCheckSecurityGroupExists(resourceName, &group), + resource.TestCheckResourceAttr(resourceName, "egress.#", "1"), + resource.TestCheckResourceAttr(resourceName, "ingress.#", "0"), ), }, { - Config: testAccVPCSecurityGroupConfig_classic(rName), ResourceName: resourceName, ImportState: true, ImportStateVerify: true, @@ -1606,8 +1648,9 @@ func TestAccVPCSecurityGroup_defaultEgressClassic(t *testing.T) { // Testing drift detection with groups containing the same port and types func TestAccVPCSecurityGroup_drift(t *testing.T) { - resourceName := "aws_security_group.test" var group ec2.SecurityGroup + resourceName := "aws_security_group.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -1616,10 +1659,9 @@ func TestAccVPCSecurityGroup_drift(t *testing.T) { CheckDestroy: testAccCheckSecurityGroupDestroy, Steps: []resource.TestStep{ { - Config: testAccVPCSecurityGroupConfig_drift(), + Config: testAccVPCSecurityGroupConfig_drift(rName), Check: resource.ComposeTestCheckFunc( testAccCheckSecurityGroupExists(resourceName, &group), - resource.TestCheckResourceAttr(resourceName, "description", "Used in the terraform acceptance tests"), resource.TestCheckResourceAttr(resourceName, "egress.#", "0"), resource.TestCheckResourceAttr(resourceName, "ingress.#", "2"), resource.TestCheckTypeSetElemNestedAttrs(resourceName, "ingress.*", map[string]string{ @@ -1661,7 +1703,8 @@ func TestAccVPCSecurityGroup_drift(t *testing.T) { func TestAccVPCSecurityGroup_driftComplex(t *testing.T) { var group ec2.SecurityGroup - resourceName := "aws_security_group.test" + resourceName := "aws_security_group.test1" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -1670,10 +1713,9 @@ func TestAccVPCSecurityGroup_driftComplex(t *testing.T) { CheckDestroy: testAccCheckSecurityGroupDestroy, Steps: []resource.TestStep{ { - Config: testAccVPCSecurityGroupConfig_driftComplex(), + Config: testAccVPCSecurityGroupConfig_driftComplex(rName), Check: resource.ComposeTestCheckFunc( testAccCheckSecurityGroupExists(resourceName, &group), - resource.TestCheckResourceAttr(resourceName, "description", "Used in the terraform acceptance tests"), resource.TestCheckResourceAttr(resourceName, "egress.#", "3"), resource.TestCheckTypeSetElemNestedAttrs(resourceName, "egress.*", map[string]string{ "cidr_blocks.#": "1", @@ -1764,9 +1806,9 @@ func TestAccVPCSecurityGroup_invalidCIDRBlock(t *testing.T) { }) } -func TestAccVPCSecurityGroup_tags(t *testing.T) { +func TestAccVPCSecurityGroup_cidrAndGroups(t *testing.T) { var group ec2.SecurityGroup - resourceName := "aws_security_group.test" + resourceName := "aws_security_group.test1" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ @@ -1776,11 +1818,9 @@ func TestAccVPCSecurityGroup_tags(t *testing.T) { CheckDestroy: testAccCheckSecurityGroupDestroy, Steps: []resource.TestStep{ { - Config: testAccVPCSecurityGroupConfig_tags1(rName, "key1", "value1"), + Config: testAccVPCSecurityGroupConfig_combinedCIDRAndGroups(rName), Check: resource.ComposeTestCheckFunc( testAccCheckSecurityGroupExists(resourceName, &group), - resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), - resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), ), }, { @@ -1789,30 +1829,14 @@ func TestAccVPCSecurityGroup_tags(t *testing.T) { ImportStateVerify: true, ImportStateVerifyIgnore: []string{"revoke_rules_on_delete"}, }, - { - Config: testAccVPCSecurityGroupConfig_tags2(rName, "key1", "value1updated", "key2", "value2"), - Check: resource.ComposeTestCheckFunc( - testAccCheckSecurityGroupExists(resourceName, &group), - resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), - resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), - resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), - ), - }, - { - Config: testAccVPCSecurityGroupConfig_tags1(rName, "key2", "value2"), - Check: resource.ComposeTestCheckFunc( - testAccCheckSecurityGroupExists(resourceName, &group), - resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), - resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), - ), - }, }, }) } -func TestAccVPCSecurityGroup_cidrAndGroups(t *testing.T) { +func TestAccVPCSecurityGroup_ingressWithCIDRAndSGsVPC(t *testing.T) { var group ec2.SecurityGroup - resourceName := "aws_security_group.test" + resourceName := "aws_security_group.test1" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -1821,37 +1845,9 @@ func TestAccVPCSecurityGroup_cidrAndGroups(t *testing.T) { CheckDestroy: testAccCheckSecurityGroupDestroy, Steps: []resource.TestStep{ { - Config: testAccVPCSecurityGroupConfig_combinedCIDRAndGroups, + Config: testAccVPCSecurityGroupConfig_ingressWithCIDRAndSGs(rName), Check: resource.ComposeTestCheckFunc( testAccCheckSecurityGroupExists(resourceName, &group), - // testAccCheckSecurityGroupAttributes(&group), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"revoke_rules_on_delete"}, - }, - }, - }) -} - -func TestAccVPCSecurityGroup_ingressWithCIDRAndSGsVPC(t *testing.T) { - var group ec2.SecurityGroup - resourceName := "aws_security_group.test" - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t) }, - ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), - ProviderFactories: acctest.ProviderFactories, - CheckDestroy: testAccCheckSecurityGroupDestroy, - Steps: []resource.TestStep{ - { - Config: testAccVPCSecurityGroupConfig_ingressCIDRAndSGs, - Check: resource.ComposeTestCheckFunc( - testAccCheckSecurityGroupExists(resourceName, &group), - testAccCheckSecurityGroupSGandCIDRAttributes(&group), resource.TestCheckResourceAttr(resourceName, "egress.#", "1"), resource.TestCheckTypeSetElemNestedAttrs(resourceName, "egress.*", map[string]string{ "cidr_blocks.#": "1", @@ -1891,19 +1887,19 @@ func TestAccVPCSecurityGroup_ingressWithCIDRAndSGsVPC(t *testing.T) { func TestAccVPCSecurityGroup_ingressWithCIDRAndSGsClassic(t *testing.T) { var group ec2.SecurityGroup - resourceName := "aws_security_group.test" + resourceName := "aws_security_group.test1" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckEC2Classic(t) }, ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), ProviderFactories: acctest.ProviderFactories, - CheckDestroy: testAccCheckSecurityGroupClassicDestroy, + CheckDestroy: testAccCheckSecurityGroupEC2ClassicDestroy, Steps: []resource.TestStep{ { - Config: testAccVPCSecurityGroupConfig_ingressCIDRAndSGsClassic(rName), + Config: testAccVPCSecurityGroupConfig_ingressWithCIDRAndSGsEC2Classic(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckSecurityGroupClassicExists(resourceName, &group), + testAccCheckSecurityGroupEC2ClassicExists(resourceName, &group), resource.TestCheckResourceAttr(resourceName, "egress.#", "0"), resource.TestCheckResourceAttr(resourceName, "ingress.#", "2"), resource.TestCheckTypeSetElemNestedAttrs(resourceName, "ingress.*", map[string]string{ @@ -1920,7 +1916,7 @@ func TestAccVPCSecurityGroup_ingressWithCIDRAndSGsClassic(t *testing.T) { ), }, { - Config: testAccVPCSecurityGroupConfig_ingressCIDRAndSGsClassic(rName), + Config: testAccVPCSecurityGroupConfig_ingressWithCIDRAndSGsEC2Classic(rName), ResourceName: resourceName, ImportState: true, ImportStateVerify: true, @@ -1933,6 +1929,7 @@ func TestAccVPCSecurityGroup_ingressWithCIDRAndSGsClassic(t *testing.T) { func TestAccVPCSecurityGroup_egressWithPrefixList(t *testing.T) { var group ec2.SecurityGroup resourceName := "aws_security_group.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -1941,10 +1938,9 @@ func TestAccVPCSecurityGroup_egressWithPrefixList(t *testing.T) { CheckDestroy: testAccCheckSecurityGroupDestroy, Steps: []resource.TestStep{ { - Config: testAccVPCSecurityGroupConfig_prefixListEgress, + Config: testAccVPCSecurityGroupConfig_prefixListEgress(rName), Check: resource.ComposeTestCheckFunc( testAccCheckSecurityGroupExists(resourceName, &group), - testAccCheckSecurityGroupEgressPrefixListAttributes(&group), resource.TestCheckResourceAttr(resourceName, "egress.#", "1"), ), }, @@ -1961,6 +1957,7 @@ func TestAccVPCSecurityGroup_egressWithPrefixList(t *testing.T) { func TestAccVPCSecurityGroup_ingressWithPrefixList(t *testing.T) { var group ec2.SecurityGroup resourceName := "aws_security_group.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -1969,10 +1966,9 @@ func TestAccVPCSecurityGroup_ingressWithPrefixList(t *testing.T) { CheckDestroy: testAccCheckSecurityGroupDestroy, Steps: []resource.TestStep{ { - Config: testAccVPCSecurityGroupConfig_prefixListIngress, + Config: testAccVPCSecurityGroupConfig_prefixListIngress(rName), Check: resource.ComposeTestCheckFunc( testAccCheckSecurityGroupExists(resourceName, &group), - testAccCheckSecurityGroupIngressPrefixListAttributes(&group), resource.TestCheckResourceAttr(resourceName, "ingress.#", "1"), ), }, @@ -1989,6 +1985,7 @@ func TestAccVPCSecurityGroup_ingressWithPrefixList(t *testing.T) { func TestAccVPCSecurityGroup_ipv4AndIPv6Egress(t *testing.T) { var group ec2.SecurityGroup resourceName := "aws_security_group.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -1997,7 +1994,7 @@ func TestAccVPCSecurityGroup_ipv4AndIPv6Egress(t *testing.T) { CheckDestroy: testAccCheckSecurityGroupDestroy, Steps: []resource.TestStep{ { - Config: testAccVPCSecurityGroupConfig_ipv4andIPv6Egress, + Config: testAccVPCSecurityGroupConfig_ipv4andIPv6Egress(rName), Check: resource.ComposeTestCheckFunc( testAccCheckSecurityGroupExists(resourceName, &group), resource.TestCheckResourceAttr(resourceName, "egress.#", "2"), @@ -2038,487 +2035,143 @@ func TestAccVPCSecurityGroup_ipv4AndIPv6Egress(t *testing.T) { }) } -func testAccSecurityGroupCheckVPCIDExists(group *ec2.SecurityGroup) resource.TestCheckFunc { - return func(*terraform.State) error { - if aws.StringValue(group.VpcId) == "" { - return fmt.Errorf("should have vpc ID") - } - return nil - } -} +func TestAccVPCSecurityGroup_failWithDiffMismatch(t *testing.T) { + var group ec2.SecurityGroup + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_security_group.test1" -// cycleIPPermForGroup returns an IpPermission struct with a configured -// UserIdGroupPair for the groupid given. Used in -// TestAccVPCSecurityGroup_forceRevokeRulesTrue to create a cyclic rule -// between 2 security groups -func cycleIPPermForGroup(groupId string) *ec2.IpPermission { - var perm ec2.IpPermission - perm.FromPort = aws.Int64(0) - perm.ToPort = aws.Int64(0) - perm.IpProtocol = aws.String("icmp") - perm.UserIdGroupPairs = make([]*ec2.UserIdGroupPair, 1) - perm.UserIdGroupPairs[0] = &ec2.UserIdGroupPair{ - GroupId: aws.String(groupId), - } - return &perm + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + ProviderFactories: acctest.ProviderFactories, + CheckDestroy: testAccCheckSecurityGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccVPCSecurityGroupConfig_failWithDiffMismatch(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckSecurityGroupExists(resourceName, &group), + resource.TestCheckResourceAttr(resourceName, "egress.#", "0"), + resource.TestCheckResourceAttr(resourceName, "ingress.#", "2"), + ), + }, + }, + }) } -// testAddRuleCycle returns a TestCheckFunc to use at the end of a test, such -// that a Security Group Rule cyclic dependency will be created between the two -// Security Groups. A companion function, testRemoveRuleCycle, will undo this. -func testAddRuleCycle(primary, secondary *ec2.SecurityGroup) resource.TestCheckFunc { - return func(s *terraform.State) error { - if primary.GroupId == nil { - return fmt.Errorf("Primary SG not set for TestAccVPCSecurityGroup_forceRevokeRulesTrue") - } - if secondary.GroupId == nil { - return fmt.Errorf("Secondary SG not set for TestAccVPCSecurityGroup_forceRevokeRulesTrue") - } - - conn := acctest.Provider.Meta().(*conns.AWSClient).EC2Conn - - // cycle from primary to secondary - perm1 := cycleIPPermForGroup(*secondary.GroupId) - // cycle from secondary to primary - perm2 := cycleIPPermForGroup(*primary.GroupId) +func TestAccVPCSecurityGroup_ruleLimitExceededAppend(t *testing.T) { + ruleLimit := testAccSecurityGroupRulesPerGroupLimitFromEnv() - req1 := &ec2.AuthorizeSecurityGroupEgressInput{ - GroupId: primary.GroupId, - IpPermissions: []*ec2.IpPermission{perm1}, - } - req2 := &ec2.AuthorizeSecurityGroupEgressInput{ - GroupId: secondary.GroupId, - IpPermissions: []*ec2.IpPermission{perm2}, - } + var group ec2.SecurityGroup + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_security_group.test" - var err error - _, err = conn.AuthorizeSecurityGroupEgress(req1) - if err != nil { - return fmt.Errorf("Error authorizing primary security group %s rules: %w", aws.StringValue(primary.GroupId), err) - } - _, err = conn.AuthorizeSecurityGroupEgress(req2) - if err != nil { - return fmt.Errorf("Error authorizing secondary security group %s rules: %w", aws.StringValue(secondary.GroupId), err) - } - return nil - } + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + ProviderFactories: acctest.ProviderFactories, + CheckDestroy: testAccCheckSecurityGroupDestroy, + Steps: []resource.TestStep{ + // create a valid SG just under the limit + { + Config: testAccVPCSecurityGroupConfig_ruleLimit(rName, 0, ruleLimit), + Check: resource.ComposeTestCheckFunc( + testAccCheckSecurityGroupExists(resourceName, &group), + testAccCheckSecurityGroupRuleCount(&group, 0, ruleLimit), + resource.TestCheckResourceAttr(resourceName, "egress.#", strconv.Itoa(ruleLimit)), + ), + }, + // append a rule to step over the limit + { + Config: testAccVPCSecurityGroupConfig_ruleLimit(rName, 0, ruleLimit+1), + ExpectError: regexp.MustCompile("RulesPerSecurityGroupLimitExceeded"), + }, + { + PreConfig: func() { + // should have the original rules still + err := testSecurityGroupRuleCount(aws.StringValue(group.GroupId), 0, ruleLimit) + if err != nil { + t.Fatalf("PreConfig check failed: %s", err) + } + }, + // running the original config again now should restore the rules + Config: testAccVPCSecurityGroupConfig_ruleLimit(rName, 0, ruleLimit), + Check: resource.ComposeTestCheckFunc( + testAccCheckSecurityGroupExists(resourceName, &group), + testAccCheckSecurityGroupRuleCount(&group, 0, ruleLimit), + resource.TestCheckResourceAttr(resourceName, "egress.#", strconv.Itoa(ruleLimit)), + ), + }, + }, + }) } -// testRemoveRuleCycle removes the cyclic dependency between two security groups -// that was added in testAddRuleCycle -func testRemoveRuleCycle(primary, secondary *ec2.SecurityGroup) resource.TestCheckFunc { - return func(s *terraform.State) error { - if primary.GroupId == nil { - return fmt.Errorf("Primary SG not set for TestAccVPCSecurityGroup_forceRevokeRulesTrue") - } - if secondary.GroupId == nil { - return fmt.Errorf("Secondary SG not set for TestAccVPCSecurityGroup_forceRevokeRulesTrue") - } - - conn := acctest.Provider.Meta().(*conns.AWSClient).EC2Conn - for _, sg := range []*ec2.SecurityGroup{primary, secondary} { - var err error - if sg.IpPermissions != nil { - req := &ec2.RevokeSecurityGroupIngressInput{ - GroupId: sg.GroupId, - IpPermissions: sg.IpPermissions, - } - - if _, err = conn.RevokeSecurityGroupIngress(req); err != nil { - return fmt.Errorf("Error revoking default ingress rule for Security Group in testRemoveCycle (%s): %w", aws.StringValue(primary.GroupId), err) - } - } - - if sg.IpPermissionsEgress != nil { - req := &ec2.RevokeSecurityGroupEgressInput{ - GroupId: sg.GroupId, - IpPermissions: sg.IpPermissionsEgress, - } - - if _, err = conn.RevokeSecurityGroupEgress(req); err != nil { - return fmt.Errorf("Error revoking default egress rule for Security Group in testRemoveCycle (%s): %w", aws.StringValue(sg.GroupId), err) - } - } - } - return nil - } -} +func TestAccVPCSecurityGroup_ruleLimitCIDRBlockExceededAppend(t *testing.T) { + ruleLimit := testAccSecurityGroupRulesPerGroupLimitFromEnv() -func testAccCheckSecurityGroupAndInstanceDestroy(s *terraform.State) error { - err := testAccCheckInstanceDestroy(s) - if err != nil { - return err - } - return testAccCheckSecurityGroupDestroy(s) -} + var group ec2.SecurityGroup + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_security_group.test" -func testAccCheckSecurityGroupDestroy(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).EC2Conn + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + ProviderFactories: acctest.ProviderFactories, + CheckDestroy: testAccCheckSecurityGroupDestroy, + Steps: []resource.TestStep{ + // create a valid SG just under the limit + { + Config: testAccVPCSecurityGroupConfig_cidrBlockRuleLimit(rName, 0, ruleLimit), + Check: resource.ComposeTestCheckFunc( + testAccCheckSecurityGroupExists(resourceName, &group), + testAccCheckSecurityGroupRuleCount(&group, 0, 1), + ), + }, + // append a rule to step over the limit + { + Config: testAccVPCSecurityGroupConfig_cidrBlockRuleLimit(rName, 0, ruleLimit+1), + ExpectError: regexp.MustCompile("RulesPerSecurityGroupLimitExceeded"), + }, + { + PreConfig: func() { + // should have the original cidr blocks still in 1 rule + err := testSecurityGroupRuleCount(aws.StringValue(group.GroupId), 0, 1) + if err != nil { + t.Fatalf("PreConfig check failed: %s", err) + } - for _, rs := range s.RootModule().Resources { - if rs.Type != "aws_security_group" { - continue - } + id := aws.StringValue(group.GroupId) - _, err := tfec2.FindSecurityGroupByID(conn, rs.Primary.ID) - if tfresource.NotFound(err) { - continue - } - if err != nil { - return err - } + conn := acctest.Provider.Meta().(*conns.AWSClient).EC2Conn - return fmt.Errorf("Security Group (%s) still exists.", rs.Primary.ID) - } + match, err := tfec2.FindSecurityGroupByID(conn, id) + if tfresource.NotFound(err) { + t.Fatalf("PreConfig check failed: Security Group (%s) not found: %s", id, err) + } + if err != nil { + t.Fatalf("PreConfig check failed: %s", err) + } - return nil + if cidrCount := len(match.IpPermissionsEgress[0].IpRanges); cidrCount != ruleLimit { + t.Fatalf("PreConfig check failed: rule does not have previous IP ranges, has %d", cidrCount) + } + }, + // running the original config again now should restore the rules + Config: testAccVPCSecurityGroupConfig_cidrBlockRuleLimit(rName, 0, ruleLimit), + Check: resource.ComposeTestCheckFunc( + testAccCheckSecurityGroupExists(resourceName, &group), + testAccCheckSecurityGroupRuleCount(&group, 0, 1), + ), + }, + }, + }) } -func testAccCheckSecurityGroupClassicDestroy(s *terraform.State) error { - conn := acctest.ProviderEC2Classic.Meta().(*conns.AWSClient).EC2Conn +func TestAccVPCSecurityGroup_ruleLimitExceededPrepend(t *testing.T) { + ruleLimit := testAccSecurityGroupRulesPerGroupLimitFromEnv() - for _, rs := range s.RootModule().Resources { - if rs.Type != "aws_security_group" { - continue - } - - _, err := tfec2.FindSecurityGroupByID(conn, rs.Primary.ID) - if tfresource.NotFound(err) { - continue - } - if err != nil { - return err - } - - return fmt.Errorf("Security Group (%s) still exists.", rs.Primary.ID) - } - - return nil -} - -func testAccCheckSecurityGroupExists(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 := acctest.Provider.Meta().(*conns.AWSClient).EC2Conn - - sg, err := tfec2.FindSecurityGroupByID(conn, rs.Primary.ID) - if tfresource.NotFound(err) { - return fmt.Errorf("Security Group (%s) not found: %w", rs.Primary.ID, err) - } - if err != nil { - return err - } - - *group = *sg - - return nil - } -} - -func testAccCheckSecurityGroupClassicExists(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 := acctest.ProviderEC2Classic.Meta().(*conns.AWSClient).EC2Conn - - sg, err := tfec2.FindSecurityGroupByID(conn, rs.Primary.ID) - if tfresource.NotFound(err) { - return fmt.Errorf("Security Group (%s) not found: %w", rs.Primary.ID, err) - } - if err != nil { - return err - } - - *group = *sg - - return nil - } -} - -func testAccCheckSecurityGroupAttributes(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 *group.GroupName != "terraform_acceptance_test_example" { - return fmt.Errorf("Bad name: %s", *group.GroupName) - } - - if *group.Description != "Used in the terraform acceptance tests" { - return fmt.Errorf("Bad description: %s", *group.Description) - } - - 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 testAccCheckSecurityGroupAttributesNegOneProtocol(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 *group.GroupName != "terraform_acceptance_test_example" { - return fmt.Errorf("Bad name: %s", *group.GroupName) - } - - if *group.Description != "Used in the terraform acceptance tests" { - return fmt.Errorf("Bad description: %s", *group.Description) - } - - 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 - } -} - -// testAccSecurityGroupRulesPerGroupLimitFromEnv returns security group rules per group limit -// Currently this information is not available from any EC2 or Trusted Advisor API -// Prefers the EC2_SECURITY_GROUP_RULES_PER_GROUP_LIMIT environment variable or defaults to 50 -func testAccSecurityGroupRulesPerGroupLimitFromEnv() int { - const defaultLimit = 50 - const envVar = "EC2_SECURITY_GROUP_RULES_PER_GROUP_LIMIT" - - envLimitStr := os.Getenv(envVar) - if envLimitStr == "" { - return defaultLimit - } - envLimitInt, err := strconv.Atoi(envLimitStr) - if err != nil { - log.Printf("[WARN] Error converting %q environment variable value %q to integer: %s", envVar, envLimitStr, err) - return defaultLimit - } - if envLimitInt <= 50 { - return defaultLimit - } - return envLimitInt -} - -func testAccCheckSecurityGroupSGandCIDRAttributes(group *ec2.SecurityGroup) resource.TestCheckFunc { - return func(s *terraform.State) error { - if *group.GroupName != "terraform_acceptance_test_example" { - return fmt.Errorf("Bad name: %s", *group.GroupName) - } - - if *group.Description != "Used in the terraform acceptance tests" { - return fmt.Errorf("Bad description: %s", *group.Description) - } - - 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 testAccCheckSecurityGroupEgressPrefixListAttributes(group *ec2.SecurityGroup) resource.TestCheckFunc { - return func(s *terraform.State) error { - if *group.GroupName != "terraform_acceptance_test_prefix_list_egress" { - return fmt.Errorf("Bad name: %s", *group.GroupName) - } - if *group.Description != "Used in the terraform acceptance tests" { - return fmt.Errorf("Bad description: %s", *group.Description) - } - 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 testAccCheckSecurityGroupIngressPrefixListAttributes(group *ec2.SecurityGroup) resource.TestCheckFunc { - return func(s *terraform.State) error { - if *group.GroupName != "terraform_acceptance_test_prefix_list_ingress" { - return fmt.Errorf("Bad name: %s", *group.GroupName) - } - if *group.Description != "Used in the terraform acceptance tests" { - return fmt.Errorf("Bad description: %s", *group.Description) - } - if len(group.IpPermissions) == 0 { - return fmt.Errorf("No IPPerms") - } - if len(group.IpPermissions) != 1 { - return fmt.Errorf("Expected 1 rule, got %d", len(group.IpPermissions)) - } - - p := group.IpPermissions[0] - - if len(p.PrefixListIds) != 1 { - return fmt.Errorf("Expected 1 prefix list, got %d", len(p.PrefixListIds)) - } - - return nil - } -} - -func testAccCheckSecurityGroupAttributesChanged(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"), - }, - }, - }, - } - - if *group.GroupName != "terraform_acceptance_test_example" { - return fmt.Errorf("Bad name: %s", *group.GroupName) - } - - if *group.Description != "Used in the terraform acceptance tests" { - return fmt.Errorf("Bad description: %s", *group.Description) - } - - // 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 len(group.IpPermissions[1].IpRanges) > 1 { - if *group.IpPermissions[1].IpRanges[0].CidrIp != "0.0.0.0/0" { - group.IpPermissions[1].IpRanges[0], group.IpPermissions[1].IpRanges[1] = - group.IpPermissions[1].IpRanges[1], group.IpPermissions[1].IpRanges[0] - } - } - - 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 testAccCheckSecurityGroupExistsWithoutDefault(n string) 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 := acctest.Provider.Meta().(*conns.AWSClient).EC2Conn - - group, err := tfec2.FindSecurityGroupByID(conn, rs.Primary.ID) - if tfresource.NotFound(err) { - return fmt.Errorf("Security Group (%s) not found: %w", rs.Primary.ID, err) - } - if err != nil { - return err - } - - if len(group.IpPermissionsEgress) != 1 { - return fmt.Errorf("Security Group should have only 1 egress rule, got %d", len(group.IpPermissionsEgress)) - } - - return nil - } -} - -func TestAccVPCSecurityGroup_failWithDiffMismatch(t *testing.T) { - var group ec2.SecurityGroup - - resourceName := "aws_security_group.nat" + var group ec2.SecurityGroup + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_security_group.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -2526,23 +2179,43 @@ func TestAccVPCSecurityGroup_failWithDiffMismatch(t *testing.T) { ProviderFactories: acctest.ProviderFactories, CheckDestroy: testAccCheckSecurityGroupDestroy, Steps: []resource.TestStep{ + // create a valid SG just under the limit { - Config: testAccVPCSecurityGroupConfig_failDiffMismatch, + Config: testAccVPCSecurityGroupConfig_ruleLimit(rName, 0, ruleLimit), Check: resource.ComposeTestCheckFunc( testAccCheckSecurityGroupExists(resourceName, &group), - resource.TestCheckResourceAttr(resourceName, "egress.#", "0"), - resource.TestCheckResourceAttr(resourceName, "ingress.#", "2"), + testAccCheckSecurityGroupRuleCount(&group, 0, ruleLimit), + ), + }, + // prepend a rule to step over the limit + { + Config: testAccVPCSecurityGroupConfig_ruleLimit(rName, 1, ruleLimit+1), + ExpectError: regexp.MustCompile("RulesPerSecurityGroupLimitExceeded"), + }, + { + PreConfig: func() { + // should have the original rules still (limit - 1 because of the shift) + err := testSecurityGroupRuleCount(aws.StringValue(group.GroupId), 0, ruleLimit-1) + if err != nil { + t.Fatalf("PreConfig check failed: %s", err) + } + }, + // running the original config again now should restore the rules + Config: testAccVPCSecurityGroupConfig_ruleLimit(rName, 0, ruleLimit), + Check: resource.ComposeTestCheckFunc( + testAccCheckSecurityGroupExists(resourceName, &group), + testAccCheckSecurityGroupRuleCount(&group, 0, ruleLimit), ), }, }, }) } -func TestAccVPCSecurityGroup_ruleLimitExceededAppend(t *testing.T) { +func TestAccVPCSecurityGroup_ruleLimitExceededAllNew(t *testing.T) { ruleLimit := testAccSecurityGroupRulesPerGroupLimitFromEnv() var group ec2.SecurityGroup - + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_security_group.test" resource.ParallelTest(t, resource.TestCase{ @@ -2553,43 +2226,39 @@ func TestAccVPCSecurityGroup_ruleLimitExceededAppend(t *testing.T) { Steps: []resource.TestStep{ // create a valid SG just under the limit { - Config: testAccVPCSecurityGroupConfig_ruleLimit(0, ruleLimit), + Config: testAccVPCSecurityGroupConfig_ruleLimit(rName, 0, ruleLimit), Check: resource.ComposeTestCheckFunc( testAccCheckSecurityGroupExists(resourceName, &group), testAccCheckSecurityGroupRuleCount(&group, 0, ruleLimit), - resource.TestCheckResourceAttr(resourceName, "egress.#", strconv.Itoa(ruleLimit)), ), }, - // append a rule to step over the limit + // add a rule to step over the limit with entirely new rules { - Config: testAccVPCSecurityGroupConfig_ruleLimit(0, ruleLimit+1), + Config: testAccVPCSecurityGroupConfig_ruleLimit(rName, 100, ruleLimit+1), ExpectError: regexp.MustCompile("RulesPerSecurityGroupLimitExceeded"), }, { + // all the rules should have been revoked and the add failed PreConfig: func() { - // should have the original rules still - err := testSecurityGroupRuleCount(aws.StringValue(group.GroupId), 0, ruleLimit) + err := testSecurityGroupRuleCount(aws.StringValue(group.GroupId), 0, 0) if err != nil { t.Fatalf("PreConfig check failed: %s", err) } }, // running the original config again now should restore the rules - Config: testAccVPCSecurityGroupConfig_ruleLimit(0, ruleLimit), + Config: testAccVPCSecurityGroupConfig_ruleLimit(rName, 0, ruleLimit), Check: resource.ComposeTestCheckFunc( testAccCheckSecurityGroupExists(resourceName, &group), testAccCheckSecurityGroupRuleCount(&group, 0, ruleLimit), - resource.TestCheckResourceAttr(resourceName, "egress.#", strconv.Itoa(ruleLimit)), ), }, }, }) } -func TestAccVPCSecurityGroup_ruleLimitCIDRBlockExceededAppend(t *testing.T) { - ruleLimit := testAccSecurityGroupRulesPerGroupLimitFromEnv() - +func TestAccVPCSecurityGroup_rulesDropOnError(t *testing.T) { var group ec2.SecurityGroup - + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_security_group.test" resource.ParallelTest(t, resource.TestCase{ @@ -2598,174 +2267,242 @@ func TestAccVPCSecurityGroup_ruleLimitCIDRBlockExceededAppend(t *testing.T) { ProviderFactories: acctest.ProviderFactories, CheckDestroy: testAccCheckSecurityGroupDestroy, Steps: []resource.TestStep{ - // create a valid SG just under the limit + // Create a valid security group with some rules and make sure it exists { - Config: testAccVPCSecurityGroupConfig_cidrBlockRuleLimit(0, ruleLimit), + Config: testAccVPCSecurityGroupConfig_rulesDropOnErrorInit(rName), Check: resource.ComposeTestCheckFunc( testAccCheckSecurityGroupExists(resourceName, &group), - testAccCheckSecurityGroupRuleCount(&group, 0, 1), ), }, - // append a rule to step over the limit + // Add a bad rule to trigger API error { - Config: testAccVPCSecurityGroupConfig_cidrBlockRuleLimit(0, ruleLimit+1), - ExpectError: regexp.MustCompile("RulesPerSecurityGroupLimitExceeded"), + Config: testAccVPCSecurityGroupConfig_rulesDropOnErrorAddBadRule(rName), + ExpectError: regexp.MustCompile("InvalidGroupId.Malformed"), }, + // All originally added rules must survive. This will return non-empty plan if anything changed. { - PreConfig: func() { - // should have the original cidr blocks still in 1 rule - err := testSecurityGroupRuleCount(aws.StringValue(group.GroupId), 0, 1) - if err != nil { - t.Fatalf("PreConfig check failed: %s", err) - } + Config: testAccVPCSecurityGroupConfig_rulesDropOnErrorInit(rName), + PlanOnly: true, + }, + }, + }) +} - id := aws.StringValue(group.GroupId) +// cycleIPPermForGroup returns an IpPermission struct with a configured +// UserIdGroupPair for the groupid given. Used in +// TestAccAWSSecurityGroup_forceRevokeRules_should_fail to create a cyclic rule +// between 2 security groups +func cycleIPPermForGroup(groupId string) *ec2.IpPermission { + var perm ec2.IpPermission + perm.FromPort = aws.Int64(0) + perm.ToPort = aws.Int64(0) + perm.IpProtocol = aws.String("icmp") + perm.UserIdGroupPairs = make([]*ec2.UserIdGroupPair, 1) + perm.UserIdGroupPairs[0] = &ec2.UserIdGroupPair{ + GroupId: aws.String(groupId), + } + return &perm +} - conn := acctest.Provider.Meta().(*conns.AWSClient).EC2Conn +// testAddRuleCycle returns a TestCheckFunc to use at the end of a test, such +// that a Security Group Rule cyclic dependency will be created between the two +// Security Groups. A companion function, testRemoveRuleCycle, will undo this. +func testAddRuleCycle(primary, secondary *ec2.SecurityGroup) resource.TestCheckFunc { + return func(s *terraform.State) error { + if primary.GroupId == nil { + return fmt.Errorf("Primary SG not set for TestAccAWSSecurityGroup_forceRevokeRules_should_fail") + } + if secondary.GroupId == nil { + return fmt.Errorf("Secondary SG not set for TestAccAWSSecurityGroup_forceRevokeRules_should_fail") + } - match, err := tfec2.FindSecurityGroupByID(conn, id) - if tfresource.NotFound(err) { - t.Fatalf("PreConfig check failed: Security Group (%s) not found: %s", id, err) - } - if err != nil { - t.Fatalf("PreConfig check failed: %s", err) - } + conn := acctest.Provider.Meta().(*conns.AWSClient).EC2Conn - if cidrCount := len(match.IpPermissionsEgress[0].IpRanges); cidrCount != ruleLimit { - t.Fatalf("PreConfig check failed: rule does not have previous IP ranges, has %d", cidrCount) - } - }, - // running the original config again now should restore the rules - Config: testAccVPCSecurityGroupConfig_cidrBlockRuleLimit(0, ruleLimit), - Check: resource.ComposeTestCheckFunc( - testAccCheckSecurityGroupExists(resourceName, &group), - testAccCheckSecurityGroupRuleCount(&group, 0, 1), - ), - }, - }, - }) + // cycle from primary to secondary + perm1 := cycleIPPermForGroup(aws.StringValue(secondary.GroupId)) + // cycle from secondary to primary + perm2 := cycleIPPermForGroup(aws.StringValue(primary.GroupId)) + + req1 := &ec2.AuthorizeSecurityGroupEgressInput{ + GroupId: primary.GroupId, + IpPermissions: []*ec2.IpPermission{perm1}, + } + req2 := &ec2.AuthorizeSecurityGroupEgressInput{ + GroupId: secondary.GroupId, + IpPermissions: []*ec2.IpPermission{perm2}, + } + + var err error + _, err = conn.AuthorizeSecurityGroupEgress(req1) + if err != nil { + return fmt.Errorf("Error authorizing primary security group %s rules: %w", aws.StringValue(primary.GroupId), err) + } + _, err = conn.AuthorizeSecurityGroupEgress(req2) + if err != nil { + return fmt.Errorf("Error authorizing secondary security group %s rules: %w", aws.StringValue(secondary.GroupId), err) + } + return nil + } +} + +// testRemoveRuleCycle removes the cyclic dependency between two security groups +// that was added in testAddRuleCycle +func testRemoveRuleCycle(primary, secondary *ec2.SecurityGroup) resource.TestCheckFunc { + return func(s *terraform.State) error { + if primary.GroupId == nil { + return fmt.Errorf("Primary SG not set for TestAccAWSSecurityGroup_forceRevokeRules_should_fail") + } + if secondary.GroupId == nil { + return fmt.Errorf("Secondary SG not set for TestAccAWSSecurityGroup_forceRevokeRules_should_fail") + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).EC2Conn + for _, sg := range []*ec2.SecurityGroup{primary, secondary} { + var err error + if sg.IpPermissions != nil { + req := &ec2.RevokeSecurityGroupIngressInput{ + GroupId: sg.GroupId, + IpPermissions: sg.IpPermissions, + } + + if _, err = conn.RevokeSecurityGroupIngress(req); err != nil { + return fmt.Errorf("Error revoking default ingress rule for Security Group in testRemoveCycle (%s): %w", aws.StringValue(primary.GroupId), err) + } + } + + if sg.IpPermissionsEgress != nil { + req := &ec2.RevokeSecurityGroupEgressInput{ + GroupId: sg.GroupId, + IpPermissions: sg.IpPermissionsEgress, + } + + if _, err = conn.RevokeSecurityGroupEgress(req); err != nil { + return fmt.Errorf("Error revoking default egress rule for Security Group in testRemoveCycle (%s): %w", aws.StringValue(sg.GroupId), err) + } + } + } + return nil + } +} + +func testAccCheckSecurityGroupDestroy(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).EC2Conn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_security_group" { + continue + } + + _, err := tfec2.FindSecurityGroupByID(conn, rs.Primary.ID) + + if tfresource.NotFound(err) { + continue + } + + if err != nil { + return err + } + + return fmt.Errorf("VPC Security Group (%s) still exists.", rs.Primary.ID) + } + + return nil +} + +func testAccCheckSecurityGroupEC2ClassicDestroy(s *terraform.State) error { // nosemgrep:ec2-in-func-name + conn := acctest.ProviderEC2Classic.Meta().(*conns.AWSClient).EC2Conn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_security_group" { + continue + } + + _, err := tfec2.FindSecurityGroupByID(conn, rs.Primary.ID) + + if tfresource.NotFound(err) { + continue + } + + if err != nil { + return err + } + + return fmt.Errorf("EC2 Classic Security Group (%s) still exists.", rs.Primary.ID) + } + + return nil } -func TestAccVPCSecurityGroup_ruleLimitExceededPrepend(t *testing.T) { - ruleLimit := testAccSecurityGroupRulesPerGroupLimitFromEnv() +func testAccCheckSecurityGroupExists(n string, v *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) + } - var group ec2.SecurityGroup + if rs.Primary.ID == "" { + return fmt.Errorf("No VPC Security Group ID is set") + } - resourceName := "aws_security_group.test" + conn := acctest.Provider.Meta().(*conns.AWSClient).EC2Conn - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t) }, - ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), - ProviderFactories: acctest.ProviderFactories, - CheckDestroy: testAccCheckSecurityGroupDestroy, - Steps: []resource.TestStep{ - // create a valid SG just under the limit - { - Config: testAccVPCSecurityGroupConfig_ruleLimit(0, ruleLimit), - Check: resource.ComposeTestCheckFunc( - testAccCheckSecurityGroupExists(resourceName, &group), - testAccCheckSecurityGroupRuleCount(&group, 0, ruleLimit), - ), - }, - // prepend a rule to step over the limit - { - Config: testAccVPCSecurityGroupConfig_ruleLimit(1, ruleLimit+1), - ExpectError: regexp.MustCompile("RulesPerSecurityGroupLimitExceeded"), - }, - { - PreConfig: func() { - // should have the original rules still (limit - 1 because of the shift) - err := testSecurityGroupRuleCount(aws.StringValue(group.GroupId), 0, ruleLimit-1) - if err != nil { - t.Fatalf("PreConfig check failed: %s", err) - } - }, - // running the original config again now should restore the rules - Config: testAccVPCSecurityGroupConfig_ruleLimit(0, ruleLimit), - Check: resource.ComposeTestCheckFunc( - testAccCheckSecurityGroupExists(resourceName, &group), - testAccCheckSecurityGroupRuleCount(&group, 0, ruleLimit), - ), - }, - }, - }) + output, err := tfec2.FindSecurityGroupByID(conn, rs.Primary.ID) + + if err != nil { + return err + } + + *v = *output + + return nil + } } -func TestAccVPCSecurityGroup_ruleLimitExceededAllNew(t *testing.T) { - ruleLimit := testAccSecurityGroupRulesPerGroupLimitFromEnv() +func testAccCheckSecurityGroupEC2ClassicExists(n string, v *ec2.SecurityGroup) resource.TestCheckFunc { // nosemgrep:ec2-in-func-name + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } - var group ec2.SecurityGroup + if rs.Primary.ID == "" { + return fmt.Errorf("No EC2 Classic Security Group ID is set") + } - resourceName := "aws_security_group.test" + conn := acctest.ProviderEC2Classic.Meta().(*conns.AWSClient).EC2Conn - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t) }, - ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), - ProviderFactories: acctest.ProviderFactories, - CheckDestroy: testAccCheckSecurityGroupDestroy, - Steps: []resource.TestStep{ - // create a valid SG just under the limit - { - Config: testAccVPCSecurityGroupConfig_ruleLimit(0, ruleLimit), - Check: resource.ComposeTestCheckFunc( - testAccCheckSecurityGroupExists(resourceName, &group), - testAccCheckSecurityGroupRuleCount(&group, 0, ruleLimit), - ), - }, - // add a rule to step over the limit with entirely new rules - { - Config: testAccVPCSecurityGroupConfig_ruleLimit(100, ruleLimit+1), - ExpectError: regexp.MustCompile("RulesPerSecurityGroupLimitExceeded"), - }, - { - // all the rules should have been revoked and the add failed - PreConfig: func() { - err := testSecurityGroupRuleCount(aws.StringValue(group.GroupId), 0, 0) - if err != nil { - t.Fatalf("PreConfig check failed: %s", err) - } - }, - // running the original config again now should restore the rules - Config: testAccVPCSecurityGroupConfig_ruleLimit(0, ruleLimit), - Check: resource.ComposeTestCheckFunc( - testAccCheckSecurityGroupExists(resourceName, &group), - testAccCheckSecurityGroupRuleCount(&group, 0, ruleLimit), - ), - }, - }, - }) -} + output, err := tfec2.FindSecurityGroupByID(conn, rs.Primary.ID) -func TestAccVPCSecurityGroup_rulesDropOnError(t *testing.T) { - var group ec2.SecurityGroup + if err != nil { + return err + } - resourceName := "aws_security_group.test" + *v = *output - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t) }, - ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), - ProviderFactories: acctest.ProviderFactories, - CheckDestroy: testAccCheckSecurityGroupDestroy, - Steps: []resource.TestStep{ - // Create a valid security group with some rules and make sure it exists - { - Config: testAccVPCSecurityGroupConfig_rulesDropOnErrorInit, - Check: resource.ComposeTestCheckFunc( - testAccCheckSecurityGroupExists(resourceName, &group), - ), - }, - // Add a bad rule to trigger API error - { - Config: testAccVPCSecurityGroupConfig_rulesDropOnErrorAddBadRule, - ExpectError: regexp.MustCompile("InvalidGroupId.Malformed"), - }, - // All originally added rules must survive. This will return non-empty plan if anything changed. - { - Config: testAccVPCSecurityGroupConfig_rulesDropOnErrorInit, - PlanOnly: true, - }, - }, - }) + return nil + } +} + +// testAccSecurityGroupRulesPerGroupLimitFromEnv returns security group rules per group limit +// Currently this information is not available from any EC2 or Trusted Advisor API +// Prefers the EC2_SECURITY_GROUP_RULES_PER_GROUP_LIMIT environment variable or defaults to 50 +func testAccSecurityGroupRulesPerGroupLimitFromEnv() int { + const defaultLimit = 50 + const envVar = "EC2_SECURITY_GROUP_RULES_PER_GROUP_LIMIT" + + envLimitStr := os.Getenv(envVar) + if envLimitStr == "" { + return defaultLimit + } + envLimitInt, err := strconv.Atoi(envLimitStr) + if err != nil { + log.Printf("[WARN] Error converting %q environment variable value %q to integer: %s", envVar, envLimitStr, err) + return defaultLimit + } + if envLimitInt <= 50 { + return defaultLimit + } + return envLimitInt } func testAccCheckSecurityGroupRuleCount(group *ec2.SecurityGroup, expectedIngressCount, expectedEgressCount int) resource.TestCheckFunc { @@ -2797,7 +2534,108 @@ func testSecurityGroupRuleCount(id string, expectedIngressCount, expectedEgressC return nil } -func testAccVPCSecurityGroupConfig_ruleLimit(egressStartIndex, egressRulesCount int) string { +func testAccVPCSecurityGroupConfig_name(rName string) string { + return fmt.Sprintf(` +resource "aws_vpc" "test" { + cidr_block = "10.0.0.0/16" + + tags = { + Name = %[1]q + } +} + +resource "aws_security_group" "test" { + name = %[1]q + vpc_id = aws_vpc.test.id +} +`, rName) +} + +func testAccVPCSecurityGroupConfig_ec2Classic(rName string) string { // nosemgrep:ec2-in-func-name + return acctest.ConfigCompose(acctest.ConfigEC2ClassicRegionProvider(), fmt.Sprintf(` +resource "aws_security_group" "test" { + name = %[1]q +} +`, rName)) +} + +func testAccVPCSecurityGroupConfig_nameGenerated(rName string) string { + return fmt.Sprintf(` +resource "aws_vpc" "test" { + cidr_block = "10.1.0.0/16" + + tags = { + Name = %[1]q + } +} + +resource "aws_security_group" "test" { + vpc_id = aws_vpc.test.id +} +`, rName) +} + +func testAccVPCSecurityGroupConfig_namePrefix(rName, namePrefix string) string { + return fmt.Sprintf(` +resource "aws_vpc" "test" { + cidr_block = "10.0.0.0/16" + + tags = { + Name = %[1]q + } +} + +resource "aws_security_group" "test" { + name_prefix = %[2]q + vpc_id = aws_vpc.test.id +} +`, rName, namePrefix) +} + +func testAccVPCSecurityGroupConfig_tags1(rName, tagKey1, tagValue1 string) string { + return fmt.Sprintf(` +resource "aws_vpc" "test" { + cidr_block = "10.1.0.0/16" + + tags = { + Name = %[1]q + } +} + +resource "aws_security_group" "test" { + name = %[1]q + vpc_id = aws_vpc.test.id + + tags = { + %[2]q = %[3]q + } +} +`, rName, tagKey1, tagValue1) +} + +func testAccVPCSecurityGroupConfig_tags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { + return fmt.Sprintf(` +resource "aws_vpc" "test" { + cidr_block = "10.1.0.0/16" + + tags = { + Name = %[1]q + } +} + +resource "aws_security_group" "test" { + name = %[1]q + vpc_id = aws_vpc.test.id + + tags = { + %[2]q = %[3]q + %[4]q = %[5]q + } +} +`, rName, tagKey1, tagValue1, tagKey2, tagValue2) +} + +func testAccVPCSecurityGroupConfig_ruleLimit(rName string, egressStartIndex, egressRulesCount int) string { var egressRules strings.Builder for i := egressStartIndex; i < egressRulesCount+egressStartIndex; i++ { fmt.Fprintf(&egressRules, ` @@ -2815,26 +2653,25 @@ resource "aws_vpc" "test" { cidr_block = "10.1.0.0/16" tags = { - Name = "terraform-testacc-security-group-rule-limit" + Name = %[1]q } } resource "aws_security_group" "test" { - name = "terraform_acceptance_test_rule_limit" - description = "Used in the terraform acceptance tests" - vpc_id = aws_vpc.test.id + name = %[1]q + vpc_id = aws_vpc.test.id tags = { - Name = "tf-acc-test" + Name = %[1]q } # egress rules to exhaust the limit - %[1]s + %[2]s } -`, egressRules.String()) +`, rName, egressRules.String()) } -func testAccVPCSecurityGroupConfig_cidrBlockRuleLimit(egressStartIndex, egressRulesCount int) string { +func testAccVPCSecurityGroupConfig_cidrBlockRuleLimit(rName string, egressStartIndex, egressRulesCount int) string { var cidrBlocks strings.Builder for i := egressStartIndex; i < egressRulesCount+egressStartIndex; i++ { fmt.Fprintf(&cidrBlocks, ` @@ -2847,17 +2684,16 @@ resource "aws_vpc" "test" { cidr_block = "10.1.0.0/16" tags = { - Name = "terraform-testacc-security-group-rule-limit" + Name = %[1]q } } resource "aws_security_group" "test" { - name = "terraform_acceptance_test_rule_limit" - description = "Used in the terraform acceptance tests" - vpc_id = aws_vpc.test.id + name = %[1]q + vpc_id = aws_vpc.test.id tags = { - Name = "tf-acc-test" + Name = %[1]q } egress { @@ -2866,26 +2702,26 @@ resource "aws_security_group" "test" { to_port = "80" # cidr_blocks to exhaust the limit cidr_blocks = [ - %s + %[2]s ] } } -`, cidrBlocks.String()) +`, rName, cidrBlocks.String()) } -const testAccVPCSecurityGroupConfig_emptyRuleDescription = ` -resource "aws_vpc" "foo" { +func testAccVPCSecurityGroupConfig_emptyRuleDescription(rName string) string { + return fmt.Sprintf(` +resource "aws_vpc" "test" { cidr_block = "10.1.0.0/16" tags = { - Name = "terraform-testacc-security-group-empty-rule-description" + Name = %[1]q } } resource "aws_security_group" "test" { - name = "terraform_acceptance_test_desc_example" - description = "Used in the terraform acceptance tests" - vpc_id = aws_vpc.foo.id + name = %[1]q + vpc_id = aws_vpc.test.id ingress { protocol = "6" @@ -2904,24 +2740,25 @@ resource "aws_security_group" "test" { } tags = { - Name = "tf-acc-test" + Name = %[1]q } } -` +`, rName) +} -const testAccVPCSecurityGroupConfig_ipv6 = ` -resource "aws_vpc" "foo" { +func testAccVPCSecurityGroupConfig_ipv6(rName string) string { + return fmt.Sprintf(` +resource "aws_vpc" "test" { cidr_block = "10.1.0.0/16" tags = { - Name = "terraform-testacc-security-group-ipv6" + Name = %[1]q } } resource "aws_security_group" "test" { - name = "terraform_acceptance_test_example" - description = "Used in the terraform acceptance tests" - vpc_id = aws_vpc.foo.id + name = %[1]q + vpc_id = aws_vpc.test.id ingress { protocol = "6" @@ -2938,24 +2775,25 @@ resource "aws_security_group" "test" { } tags = { - Name = "tf-acc-test" + Name = %[1]q } } -` +`, rName) +} -const testAccVPCSecurityGroupConfig_basic = ` -resource "aws_vpc" "foo" { +func testAccVPCSecurityGroupConfig_basic(rName string) string { + return fmt.Sprintf(` +resource "aws_vpc" "test" { cidr_block = "10.1.0.0/16" tags = { - Name = "terraform-testacc-security-group" + Name = %[1]q } } resource "aws_security_group" "test" { - name = "terraform_acceptance_test_example" - description = "Used in the terraform acceptance tests" - vpc_id = aws_vpc.foo.id + name = %[1]q + vpc_id = aws_vpc.test.id ingress { protocol = "6" @@ -2963,130 +2801,137 @@ resource "aws_security_group" "test" { to_port = 8000 cidr_blocks = ["10.0.0.0/8"] } + + tags = { + Name = %[1]q + } +} +`, rName) } -` -const testAccVPCSecurityGroupConfig_revokeBaseRemoved = ` -resource "aws_vpc" "sg-race-revoke" { +func testAccVPCSecurityGroupConfig_revokeBaseRemoved(rName string) string { + return fmt.Sprintf(` +resource "aws_vpc" "test" { cidr_block = "10.1.0.0/16" tags = { - Name = "terraform-testacc-security-group-revoke" + Name = %[1]q } } -` +`, rName) +} -const testAccVPCSecurityGroupConfig_revokeBase = ` -resource "aws_vpc" "sg-race-revoke" { +func testAccVPCSecurityGroupConfig_revokeBase(rName string) string { + return fmt.Sprintf(` +resource "aws_vpc" "test" { cidr_block = "10.1.0.0/16" tags = { - Name = "terraform-testacc-security-group-revoke" + Name = %[1]q } } resource "aws_security_group" "primary" { - name = "tf-acc-sg-race-revoke-primary" - description = "Used in the terraform acceptance tests" - vpc_id = aws_vpc.sg-race-revoke.id + name = "%[1]s-primary" + vpc_id = aws_vpc.test.id tags = { - Name = "tf-acc-revoke-test-primary" + Name = %[1]q } } resource "aws_security_group" "secondary" { - name = "tf-acc-sg-race-revoke-secondary" - description = "Used in the terraform acceptance tests" - vpc_id = aws_vpc.sg-race-revoke.id + name = "%[1]s-secondary" + vpc_id = aws_vpc.test.id tags = { - Name = "tf-acc-revoke-test-secondary" + Name = %[1]q } } -` +`, rName) +} -const testAccVPCSecurityGroupConfig_revokeFalse = ` -resource "aws_vpc" "sg-race-revoke" { +func testAccVPCSecurityGroupConfig_revokeFalse(rName string) string { + return fmt.Sprintf(` +resource "aws_vpc" "test" { cidr_block = "10.1.0.0/16" tags = { - Name = "terraform-testacc-security-group-revoke" + Name = %[1]q } } resource "aws_security_group" "primary" { - name = "tf-acc-sg-race-revoke-primary" - description = "Used in the terraform acceptance tests" - vpc_id = aws_vpc.sg-race-revoke.id + name = "%[1]s-primary" + vpc_id = aws_vpc.test.id tags = { - Name = "tf-acc-revoke-test-primary" + Name = %[1]q } revoke_rules_on_delete = false } resource "aws_security_group" "secondary" { - name = "tf-acc-sg-race-revoke-secondary" - description = "Used in the terraform acceptance tests" - vpc_id = aws_vpc.sg-race-revoke.id + name = "%[1]s-secondary" + vpc_id = aws_vpc.test.id tags = { - Name = "tf-acc-revoke-test-secondary" + Name = %[1]q } revoke_rules_on_delete = false } -` +`, rName) +} -const testAccVPCSecurityGroupConfig_revokeTrue = ` -resource "aws_vpc" "sg-race-revoke" { +func testAccVPCSecurityGroupConfig_revokeTrue(rName string) string { + return fmt.Sprintf(` +resource "aws_vpc" "test" { cidr_block = "10.1.0.0/16" tags = { - Name = "terraform-testacc-security-group-revoke" + Name = %[1]q } } resource "aws_security_group" "primary" { - name = "tf-acc-sg-race-revoke-primary" - description = "Used in the terraform acceptance tests" - vpc_id = aws_vpc.sg-race-revoke.id + name = "%[1]s-primary" + vpc_id = aws_vpc.test.id tags = { - Name = "tf-acc-revoke-test-primary" + Name = %[1]q } revoke_rules_on_delete = true } resource "aws_security_group" "secondary" { - name = "tf-acc-sg-race-revoke-secondary" - description = "Used in the terraform acceptance tests" - vpc_id = aws_vpc.sg-race-revoke.id + name = "%[1]s-secondary" + vpc_id = aws_vpc.test.id tags = { - Name = "tf-acc-revoke-test-secondary" + Name = %[1]q } revoke_rules_on_delete = true } -` +`, rName) +} -const testAccVPCSecurityGroupConfig_change = ` -resource "aws_vpc" "foo" { +func testAccVPCSecurityGroupConfig_changed(rName string) string { + return fmt.Sprintf(` +resource "aws_vpc" "test" { cidr_block = "10.1.0.0/16" tags = { - Name = "terraform-testacc-security-group-change" + Name = %[1]q } } resource "aws_security_group" "test" { - name = "terraform_acceptance_test_example" - description = "Used in the terraform acceptance tests" - vpc_id = aws_vpc.foo.id + name = %[1]q + vpc_id = aws_vpc.test.id ingress { protocol = "tcp" @@ -3108,30 +2953,34 @@ resource "aws_security_group" "test" { to_port = 8000 cidr_blocks = ["10.0.0.0/8"] } + + tags = { + Name = %[1]q + } +} +`, rName) } -` -func testAccVPCSecurityGroupConfig_ruleDescription(egressDescription, ingressDescription string) string { +func testAccVPCSecurityGroupConfig_ruleDescription(rName, egressDescription, ingressDescription string) string { return fmt.Sprintf(` -resource "aws_vpc" "foo" { +resource "aws_vpc" "test" { cidr_block = "10.1.0.0/16" tags = { - Name = "terraform-testacc-security-group-description" + Name = %[1]q } } resource "aws_security_group" "test" { - name = "terraform_acceptance_test_example" - description = "Used in the terraform acceptance tests" - vpc_id = aws_vpc.foo.id + name = %[1]q + vpc_id = aws_vpc.test.id ingress { protocol = "6" from_port = 80 to_port = 8000 cidr_blocks = ["10.0.0.0/8"] - description = "%s" + description = %[2]q } egress { @@ -3139,29 +2988,29 @@ resource "aws_security_group" "test" { from_port = 80 to_port = 8000 cidr_blocks = ["10.0.0.0/8"] - description = "%s" + description = %[3]q } tags = { - Name = "tf-acc-test" + Name = %[1]q } } -`, ingressDescription, egressDescription) +`, rName, ingressDescription, egressDescription) } -const testAccVPCSecurityGroupConfig_self = ` -resource "aws_vpc" "foo" { +func testAccVPCSecurityGroupConfig_self(rName string) string { + return fmt.Sprintf(` +resource "aws_vpc" "test" { cidr_block = "10.1.0.0/16" tags = { - Name = "terraform-testacc-security-group-self" + Name = %[1]q } } resource "aws_security_group" "test" { - name = "terraform_acceptance_test_example" - description = "Used in the terraform acceptance tests" - vpc_id = aws_vpc.foo.id + name = %[1]q + vpc_id = aws_vpc.test.id ingress { protocol = "tcp" @@ -3176,22 +3025,27 @@ resource "aws_security_group" "test" { to_port = 8000 cidr_blocks = ["10.0.0.0/8"] } + + tags = { + Name = %[1]q + } +} +`, rName) } -` -const testAccVPCSecurityGroupConfig_vpc = ` -resource "aws_vpc" "foo" { +func testAccVPCSecurityGroupConfig_vpc(rName string) string { + return fmt.Sprintf(` +resource "aws_vpc" "test" { cidr_block = "10.1.0.0/16" tags = { - Name = "terraform-testacc-security-group-vpc" + Name = %[1]q } } resource "aws_security_group" "test" { - name = "terraform_acceptance_test_example" - description = "Used in the terraform acceptance tests" - vpc_id = aws_vpc.foo.id + name = %[1]q + vpc_id = aws_vpc.test.id ingress { protocol = "tcp" @@ -3206,22 +3060,27 @@ resource "aws_security_group" "test" { to_port = 8000 cidr_blocks = ["10.0.0.0/8"] } + + tags = { + Name = %[1]q + } +} +`, rName) } -` -const testAccVPCSecurityGroupConfig_negOneIngress = ` -resource "aws_vpc" "foo" { +func testAccVPCSecurityGroupConfig_vpcNegativeOneIngress(rName string) string { + return fmt.Sprintf(` +resource "aws_vpc" "test" { cidr_block = "10.1.0.0/16" tags = { - Name = "terraform-testacc-security-group-vpc-neg-one-ingress" + Name = %[1]q } } resource "aws_security_group" "test" { - name = "terraform_acceptance_test_example" - description = "Used in the terraform acceptance tests" - vpc_id = aws_vpc.foo.id + name = %[1]q + vpc_id = aws_vpc.test.id ingress { protocol = "-1" @@ -3229,22 +3088,27 @@ resource "aws_security_group" "test" { to_port = 0 cidr_blocks = ["10.0.0.0/8"] } + + tags = { + Name = %[1]q + } +} +`, rName) } -` -const testAccVPCSecurityGroupConfig_protoNumIngress = ` -resource "aws_vpc" "foo" { +func testAccVPCSecurityGroupConfig_vpcProtocolNumberIngress(rName string) string { + return fmt.Sprintf(` +resource "aws_vpc" "test" { cidr_block = "10.1.0.0/16" tags = { - Name = "terraform-testacc-security-group-vpc-proto-num-ingress" + Name = %[1]q } } resource "aws_security_group" "test" { - name = "terraform_acceptance_test_example" - description = "Used in the terraform acceptance tests" - vpc_id = aws_vpc.foo.id + name = %[1]q + vpc_id = aws_vpc.test.id ingress { protocol = "50" @@ -3252,22 +3116,27 @@ resource "aws_security_group" "test" { to_port = 0 cidr_blocks = ["10.0.0.0/8"] } + + tags = { + Name = %[1]q + } +} +`, rName) } -` -const testAccVPCSecurityGroupConfig_multiIngress = ` -resource "aws_vpc" "foo" { +func testAccVPCSecurityGroupConfig_multiIngress(rName string) string { + return fmt.Sprintf(` +resource "aws_vpc" "test" { cidr_block = "10.1.0.0/16" tags = { - Name = "terraform-testacc-security-group-multi-ingress" + Name = %[1]q } } -resource "aws_security_group" "test" { - name = "terraform_acceptance_test_example_1" - description = "Used in the terraform acceptance tests" - vpc_id = aws_vpc.foo.id +resource "aws_security_group" "test1" { + name = "%[1]s-1" + vpc_id = aws_vpc.test.id ingress { protocol = "tcp" @@ -3282,12 +3151,15 @@ resource "aws_security_group" "test" { to_port = 8000 cidr_blocks = ["10.0.0.0/8"] } + + tags = { + Name = %[1]q + } } resource "aws_security_group" "test2" { - name = "terraform_acceptance_test_example_2" - description = "Used in the terraform acceptance tests" - vpc_id = aws_vpc.foo.id + name = "%[1]s-2" + vpc_id = aws_vpc.test.id ingress { protocol = "tcp" @@ -3307,7 +3179,7 @@ resource "aws_security_group" "test2" { protocol = "tcp" from_port = 80 to_port = 8000 - security_groups = [aws_security_group.test.id] + security_groups = [aws_security_group.test1.id] } egress { @@ -3316,85 +3188,27 @@ resource "aws_security_group" "test2" { to_port = 8000 cidr_blocks = ["10.0.0.0/8"] } -} -` - -func testAccVPCSecurityGroupConfig_tags1(rName, tagKey1, tagValue1 string) string { - return fmt.Sprintf(` -resource "aws_vpc" "test" { - cidr_block = "10.1.0.0/16" tags = { Name = %[1]q } } - -resource "aws_security_group" "test" { - name = %[1]q - description = "Used in the terraform acceptance tests" - vpc_id = aws_vpc.test.id - - tags = { - %[2]q = %[3]q - } -} -`, rName, tagKey1, tagValue1) +`, rName) } -func testAccVPCSecurityGroupConfig_tags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { +func testAccVPCSecurityGroupConfig_defaultEgress(rName string) string { return fmt.Sprintf(` resource "aws_vpc" "test" { - cidr_block = "10.1.0.0/16" - - tags = { - Name = %[1]q - } -} - -resource "aws_security_group" "test" { - name = %[1]q - description = "Used in the terraform acceptance tests" - vpc_id = aws_vpc.test.id - - tags = { - %[2]q = %[3]q - %[4]q = %[5]q - } -} -`, rName, tagKey1, tagValue1, tagKey2, tagValue2) -} - -const testAccVPCSecurityGroupConfig_generatedName = ` -resource "aws_vpc" "foo" { - cidr_block = "10.1.0.0/16" - - tags = { - Name = "terraform-testacc-security-group-generated-name" - } -} - -resource "aws_security_group" "test" { - vpc_id = aws_vpc.foo.id - - tags = { - Name = "tf-acc-test" - } -} -` - -const testAccVPCSecurityGroupConfig_defaultEgress = ` -resource "aws_vpc" "tf_sg_egress_test" { cidr_block = "10.0.0.0/16" tags = { - Name = "terraform-testacc-security-group-default-egress" + Name = %[1]q } } resource "aws_security_group" "test" { - name = "terraform_acceptance_test_example_1" - description = "Used in the terraform acceptance tests" - vpc_id = aws_vpc.tf_sg_egress_test.id + name = %[1]q + vpc_id = aws_vpc.test.id egress { protocol = "tcp" @@ -3402,59 +3216,18 @@ resource "aws_security_group" "test" { to_port = 8000 cidr_blocks = ["10.0.0.0/8"] } -} -` - -func testAccVPCSecurityGroupConfig_classic(rName string) string { - return acctest.ConfigCompose( - acctest.ConfigEC2ClassicRegionProvider(), - fmt.Sprintf(` -resource "aws_security_group" "test" { - name = %[1]q - description = "Used in the terraform acceptance tests" -} -`, rName)) -} - -func testAccVPCSecurityGroupConfig_name(name string) string { - return fmt.Sprintf(` -resource "aws_vpc" "test" { - cidr_block = "10.0.0.0/16" - - tags = { - Name = "tf-acc-test-security-group-name" - } -} - -resource "aws_security_group" "test" { - name = %[1]q - vpc_id = aws_vpc.test.id -} -`, name) -} - -func testAccVPCSecurityGroupConfig_namePrefix(namePrefix string) string { - return fmt.Sprintf(` -resource "aws_vpc" "test" { - cidr_block = "10.0.0.0/16" tags = { - Name = "tf-acc-test-security-group-name-prefix" + Name = %[1]q } } - -resource "aws_security_group" "test" { - name_prefix = %[1]q - vpc_id = aws_vpc.test.id -} -`, namePrefix) +`, rName) } -func testAccVPCSecurityGroupConfig_drift() string { +func testAccVPCSecurityGroupConfig_drift(rName string) string { return fmt.Sprintf(` resource "aws_security_group" "test" { - name = "tf_acc_%d" - description = "Used in the terraform acceptance tests" + name = %[1]q ingress { protocol = "tcp" @@ -3471,32 +3244,34 @@ resource "aws_security_group" "test" { } tags = { - Name = "tf-acc-test" + Name = %[1]q } } -`, sdkacctest.RandInt()) +`, rName) } -func testAccVPCSecurityGroupConfig_driftComplex() string { +func testAccVPCSecurityGroupConfig_driftComplex(rName string) string { return fmt.Sprintf(` -resource "aws_vpc" "foo" { +resource "aws_vpc" "test" { cidr_block = "10.1.0.0/16" tags = { - Name = "terraform-testacc-security-group-drift-complex" + Name = %[1]q } } resource "aws_security_group" "test2" { - name = "tf_acc_%d" - description = "Used in the terraform acceptance tests" - vpc_id = aws_vpc.foo.id + name = "%[1]s-2" + vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } } -resource "aws_security_group" "test" { - name = "tf_acc_%d" - description = "Used in the terraform acceptance tests" - vpc_id = aws_vpc.foo.id +resource "aws_security_group" "test1" { + name = "%[1]s-1" + vpc_id = aws_vpc.test.id ingress { protocol = "tcp" @@ -3541,17 +3316,14 @@ resource "aws_security_group" "test" { } tags = { - Name = "tf-acc-test" + Name = %[1]q } } -`, sdkacctest.RandInt(), sdkacctest.RandInt()) +`, rName) } const testAccVPCSecurityGroupConfig_invalidIngressCIDR = ` resource "aws_security_group" "test" { - name = "testing-foo" - description = "foo-testing" - ingress { from_port = 0 to_port = 0 @@ -3563,9 +3335,6 @@ resource "aws_security_group" "test" { const testAccVPCSecurityGroupConfig_invalidEgressCIDR = ` resource "aws_security_group" "test" { - name = "testing-foo" - description = "foo-testing" - egress { from_port = 0 to_port = 0 @@ -3577,9 +3346,6 @@ resource "aws_security_group" "test" { const testAccVPCSecurityGroupConfig_invalidIPv6IngressCIDR = ` resource "aws_security_group" "test" { - name = "testing-foo" - description = "foo-testing" - ingress { from_port = 0 to_port = 0 @@ -3591,9 +3357,6 @@ resource "aws_security_group" "test" { const testAccVPCSecurityGroupConfig_invalidIPv6EgressCIDR = ` resource "aws_security_group" "test" { - name = "testing-foo" - description = "foo-testing" - egress { from_port = 0 to_port = 0 @@ -3603,45 +3366,50 @@ resource "aws_security_group" "test" { } ` -const testAccVPCSecurityGroupConfig_combinedCIDRAndGroups = ` -resource "aws_vpc" "foo" { +func testAccVPCSecurityGroupConfig_combinedCIDRAndGroups(rName string) string { + return fmt.Sprintf(` +resource "aws_vpc" "test" { cidr_block = "10.1.0.0/16" tags = { - Name = "terraform-testacc-security-group-combine-rand-groups" + Name = %[1]q } } -resource "aws_security_group" "two" { - name = "tf-test-1" - vpc_id = aws_vpc.foo.id +resource "aws_security_group" "test2" { + name = "%[1]s-2" + vpc_id = aws_vpc.test.id tags = { - Name = "tf-test-1" + Name = %[1]q } } -resource "aws_security_group" "one" { - name = "tf-test-2" - vpc_id = aws_vpc.foo.id +resource "aws_security_group" "test3" { + name = "%[1]s-3" + vpc_id = aws_vpc.test.id tags = { - Name = "tf-test-w" + Name = %[1]q } } -resource "aws_security_group" "three" { - name = "tf-test-3" - vpc_id = aws_vpc.foo.id +resource "aws_security_group" "test4" { + name = "%[1]s-4" + vpc_id = aws_vpc.test.id tags = { - Name = "tf-test-3" + Name = %[1]q } } -resource "aws_security_group" "test" { - name = "tf-mix-test" - vpc_id = aws_vpc.foo.id +resource "aws_security_group" "test1" { + name = "%[1]s-1" + vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } ingress { from_port = 80 @@ -3650,41 +3418,41 @@ resource "aws_security_group" "test" { 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, + aws_security_group.test2.id, + aws_security_group.test3.id, + aws_security_group.test4.id, ] } - - tags = { - Name = "tf-mix-test" - } } -` +`, rName) +} -const testAccVPCSecurityGroupConfig_ingressCIDRAndSGs = ` -resource "aws_vpc" "foo" { +func testAccVPCSecurityGroupConfig_ingressWithCIDRAndSGs(rName string) string { + return fmt.Sprintf(` +resource "aws_vpc" "test" { cidr_block = "10.1.0.0/16" tags = { - Name = "terraform-testacc-security-group-ingress-w-cidr-and-sg" + Name = %[1]q } } resource "aws_security_group" "test2" { - name = "tf_other_acc_tests" - description = "Used in the terraform acceptance tests" - vpc_id = aws_vpc.foo.id + name = "%[1]s-2" + vpc_id = aws_vpc.test.id tags = { - Name = "tf-acc-test" + Name = %[1]q } } -resource "aws_security_group" "test" { - name = "terraform_acceptance_test_example" - description = "Used in the terraform acceptance tests" - vpc_id = aws_vpc.foo.id +resource "aws_security_group" "test1" { + name = "%[1]s-1" + vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } ingress { protocol = "tcp" @@ -3710,29 +3478,22 @@ resource "aws_security_group" "test" { to_port = 8000 cidr_blocks = ["10.0.0.0/8"] } - - tags = { - Name = "tf-acc-test" - } } -` +`, rName) +} -func testAccVPCSecurityGroupConfig_ingressCIDRAndSGsClassic(rName string) string { - return acctest.ConfigCompose( - acctest.ConfigEC2ClassicRegionProvider(), - fmt.Sprintf(` +func testAccVPCSecurityGroupConfig_ingressWithCIDRAndSGsEC2Classic(rName string) string { // nosemgrep:ec2-in-func-name + return acctest.ConfigCompose(acctest.ConfigEC2ClassicRegionProvider(), fmt.Sprintf(` resource "aws_security_group" "test2" { - name = "%[1]s-2" - description = "Used in the terraform acceptance tests" + name = "%[1]s-2" tags = { - Name = "tf-acc-test" + Name = %[1]q } } -resource "aws_security_group" "test" { - name = %[1]q - description = "Used in the terraform acceptance tests" +resource "aws_security_group" "test1" { + name = "%[1]s-1" ingress { protocol = "tcp" @@ -3753,7 +3514,7 @@ resource "aws_security_group" "test" { } tags = { - Name = "tf-acc-test" + Name = %[1]q } } `, rName)) @@ -3761,67 +3522,79 @@ resource "aws_security_group" "test" { // fails to apply in one pass with the error "diffs didn't match during apply" // GH-2027 -const testAccVPCSecurityGroupConfig_failDiffMismatch = ` +func testAccVPCSecurityGroupConfig_failWithDiffMismatch(rName string) string { + return fmt.Sprintf(` resource "aws_vpc" "main" { cidr_block = "10.0.0.0/16" tags = { - Name = "terraform-testacc-security-group-fail-w-diff-mismatch" + Name = %[1]q } } -resource "aws_security_group" "ssh_base" { - name = "test-ssh-base" +resource "aws_security_group" "test3" { vpc_id = aws_vpc.main.id -} + name = "%[1]s-3" -resource "aws_security_group" "jump" { - name = "test-jump" - vpc_id = aws_vpc.main.id + tags = { + Name = %[1]q + } } -resource "aws_security_group" "provision" { - name = "test-provision" +resource "aws_security_group" "test2" { vpc_id = aws_vpc.main.id + name = "%[1]s-2" + + tags = { + Name = %[1]q + } } -resource "aws_security_group" "nat" { - vpc_id = aws_vpc.main.id - name = "nat" - description = "For nat servers " +resource "aws_security_group" "test1" { + vpc_id = aws_vpc.main.id + name = "%[1]s-1" ingress { from_port = 22 to_port = 22 protocol = "tcp" - security_groups = [aws_security_group.jump.id] + security_groups = [aws_security_group.test2.id] } ingress { from_port = 22 to_port = 22 protocol = "tcp" - security_groups = [aws_security_group.provision.id] + security_groups = [aws_security_group.test3.id] + } + + tags = { + Name = %[1]q } } -` +`, rName) +} -const testAccVPCSecurityGroupConfig_allowAll = ` -resource "aws_vpc" "foo" { +func testAccVPCSecurityGroupConfig_allowAll(rName string) string { + return fmt.Sprintf(` +resource "aws_vpc" "test" { cidr_block = "10.1.0.0/16" tags = { - Name = "terraform-testacc-security-group-allow-all" + Name = %[1]q } } resource "aws_security_group" "test" { - name = "allow_all" - description = "Allow all inbound traffic" - vpc_id = aws_vpc.foo.id + name = %[1]q + vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } } -resource "aws_security_group_rule" "allow_all" { +resource "aws_security_group_rule" "allow_all-1" { type = "ingress" from_port = 0 to_port = 65535 @@ -3831,7 +3604,7 @@ resource "aws_security_group_rule" "allow_all" { security_group_id = aws_security_group.test.id } -resource "aws_security_group_rule" "allow_all-1" { +resource "aws_security_group_rule" "allow_all-2" { type = "ingress" from_port = 65534 to_port = 65535 @@ -3840,30 +3613,44 @@ resource "aws_security_group_rule" "allow_all-1" { self = true security_group_id = aws_security_group.test.id } -` +`, rName) +} -const testAccVPCSecurityGroupConfig_source = ` -resource "aws_vpc" "foo" { +func testAccVPCSecurityGroupConfig_sourceSecurityGroup(rName string) string { + return fmt.Sprintf(` +resource "aws_vpc" "test" { cidr_block = "10.1.0.0/16" tags = { - Name = "terraform-testacc-security-group-source-sg" + Name = %[1]q } } resource "aws_security_group" "test" { - name = "test group 1" - vpc_id = aws_vpc.foo.id + name = "%[1]s-1" + vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } } resource "aws_security_group" "test2" { - name = "test group 2" - vpc_id = aws_vpc.foo.id + name = "%[1]s-2" + vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } } resource "aws_security_group" "test3" { - name = "test group 3" - vpc_id = aws_vpc.foo.id + name = "%[1]s-3" + vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } } resource "aws_security_group_rule" "allow_test2" { @@ -3885,25 +3672,35 @@ resource "aws_security_group_rule" "allow_test3" { source_security_group_id = aws_security_group.test.id security_group_id = aws_security_group.test3.id } -` +`, rName) +} -const testAccVPCSecurityGroupConfig_ipRangeSGSameRules = ` -resource "aws_vpc" "foo" { +func testAccVPCSecurityGroupConfig_ipRangeAndSecurityGroupWithSameRules(rName string) string { + return fmt.Sprintf(` +resource "aws_vpc" "test" { cidr_block = "10.1.0.0/16" tags = { - Name = "terraform-testacc-security-group-import-ip-range-and-sg" + Name = %[1]q } } resource "aws_security_group" "test" { - name = "test group 1" - vpc_id = aws_vpc.foo.id + name = "%[1]s-1" + vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } } resource "aws_security_group" "test2" { - name = "test group 2" - vpc_id = aws_vpc.foo.id + name = "%[1]s-2" + vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } } resource "aws_security_group_rule" "allow_security_group" { @@ -3935,20 +3732,26 @@ resource "aws_security_group_rule" "allow_ipv6_cidr_block" { ipv6_cidr_blocks = ["::/0"] security_group_id = aws_security_group.test.id } -` +`, rName) +} -const testAccVPCSecurityGroupConfig_ipRangesSameRules = ` -resource "aws_vpc" "foo" { +func testAccVPCSecurityGroupConfig_ipRangesWithSameRules(rName string) string { + return fmt.Sprintf(` +resource "aws_vpc" "test" { cidr_block = "10.1.0.0/16" tags = { - Name = "terraform-testacc-security-group-import-ip-ranges" + Name = %[1]q } } resource "aws_security_group" "test" { - name = "test group 1" - vpc_id = aws_vpc.foo.id + name = %[1]q + vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } } resource "aws_security_group_rule" "allow_cidr_block" { @@ -3970,22 +3773,23 @@ resource "aws_security_group_rule" "allow_ipv6_cidr_block" { ipv6_cidr_blocks = ["::/0"] security_group_id = aws_security_group.test.id } -` +`, rName) +} -const testAccVPCSecurityGroupConfig_ipv4andIPv6Egress = ` -resource "aws_vpc" "foo" { +func testAccVPCSecurityGroupConfig_ipv4andIPv6Egress(rName string) string { + return fmt.Sprintf(` +resource "aws_vpc" "test" { cidr_block = "10.1.0.0/16" assign_generated_ipv6_cidr_block = true tags = { - Name = "terraform-testacc-security-group-ipv4-and-ipv6-egress" + Name = %[1]q } } resource "aws_security_group" "test" { - name = "terraform_acceptance_test_example" - description = "Used in the terraform acceptance tests" - vpc_id = aws_vpc.foo.id + name = %[1]q + vpc_id = aws_vpc.test.id egress { from_port = 0 @@ -4000,28 +3804,42 @@ resource "aws_security_group" "test" { protocol = "-1" ipv6_cidr_blocks = ["::/0"] } + + tags = { + Name = %[1]q + } +} +`, rName) } -` -const testAccVPCSecurityGroupConfig_prefixListEgress = ` +func testAccVPCSecurityGroupConfig_prefixListEgress(rName string) string { + return fmt.Sprintf(` data "aws_region" "current" {} -resource "aws_vpc" "tf_sg_prefix_list_egress_test" { +resource "aws_vpc" "test" { cidr_block = "10.0.0.0/16" tags = { - Name = "terraform-testacc-security-group-prefix-list-egress" + Name = %[1]q } } -resource "aws_route_table" "default" { - vpc_id = aws_vpc.tf_sg_prefix_list_egress_test.id +resource "aws_route_table" "test" { + vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } } resource "aws_vpc_endpoint" "test" { - vpc_id = aws_vpc.tf_sg_prefix_list_egress_test.id + vpc_id = aws_vpc.test.id service_name = "com.amazonaws.${data.aws_region.current.name}.s3" - route_table_ids = [aws_route_table.default.id] + route_table_ids = [aws_route_table.test.id] + + tags = { + Name = %[1]q + } policy = <