Skip to content

Commit 79bb2e8

Browse files
authored
provider/aws: Add Default Security Group Resource (hashicorp#8861)
* Docs for default security group * overrides of default behavior * add special disclaimer * update to support classic environments
1 parent 1bbf1ee commit 79bb2e8

5 files changed

Lines changed: 470 additions & 0 deletions

File tree

builtin/providers/aws/provider.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,7 @@ func Provider() terraform.ResourceProvider {
317317
"aws_s3_bucket_policy": resourceAwsS3BucketPolicy(),
318318
"aws_s3_bucket_object": resourceAwsS3BucketObject(),
319319
"aws_s3_bucket_notification": resourceAwsS3BucketNotification(),
320+
"aws_default_security_group": resourceAwsDefaultSecurityGroup(),
320321
"aws_security_group": resourceAwsSecurityGroup(),
321322
"aws_security_group_rule": resourceAwsSecurityGroupRule(),
322323
"aws_simpledb_domain": resourceAwsSimpleDBDomain(),
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
package aws
2+
3+
import (
4+
"fmt"
5+
"log"
6+
7+
"github.com/aws/aws-sdk-go/aws"
8+
"github.com/aws/aws-sdk-go/service/ec2"
9+
"github.com/hashicorp/errwrap"
10+
"github.com/hashicorp/terraform/helper/schema"
11+
)
12+
13+
func resourceAwsDefaultSecurityGroup() *schema.Resource {
14+
// reuse aws_security_group_rule schema, and methods for READ, UPDATE
15+
dsg := resourceAwsSecurityGroup()
16+
dsg.Create = resourceAwsDefaultSecurityGroupCreate
17+
dsg.Delete = resourceAwsDefaultSecurityGroupDelete
18+
19+
// Descriptions cannot be updated
20+
delete(dsg.Schema, "description")
21+
22+
// name is a computed value for Default Security Groups and cannot be changed
23+
delete(dsg.Schema, "name_prefix")
24+
dsg.Schema["name"] = &schema.Schema{
25+
Type: schema.TypeString,
26+
Computed: true,
27+
}
28+
29+
// We want explicit management of Rules here, so we do not allow them to be
30+
// computed. Instead, an empty config will enforce just that; removal of the
31+
// rules
32+
dsg.Schema["ingress"].Computed = false
33+
dsg.Schema["egress"].Computed = false
34+
return dsg
35+
}
36+
37+
func resourceAwsDefaultSecurityGroupCreate(d *schema.ResourceData, meta interface{}) error {
38+
conn := meta.(*AWSClient).ec2conn
39+
securityGroupOpts := &ec2.DescribeSecurityGroupsInput{
40+
Filters: []*ec2.Filter{
41+
&ec2.Filter{
42+
Name: aws.String("group-name"),
43+
Values: []*string{aws.String("default")},
44+
},
45+
},
46+
}
47+
48+
var vpcId string
49+
if v, ok := d.GetOk("vpc_id"); ok {
50+
vpcId = v.(string)
51+
securityGroupOpts.Filters = append(securityGroupOpts.Filters, &ec2.Filter{
52+
Name: aws.String("vpc-id"),
53+
Values: []*string{aws.String(vpcId)},
54+
})
55+
}
56+
57+
var err error
58+
log.Printf("[DEBUG] Commandeer Default Security Group: %s", securityGroupOpts)
59+
resp, err := conn.DescribeSecurityGroups(securityGroupOpts)
60+
if err != nil {
61+
return fmt.Errorf("Error creating Default Security Group: %s", err)
62+
}
63+
64+
var g *ec2.SecurityGroup
65+
if vpcId != "" {
66+
// if vpcId contains a value, then we expect just a single Security Group
67+
// returned, as default is a protected name for each VPC, and for each
68+
// Region on EC2 Classic
69+
if len(resp.SecurityGroups) != 1 {
70+
return fmt.Errorf("[ERR] Error finding default security group; found (%d) groups: %s", len(resp.SecurityGroups), resp)
71+
}
72+
g = resp.SecurityGroups[0]
73+
} else {
74+
// we need to filter through any returned security groups for the group
75+
// named "default", and does not belong to a VPC
76+
for _, sg := range resp.SecurityGroups {
77+
if sg.VpcId == nil && *sg.GroupName == "default" {
78+
g = sg
79+
}
80+
}
81+
}
82+
83+
if g == nil {
84+
return fmt.Errorf("[ERR] Error finding default security group: no matching group found")
85+
}
86+
87+
d.SetId(*g.GroupId)
88+
89+
log.Printf("[INFO] Default Security Group ID: %s", d.Id())
90+
91+
if err := setTags(conn, d); err != nil {
92+
return err
93+
}
94+
95+
if err := revokeDefaultSecurityGroupRules(meta, g); err != nil {
96+
return errwrap.Wrapf("{{err}}", err)
97+
}
98+
99+
return resourceAwsSecurityGroupUpdate(d, meta)
100+
}
101+
102+
func resourceAwsDefaultSecurityGroupDelete(d *schema.ResourceData, meta interface{}) error {
103+
log.Printf("[WARN] Cannot destroy Default Security Group. Terraform will remove this resource from the state file, however resources may remain.")
104+
d.SetId("")
105+
return nil
106+
}
107+
108+
func revokeDefaultSecurityGroupRules(meta interface{}, g *ec2.SecurityGroup) error {
109+
conn := meta.(*AWSClient).ec2conn
110+
111+
log.Printf("[WARN] Removing all ingress and egress rules found on Default Security Group (%s)", *g.GroupId)
112+
if len(g.IpPermissionsEgress) > 0 {
113+
req := &ec2.RevokeSecurityGroupEgressInput{
114+
GroupId: g.GroupId,
115+
IpPermissions: g.IpPermissionsEgress,
116+
}
117+
118+
log.Printf("[DEBUG] Revoking default egress rules for Default Security Group for %s", *g.GroupId)
119+
if _, err := conn.RevokeSecurityGroupEgress(req); err != nil {
120+
return fmt.Errorf(
121+
"Error revoking default egress rules for Default Security Group (%s): %s",
122+
*g.GroupId, err)
123+
}
124+
}
125+
if len(g.IpPermissions) > 0 {
126+
// a limitation in EC2 Classic is that a call to RevokeSecurityGroupIngress
127+
// cannot contain both the GroupName and the GroupId
128+
for _, p := range g.IpPermissions {
129+
for _, uigp := range p.UserIdGroupPairs {
130+
if uigp.GroupId != nil && uigp.GroupName != nil {
131+
uigp.GroupName = nil
132+
}
133+
}
134+
}
135+
req := &ec2.RevokeSecurityGroupIngressInput{
136+
GroupId: g.GroupId,
137+
IpPermissions: g.IpPermissions,
138+
}
139+
140+
log.Printf("[DEBUG] Revoking default ingress rules for Default Security Group for (%s): %s", *g.GroupId, req)
141+
if _, err := conn.RevokeSecurityGroupIngress(req); err != nil {
142+
return fmt.Errorf(
143+
"Error revoking default ingress rules for Default Security Group (%s): %s",
144+
*g.GroupId, err)
145+
}
146+
}
147+
148+
return nil
149+
}
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
package aws
2+
3+
import (
4+
"fmt"
5+
"reflect"
6+
"testing"
7+
8+
"github.com/aws/aws-sdk-go/aws"
9+
"github.com/aws/aws-sdk-go/service/ec2"
10+
"github.com/hashicorp/terraform/helper/resource"
11+
"github.com/hashicorp/terraform/terraform"
12+
)
13+
14+
func TestAccAWSDefaultSecurityGroup_basic(t *testing.T) {
15+
var group ec2.SecurityGroup
16+
17+
resource.Test(t, resource.TestCase{
18+
PreCheck: func() { testAccPreCheck(t) },
19+
IDRefreshName: "aws_default_security_group.web",
20+
Providers: testAccProviders,
21+
CheckDestroy: testAccCheckAWSDefaultSecurityGroupDestroy,
22+
Steps: []resource.TestStep{
23+
resource.TestStep{
24+
Config: testAccAWSDefaultSecurityGroupConfig,
25+
Check: resource.ComposeTestCheckFunc(
26+
testAccCheckAWSDefaultSecurityGroupExists("aws_default_security_group.web", &group),
27+
testAccCheckAWSDefaultSecurityGroupAttributes(&group),
28+
resource.TestCheckResourceAttr(
29+
"aws_default_security_group.web", "name", "default"),
30+
resource.TestCheckResourceAttr(
31+
"aws_default_security_group.web", "ingress.3629188364.protocol", "tcp"),
32+
resource.TestCheckResourceAttr(
33+
"aws_default_security_group.web", "ingress.3629188364.from_port", "80"),
34+
resource.TestCheckResourceAttr(
35+
"aws_default_security_group.web", "ingress.3629188364.to_port", "8000"),
36+
resource.TestCheckResourceAttr(
37+
"aws_default_security_group.web", "ingress.3629188364.cidr_blocks.#", "1"),
38+
resource.TestCheckResourceAttr(
39+
"aws_default_security_group.web", "ingress.3629188364.cidr_blocks.0", "10.0.0.0/8"),
40+
),
41+
},
42+
},
43+
})
44+
}
45+
46+
func TestAccAWSDefaultSecurityGroup_classic(t *testing.T) {
47+
var group ec2.SecurityGroup
48+
49+
resource.Test(t, resource.TestCase{
50+
PreCheck: func() { testAccPreCheck(t) },
51+
IDRefreshName: "aws_default_security_group.web",
52+
Providers: testAccProviders,
53+
CheckDestroy: testAccCheckAWSDefaultSecurityGroupDestroy,
54+
Steps: []resource.TestStep{
55+
resource.TestStep{
56+
Config: testAccAWSDefaultSecurityGroupConfig_classic,
57+
Check: resource.ComposeTestCheckFunc(
58+
testAccCheckAWSDefaultSecurityGroupExists("aws_default_security_group.web", &group),
59+
testAccCheckAWSDefaultSecurityGroupAttributes(&group),
60+
resource.TestCheckResourceAttr(
61+
"aws_default_security_group.web", "name", "default"),
62+
resource.TestCheckResourceAttr(
63+
"aws_default_security_group.web", "ingress.3629188364.protocol", "tcp"),
64+
resource.TestCheckResourceAttr(
65+
"aws_default_security_group.web", "ingress.3629188364.from_port", "80"),
66+
resource.TestCheckResourceAttr(
67+
"aws_default_security_group.web", "ingress.3629188364.to_port", "8000"),
68+
resource.TestCheckResourceAttr(
69+
"aws_default_security_group.web", "ingress.3629188364.cidr_blocks.#", "1"),
70+
resource.TestCheckResourceAttr(
71+
"aws_default_security_group.web", "ingress.3629188364.cidr_blocks.0", "10.0.0.0/8"),
72+
),
73+
},
74+
},
75+
})
76+
}
77+
78+
func testAccCheckAWSDefaultSecurityGroupDestroy(s *terraform.State) error {
79+
// We expect Security Group to still exist
80+
return nil
81+
}
82+
83+
func testAccCheckAWSDefaultSecurityGroupExists(n string, group *ec2.SecurityGroup) resource.TestCheckFunc {
84+
return func(s *terraform.State) error {
85+
rs, ok := s.RootModule().Resources[n]
86+
if !ok {
87+
return fmt.Errorf("Not found: %s", n)
88+
}
89+
90+
if rs.Primary.ID == "" {
91+
return fmt.Errorf("No Security Group is set")
92+
}
93+
94+
conn := testAccProvider.Meta().(*AWSClient).ec2conn
95+
req := &ec2.DescribeSecurityGroupsInput{
96+
GroupIds: []*string{aws.String(rs.Primary.ID)},
97+
}
98+
resp, err := conn.DescribeSecurityGroups(req)
99+
if err != nil {
100+
return err
101+
}
102+
103+
if len(resp.SecurityGroups) > 0 && *resp.SecurityGroups[0].GroupId == rs.Primary.ID {
104+
*group = *resp.SecurityGroups[0]
105+
return nil
106+
}
107+
108+
return fmt.Errorf("Security Group not found")
109+
}
110+
}
111+
112+
func testAccCheckAWSDefaultSecurityGroupAttributes(group *ec2.SecurityGroup) resource.TestCheckFunc {
113+
return func(s *terraform.State) error {
114+
p := &ec2.IpPermission{
115+
FromPort: aws.Int64(80),
116+
ToPort: aws.Int64(8000),
117+
IpProtocol: aws.String("tcp"),
118+
IpRanges: []*ec2.IpRange{&ec2.IpRange{CidrIp: aws.String("10.0.0.0/8")}},
119+
}
120+
121+
if *group.GroupName != "default" {
122+
return fmt.Errorf("Bad name: %s", *group.GroupName)
123+
}
124+
125+
if len(group.IpPermissions) == 0 {
126+
return fmt.Errorf("No IPPerms")
127+
}
128+
129+
// Compare our ingress
130+
if !reflect.DeepEqual(group.IpPermissions[0], p) {
131+
return fmt.Errorf(
132+
"Got:\n\n%#v\n\nExpected:\n\n%#v\n",
133+
group.IpPermissions[0],
134+
p)
135+
}
136+
137+
return nil
138+
}
139+
}
140+
141+
const testAccAWSDefaultSecurityGroupConfig = `
142+
resource "aws_vpc" "foo" {
143+
cidr_block = "10.1.0.0/16"
144+
}
145+
146+
resource "aws_default_security_group" "web" {
147+
vpc_id = "${aws_vpc.foo.id}"
148+
149+
ingress {
150+
protocol = "6"
151+
from_port = 80
152+
to_port = 8000
153+
cidr_blocks = ["10.0.0.0/8"]
154+
}
155+
156+
egress {
157+
protocol = "tcp"
158+
from_port = 80
159+
to_port = 8000
160+
cidr_blocks = ["10.0.0.0/8"]
161+
}
162+
163+
tags {
164+
Name = "tf-acc-test"
165+
}
166+
}
167+
`
168+
169+
const testAccAWSDefaultSecurityGroupConfig_classic = `
170+
provider "aws" {
171+
region = "us-east-1"
172+
}
173+
174+
resource "aws_default_security_group" "web" {
175+
ingress {
176+
protocol = "6"
177+
from_port = 80
178+
to_port = 8000
179+
cidr_blocks = ["10.0.0.0/8"]
180+
}
181+
182+
tags {
183+
Name = "tf-acc-test"
184+
}
185+
}`

0 commit comments

Comments
 (0)