From 885efa08374ac625f066c88fd255ee49306653b1 Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Mon, 20 Apr 2015 13:38:21 -0500 Subject: [PATCH] provider/aws: Add Security Group Rule as a top level resource - document conflict with sg rules and sg in-line rules - for this to work, ingress rules need to be computed --- builtin/providers/aws/provider.go | 1 + .../aws/resource_aws_security_group.go | 1 + .../aws/resource_aws_security_group_rule.go | 342 +++++++++++++++ .../resource_aws_security_group_rule_test.go | 391 ++++++++++++++++++ .../aws/resource_aws_security_group_test.go | 106 ++--- .../aws/r/security_group.html.markdown | 9 +- .../aws/r/security_group_rule.html.markdown | 61 +++ website/source/layouts/aws.erb | 4 + 8 files changed, 861 insertions(+), 54 deletions(-) create mode 100644 builtin/providers/aws/resource_aws_security_group_rule.go create mode 100644 builtin/providers/aws/resource_aws_security_group_rule_test.go create mode 100644 website/source/docs/providers/aws/r/security_group_rule.html.markdown diff --git a/builtin/providers/aws/provider.go b/builtin/providers/aws/provider.go index ee40ae689..095e392dd 100644 --- a/builtin/providers/aws/provider.go +++ b/builtin/providers/aws/provider.go @@ -120,6 +120,7 @@ func Provider() terraform.ResourceProvider { "aws_route_table": resourceAwsRouteTable(), "aws_s3_bucket": resourceAwsS3Bucket(), "aws_security_group": resourceAwsSecurityGroup(), + "aws_security_group_rule": resourceAwsSecurityGroupRule(), "aws_subnet": resourceAwsSubnet(), "aws_vpc_dhcp_options_association": resourceAwsVpcDhcpOptionsAssociation(), "aws_vpc_dhcp_options": resourceAwsVpcDhcpOptions(), diff --git a/builtin/providers/aws/resource_aws_security_group.go b/builtin/providers/aws/resource_aws_security_group.go index 50dfccd64..c673cb1f7 100644 --- a/builtin/providers/aws/resource_aws_security_group.go +++ b/builtin/providers/aws/resource_aws_security_group.go @@ -45,6 +45,7 @@ func resourceAwsSecurityGroup() *schema.Resource { "ingress": &schema.Schema{ Type: schema.TypeSet, Optional: true, + Computed: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "from_port": &schema.Schema{ diff --git a/builtin/providers/aws/resource_aws_security_group_rule.go b/builtin/providers/aws/resource_aws_security_group_rule.go new file mode 100644 index 000000000..b5fb3058f --- /dev/null +++ b/builtin/providers/aws/resource_aws_security_group_rule.go @@ -0,0 +1,342 @@ +package aws + +import ( + "bytes" + "fmt" + "log" + "sort" + "strings" + + "github.com/awslabs/aws-sdk-go/aws" + "github.com/awslabs/aws-sdk-go/service/ec2" + "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceAwsSecurityGroupRule() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsSecurityGroupRuleCreate, + Read: resourceAwsSecurityGroupRuleRead, + Delete: resourceAwsSecurityGroupRuleDelete, + + Schema: map[string]*schema.Schema{ + "type": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "Type of rule, ingress (inbound) or egress (outbound).", + }, + + "from_port": &schema.Schema{ + Type: schema.TypeInt, + Required: true, + ForceNew: true, + }, + + "to_port": &schema.Schema{ + Type: schema.TypeInt, + Required: true, + ForceNew: true, + }, + + "protocol": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "cidr_blocks": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + + "security_group_id": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "source_security_group_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + }, + + "self": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: false, + ForceNew: true, + }, + }, + } +} + +func resourceAwsSecurityGroupRuleCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + sg_id := d.Get("security_group_id").(string) + sg, err := findResourceSecurityGroup(conn, sg_id) + + if err != nil { + return fmt.Errorf("sorry") + } + + perm := expandIPPerm(d, sg) + + ruleType := d.Get("type").(string) + + switch ruleType { + case "ingress": + log.Printf("[DEBUG] Authorizing security group %s %s rule: %#v", + sg_id, "Ingress", perm) + + req := &ec2.AuthorizeSecurityGroupIngressInput{ + GroupID: sg.GroupID, + IPPermissions: []*ec2.IPPermission{perm}, + } + + if sg.VPCID == nil || *sg.VPCID == "" { + req.GroupID = nil + req.GroupName = sg.GroupName + } + + _, err := conn.AuthorizeSecurityGroupIngress(req) + + if err != nil { + return fmt.Errorf( + "Error authorizing security group %s rules: %s", + "rules", err) + } + + case "egress": + log.Printf("[DEBUG] Authorizing security group %s %s rule: %#v", + sg_id, "Egress", perm) + + req := &ec2.AuthorizeSecurityGroupEgressInput{ + GroupID: sg.GroupID, + IPPermissions: []*ec2.IPPermission{perm}, + } + + _, err = conn.AuthorizeSecurityGroupEgress(req) + + if err != nil { + return fmt.Errorf( + "Error authorizing security group %s rules: %s", + "rules", err) + } + + default: + return fmt.Errorf("Security Group Rule must be type 'ingress' or type 'egress'") + } + + d.SetId(ipPermissionIDHash(ruleType, perm)) + + return resourceAwsSecurityGroupRuleRead(d, meta) +} + +func resourceAwsSecurityGroupRuleRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + sg_id := d.Get("security_group_id").(string) + sg, err := findResourceSecurityGroup(conn, sg_id) + if err != nil { + d.SetId("") + } + + var rule *ec2.IPPermission + ruleType := d.Get("type").(string) + var rl []*ec2.IPPermission + switch ruleType { + case "ingress": + rl = sg.IPPermissions + default: + rl = sg.IPPermissionsEgress + } + + for _, r := range rl { + if d.Id() == ipPermissionIDHash(ruleType, r) { + rule = r + } + } + + if rule == nil { + log.Printf("[DEBUG] Unable to find matching %s Security Group Rule for Group %s", + ruleType, sg_id) + d.SetId("") + return nil + } + + d.Set("from_port", rule.FromPort) + d.Set("to_port", rule.ToPort) + d.Set("protocol", rule.IPProtocol) + d.Set("type", ruleType) + + var cb []string + for _, c := range rule.IPRanges { + cb = append(cb, *c.CIDRIP) + } + + d.Set("cidr_blocks", cb) + + if len(rule.UserIDGroupPairs) > 0 { + s := rule.UserIDGroupPairs[0] + d.Set("source_security_group_id", *s.GroupID) + } + + return nil +} + +func resourceAwsSecurityGroupRuleDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + sg_id := d.Get("security_group_id").(string) + sg, err := findResourceSecurityGroup(conn, sg_id) + + if err != nil { + return fmt.Errorf("sorry") + } + + perm := expandIPPerm(d, sg) + ruleType := d.Get("type").(string) + switch ruleType { + case "ingress": + log.Printf("[DEBUG] Revoking security group %#v %s rule: %#v", + sg_id, "ingress", perm) + req := &ec2.RevokeSecurityGroupIngressInput{ + GroupID: sg.GroupID, + IPPermissions: []*ec2.IPPermission{perm}, + } + + _, err = conn.RevokeSecurityGroupIngress(req) + + if err != nil { + return fmt.Errorf( + "Error revoking security group %s rules: %s", + sg_id, err) + } + case "egress": + + log.Printf("[DEBUG] Revoking security group %#v %s rule: %#v", + sg_id, "egress", perm) + req := &ec2.RevokeSecurityGroupEgressInput{ + GroupID: sg.GroupID, + IPPermissions: []*ec2.IPPermission{perm}, + } + + _, err = conn.RevokeSecurityGroupEgress(req) + + if err != nil { + return fmt.Errorf( + "Error revoking security group %s rules: %s", + sg_id, err) + } + } + + d.SetId("") + + return nil +} + +func findResourceSecurityGroup(conn *ec2.EC2, id string) (*ec2.SecurityGroup, error) { + req := &ec2.DescribeSecurityGroupsInput{ + GroupIDs: []*string{aws.String(id)}, + } + resp, err := conn.DescribeSecurityGroups(req) + if err != nil { + if ec2err, ok := err.(aws.APIError); ok { + if ec2err.Code == "InvalidSecurityGroupID.NotFound" || + ec2err.Code == "InvalidGroup.NotFound" { + resp = nil + err = nil + } + } + + if err != nil { + log.Printf("Error on findResourceSecurityGroup: %s", err) + return nil, err + } + } + + return resp.SecurityGroups[0], nil +} + +func ipPermissionIDHash(ruleType string, ip *ec2.IPPermission) string { + var buf bytes.Buffer + // for egress rules, an TCP rule of -1 is automatically added, in which case + // the to and from ports will be nil. We don't record this rule locally. + if ip.IPProtocol != nil && *ip.IPProtocol != "-1" { + buf.WriteString(fmt.Sprintf("%d-", *ip.FromPort)) + buf.WriteString(fmt.Sprintf("%d-", *ip.ToPort)) + buf.WriteString(fmt.Sprintf("%s-", *ip.IPProtocol)) + } + buf.WriteString(fmt.Sprintf("%s-", ruleType)) + + // 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 len(ip.IPRanges) > 0 { + s := make([]string, len(ip.IPRanges)) + for i, r := range ip.IPRanges { + s[i] = *r.CIDRIP + } + sort.Strings(s) + + for _, v := range s { + buf.WriteString(fmt.Sprintf("%s-", v)) + } + } + + return fmt.Sprintf("sg-%d", hashcode.String(buf.String())) +} + +func expandIPPerm(d *schema.ResourceData, sg *ec2.SecurityGroup) *ec2.IPPermission { + var perm ec2.IPPermission + + perm.FromPort = aws.Long(int64(d.Get("from_port").(int))) + perm.ToPort = aws.Long(int64(d.Get("to_port").(int))) + perm.IPProtocol = aws.String(d.Get("protocol").(string)) + + var groups []string + if raw, ok := d.GetOk("source_security_group_id"); ok { + groups = append(groups, raw.(string)) + } + + if v, ok := d.GetOk("self"); ok && v.(bool) { + if sg.VPCID != nil && *sg.VPCID != "" { + groups = append(groups, *sg.GroupID) + } else { + groups = append(groups, *sg.GroupName) + } + } + + if len(groups) > 0 { + perm.UserIDGroupPairs = make([]*ec2.UserIDGroupPair, len(groups)) + for i, name := range groups { + 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 sg.VPCID == nil || *sg.VPCID == "" { + perm.UserIDGroupPairs[i].GroupID = nil + perm.UserIDGroupPairs[i].GroupName = aws.String(id) + perm.UserIDGroupPairs[i].UserID = nil + } + } + } + + if raw, ok := d.GetOk("cidr_blocks"); ok { + list := raw.([]interface{}) + perm.IPRanges = make([]*ec2.IPRange, len(list)) + for i, v := range list { + perm.IPRanges[i] = &ec2.IPRange{CIDRIP: aws.String(v.(string))} + } + } + + return &perm +} diff --git a/builtin/providers/aws/resource_aws_security_group_rule_test.go b/builtin/providers/aws/resource_aws_security_group_rule_test.go new file mode 100644 index 000000000..ff75b4431 --- /dev/null +++ b/builtin/providers/aws/resource_aws_security_group_rule_test.go @@ -0,0 +1,391 @@ +package aws + +import ( + "fmt" + "reflect" + "testing" + + "github.com/awslabs/aws-sdk-go/aws" + "github.com/awslabs/aws-sdk-go/service/ec2" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestIpPermissionIDHash(t *testing.T) { + simple := &ec2.IPPermission{ + IPProtocol: aws.String("tcp"), + FromPort: aws.Long(int64(80)), + ToPort: aws.Long(int64(8000)), + IPRanges: []*ec2.IPRange{ + &ec2.IPRange{ + CIDRIP: aws.String("10.0.0.0/8"), + }, + }, + } + + egress := &ec2.IPPermission{ + IPProtocol: aws.String("tcp"), + FromPort: aws.Long(int64(80)), + ToPort: aws.Long(int64(8000)), + IPRanges: []*ec2.IPRange{ + &ec2.IPRange{ + CIDRIP: aws.String("10.0.0.0/8"), + }, + }, + } + + egress_all := &ec2.IPPermission{ + IPProtocol: aws.String("-1"), + IPRanges: []*ec2.IPRange{ + &ec2.IPRange{ + CIDRIP: aws.String("10.0.0.0/8"), + }, + }, + } + + // hardcoded hashes, to detect future change + cases := []struct { + Input *ec2.IPPermission + Type string + Output string + }{ + {simple, "ingress", "sg-82613597"}, + {egress, "egress", "sg-363054720"}, + {egress_all, "egress", "sg-857124156"}, + } + + for _, tc := range cases { + actual := ipPermissionIDHash(tc.Type, tc.Input) + if actual != tc.Output { + t.Fatalf("input: %s - %#v\noutput: %s", tc.Type, tc.Input, actual) + } + } +} + +func TestAccAWSSecurityGroupRule_Ingress(t *testing.T) { + var group ec2.SecurityGroup + + testRuleCount := func(*terraform.State) error { + if len(group.IPPermissions) != 1 { + return fmt.Errorf("Wrong Security Group rule count, expected %d, got %d", + 1, len(group.IPPermissions)) + } + + rule := group.IPPermissions[0] + if *rule.FromPort != int64(80) { + return fmt.Errorf("Wrong Security Group port setting, expected %d, got %d", + 80, int(*rule.FromPort)) + } + + return nil + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSecurityGroupRuleDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSSecurityGroupRuleIngressConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSecurityGroupRuleExists("aws_security_group.web", &group), + testAccCheckAWSSecurityGroupRuleAttributes(&group, "ingress"), + resource.TestCheckResourceAttr( + "aws_security_group_rule.ingress_1", "from_port", "80"), + testRuleCount, + ), + }, + }, + }) +} + +func TestAccAWSSecurityGroupRule_IngressClassic(t *testing.T) { + var group ec2.SecurityGroup + + testRuleCount := func(*terraform.State) error { + if len(group.IPPermissions) != 1 { + return fmt.Errorf("Wrong Security Group rule count, expected %d, got %d", + 1, len(group.IPPermissions)) + } + + rule := group.IPPermissions[0] + if *rule.FromPort != int64(80) { + return fmt.Errorf("Wrong Security Group port setting, expected %d, got %d", + 80, int(*rule.FromPort)) + } + + return nil + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSecurityGroupRuleDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSSecurityGroupRuleIngressClassicConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSecurityGroupRuleExists("aws_security_group.web", &group), + testAccCheckAWSSecurityGroupRuleAttributes(&group, "ingress"), + resource.TestCheckResourceAttr( + "aws_security_group_rule.ingress_1", "from_port", "80"), + testRuleCount, + ), + }, + }, + }) +} + +func TestAccAWSSecurityGroupRule_MultiIngress(t *testing.T) { + var group ec2.SecurityGroup + + testMultiRuleCount := func(*terraform.State) error { + if len(group.IPPermissions) != 3 { + return fmt.Errorf("Wrong Security Group rule count, expected %d, got %d", + 3, 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, int(*rule.ToPort)) + } + + return nil + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSecurityGroupRuleDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSSecurityGroupRuleConfigMultiIngress, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSecurityGroupRuleExists("aws_security_group.web", &group), + testMultiRuleCount, + ), + }, + }, + }) +} + +func TestAccAWSSecurityGroupRule_Egress(t *testing.T) { + var group ec2.SecurityGroup + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSecurityGroupRuleDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSSecurityGroupRuleEgressConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSecurityGroupRuleExists("aws_security_group.web", &group), + testAccCheckAWSSecurityGroupRuleAttributes(&group, "egress"), + ), + }, + }, + }) +} + +func testAccCheckAWSSecurityGroupRuleDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).ec2conn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_security_group" { + continue + } + + // Retrieve our group + req := &ec2.DescribeSecurityGroupsInput{ + GroupIDs: []*string{aws.String(rs.Primary.ID)}, + } + resp, err := conn.DescribeSecurityGroups(req) + if err == nil { + if len(resp.SecurityGroups) > 0 && *resp.SecurityGroups[0].GroupID == rs.Primary.ID { + return fmt.Errorf("Security Group (%s) still exists.", rs.Primary.ID) + } + + return nil + } + + ec2err, ok := err.(aws.APIError) + if !ok { + return err + } + // Confirm error code is what we want + if ec2err.Code != "InvalidGroup.NotFound" { + return err + } + } + + return nil +} + +func testAccCheckAWSSecurityGroupRuleExists(n string, group *ec2.SecurityGroup) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No Security Group is set") + } + + conn := testAccProvider.Meta().(*AWSClient).ec2conn + req := &ec2.DescribeSecurityGroupsInput{ + GroupIDs: []*string{aws.String(rs.Primary.ID)}, + } + resp, err := conn.DescribeSecurityGroups(req) + if err != nil { + return err + } + + if len(resp.SecurityGroups) > 0 && *resp.SecurityGroups[0].GroupID == rs.Primary.ID { + *group = *resp.SecurityGroups[0] + return nil + } + + return fmt.Errorf("Security Group not found") + } +} + +func testAccCheckAWSSecurityGroupRuleAttributes(group *ec2.SecurityGroup, ruleType string) resource.TestCheckFunc { + return func(s *terraform.State) error { + p := &ec2.IPPermission{ + FromPort: aws.Long(80), + ToPort: aws.Long(8000), + IPProtocol: aws.String("tcp"), + IPRanges: []*ec2.IPRange{&ec2.IPRange{CIDRIP: aws.String("10.0.0.0/8")}}, + } + var rules []*ec2.IPPermission + if ruleType == "ingress" { + rules = group.IPPermissions + } else { + rules = group.IPPermissionsEgress + } + + if len(rules) == 0 { + return fmt.Errorf("No IPPerms") + } + + // Compare our ingress + if !reflect.DeepEqual(rules[0], p) { + return fmt.Errorf( + "Got:\n\n%#v\n\nExpected:\n\n%#v\n", + rules[0], + p) + } + + return nil + } +} + +const testAccAWSSecurityGroupRuleIngressConfig = ` +resource "aws_security_group" "web" { + name = "terraform_acceptance_test_example" + description = "Used in the terraform acceptance tests" + + tags { + Name = "tf-acc-test" + } +} + +resource "aws_security_group_rule" "ingress_1" { + 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}" +} +` + +const testAccAWSSecurityGroupRuleIngressClassicConfig = ` +provider "aws" { + region = "us-east-1" +} + +resource "aws_security_group" "web" { + name = "terraform_acceptance_test_example" + description = "Used in the terraform acceptance tests" + + tags { + Name = "tf-acc-test" + } +} + +resource "aws_security_group_rule" "ingress_1" { + 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}" +} +` + +const testAccAWSSecurityGroupRuleEgressConfig = ` +resource "aws_security_group" "web" { + name = "terraform_acceptance_test_example" + description = "Used in the terraform acceptance tests" + + tags { + Name = "tf-acc-test" + } +} + +resource "aws_security_group_rule" "egress_1" { + 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}" +} +` + +const testAccAWSSecurityGroupRuleConfigMultiIngress = ` +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}" + source_security_group_id = "${aws_security_group.worker.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}" +} +` diff --git a/builtin/providers/aws/resource_aws_security_group_test.go b/builtin/providers/aws/resource_aws_security_group_test.go index 165f94029..eb3f200f2 100644 --- a/builtin/providers/aws/resource_aws_security_group_test.go +++ b/builtin/providers/aws/resource_aws_security_group_test.go @@ -216,36 +216,36 @@ func TestAccAWSSecurityGroup_generatedName(t *testing.T) { func TestAccAWSSecurityGroup_DefaultEgress(t *testing.T) { - // VPC - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckAWSSecurityGroupDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: testAccAWSSecurityGroupConfigDefaultEgress, - Check: resource.ComposeTestCheckFunc( - testAccCheckAWSSecurityGroupExistsWithoutDefault("aws_security_group.worker"), - ), - }, - }, - }) + // VPC + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSecurityGroupDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSSecurityGroupConfigDefaultEgress, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSecurityGroupExistsWithoutDefault("aws_security_group.worker"), + ), + }, + }, + }) - // Classic - var group ec2.SecurityGroup - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckAWSSecurityGroupDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: testAccAWSSecurityGroupConfigClassic, - Check: resource.ComposeTestCheckFunc( - testAccCheckAWSSecurityGroupExists("aws_security_group.web", &group), - ), - }, - }, - }) + // Classic + var group ec2.SecurityGroup + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSecurityGroupDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSSecurityGroupConfigClassic, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSecurityGroupExists("aws_security_group.web", &group), + ), + }, + }, + }) } func testAccCheckAWSSecurityGroupDestroy(s *terraform.State) error { @@ -429,35 +429,35 @@ func testAccCheckAWSSecurityGroupAttributesChanged(group *ec2.SecurityGroup) res } func testAccCheckAWSSecurityGroupExistsWithoutDefault(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) - } + 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") - } + if rs.Primary.ID == "" { + return fmt.Errorf("No Security Group is set") + } - conn := testAccProvider.Meta().(*AWSClient).ec2conn - req := &ec2.DescribeSecurityGroupsInput{ - GroupIDs: []*string{aws.String(rs.Primary.ID)}, - } - resp, err := conn.DescribeSecurityGroups(req) - if err != nil { - return err - } + conn := testAccProvider.Meta().(*AWSClient).ec2conn + req := &ec2.DescribeSecurityGroupsInput{ + GroupIDs: []*string{aws.String(rs.Primary.ID)}, + } + resp, err := conn.DescribeSecurityGroups(req) + if err != nil { + return err + } - if len(resp.SecurityGroups) > 0 && *resp.SecurityGroups[0].GroupID == rs.Primary.ID { - group := *resp.SecurityGroups[0] + if len(resp.SecurityGroups) > 0 && *resp.SecurityGroups[0].GroupID == rs.Primary.ID { + group := *resp.SecurityGroups[0] - if len(group.IPPermissionsEgress) != 1 { - return fmt.Errorf("Security Group should have only 1 egress rule, got %d", len(group.IPPermissionsEgress)) - } - } + if len(group.IPPermissionsEgress) != 1 { + return fmt.Errorf("Security Group should have only 1 egress rule, got %d", len(group.IPPermissionsEgress)) + } + } - return nil - } + return nil + } } const testAccAWSSecurityGroupConfig = ` diff --git a/website/source/docs/providers/aws/r/security_group.html.markdown b/website/source/docs/providers/aws/r/security_group.html.markdown index 765154906..e5096ae24 100644 --- a/website/source/docs/providers/aws/r/security_group.html.markdown +++ b/website/source/docs/providers/aws/r/security_group.html.markdown @@ -8,7 +8,14 @@ description: |- # aws\_security\_group -Provides an security group resource. +Provides a security group resource. + +~> **NOTE on Security Groups and Security Group Rules:** Terraform currently +provides both a standalone [Security Group Rule resource](security_group_rule.html) (a single `ingress` or +`egress` rule), and a Security Group resource with `ingress` and `egress` rules +defined in-line. At this time you cannot use a Security Group with in-line rules +in conjunction with any Security Group Rule resources. Doing so will cause +a conflict of rule settings and will overwrite rules. ## Example Usage diff --git a/website/source/docs/providers/aws/r/security_group_rule.html.markdown b/website/source/docs/providers/aws/r/security_group_rule.html.markdown new file mode 100644 index 000000000..dbc0c1c72 --- /dev/null +++ b/website/source/docs/providers/aws/r/security_group_rule.html.markdown @@ -0,0 +1,61 @@ +--- +layout: "aws" +page_title: "AWS: aws_security_group_rule" +sidebar_current: "docs-aws-resource-security-group-rule" +description: |- + Provides an security group rule resource. +--- + +# aws\_security\_group\_rule + +Provides a security group rule resource. Represents a signle `ingress` or +`egress` group rule, which can be added to external Security Groups. + +~> **NOTE on Security Groups and Security Group Rules:** Terraform currently +provides both a standalone Security Group Rule resource (a single `ingress` or +`egress` rule), and a [Security Group resource](security_group.html) with `ingress` and `egress` rules +defined in-line. At this time you cannot use a Security Group with in-line rules +in conjunction with any Security Group Rule resources. Doing so will cause +a conflict of rule settings and will overwrite rules. + +## Example Usage + +Basic usage + +``` +resource "aws_security_group_rule" "allow_all" { + type = "ingress" + from_port = 0 + to_port = 65535 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + + security_group_id = "sg-123456" + source_security_group_id = "sg-654321" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `type` - (Required) The type of rule being created. Valid options are `ingress` (inbound) +or `egress` (outbound). +* `cidr_blocks` - (Optional) List of CIDR blocks. +* `from_port` - (Required) The start port. +* `protocol` - (Required) The protocol. +* `security_group_id` - (Required) The security group to apply this rule too. +* `source_security_group_id` - (Optional) The security group id to allow access to/from, + depending on the `type`. +* `self` - (Optional) If true, the security group itself will be added as + a source to this ingress rule. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The ID of the security group rule +* `type` - The type of rule, `ingress` or `egress` +* `from_port` - The source port +* `to_port` - The destination port +* `protocol` – The protocol used diff --git a/website/source/layouts/aws.erb b/website/source/layouts/aws.erb index 160b9f03e..bb644a0bb 100644 --- a/website/source/layouts/aws.erb +++ b/website/source/layouts/aws.erb @@ -136,6 +136,10 @@ aws_security_group + > + aws_security_group_rule + + > aws_subnet