provider/aws: Add Default Security Group Resource (#8861)

* Docs for default security group
* overrides of default behavior
* add special disclaimer
* update to support classic environments
This commit is contained in:
Clint 2016-09-15 13:59:20 -05:00 committed by GitHub
parent 1bbf1eed7d
commit 79bb2e8a87
5 changed files with 470 additions and 0 deletions

View File

@ -317,6 +317,7 @@ func Provider() terraform.ResourceProvider {
"aws_s3_bucket_policy": resourceAwsS3BucketPolicy(), "aws_s3_bucket_policy": resourceAwsS3BucketPolicy(),
"aws_s3_bucket_object": resourceAwsS3BucketObject(), "aws_s3_bucket_object": resourceAwsS3BucketObject(),
"aws_s3_bucket_notification": resourceAwsS3BucketNotification(), "aws_s3_bucket_notification": resourceAwsS3BucketNotification(),
"aws_default_security_group": resourceAwsDefaultSecurityGroup(),
"aws_security_group": resourceAwsSecurityGroup(), "aws_security_group": resourceAwsSecurityGroup(),
"aws_security_group_rule": resourceAwsSecurityGroupRule(), "aws_security_group_rule": resourceAwsSecurityGroupRule(),
"aws_simpledb_domain": resourceAwsSimpleDBDomain(), "aws_simpledb_domain": resourceAwsSimpleDBDomain(),

View File

@ -0,0 +1,149 @@
package aws
import (
"fmt"
"log"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/terraform/helper/schema"
)
func resourceAwsDefaultSecurityGroup() *schema.Resource {
// reuse aws_security_group_rule schema, and methods for READ, UPDATE
dsg := resourceAwsSecurityGroup()
dsg.Create = resourceAwsDefaultSecurityGroupCreate
dsg.Delete = resourceAwsDefaultSecurityGroupDelete
// Descriptions cannot be updated
delete(dsg.Schema, "description")
// name is a computed value for Default Security Groups and cannot be changed
delete(dsg.Schema, "name_prefix")
dsg.Schema["name"] = &schema.Schema{
Type: schema.TypeString,
Computed: true,
}
// 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
dsg.Schema["ingress"].Computed = false
dsg.Schema["egress"].Computed = false
return dsg
}
func resourceAwsDefaultSecurityGroupCreate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).ec2conn
securityGroupOpts := &ec2.DescribeSecurityGroupsInput{
Filters: []*ec2.Filter{
&ec2.Filter{
Name: aws.String("group-name"),
Values: []*string{aws.String("default")},
},
},
}
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: []*string{aws.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: %s", 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("[ERR] Error finding default security group; found (%d) groups: %s", len(resp.SecurityGroups), resp)
}
g = resp.SecurityGroups[0]
} 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 && *sg.GroupName == "default" {
g = sg
}
}
}
if g == nil {
return fmt.Errorf("[ERR] Error finding default security group: no matching group found")
}
d.SetId(*g.GroupId)
log.Printf("[INFO] Default Security Group ID: %s", d.Id())
if err := setTags(conn, d); err != nil {
return err
}
if err := revokeDefaultSecurityGroupRules(meta, g); err != nil {
return errwrap.Wrapf("{{err}}", err)
}
return resourceAwsSecurityGroupUpdate(d, meta)
}
func resourceAwsDefaultSecurityGroupDelete(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.")
d.SetId("")
return nil
}
func revokeDefaultSecurityGroupRules(meta interface{}, g *ec2.SecurityGroup) error {
conn := meta.(*AWSClient).ec2conn
log.Printf("[WARN] Removing all ingress and egress rules found on Default Security Group (%s)", *g.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", *g.GroupId)
if _, err := conn.RevokeSecurityGroupEgress(req); err != nil {
return fmt.Errorf(
"Error revoking default egress rules for Default Security Group (%s): %s",
*g.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", *g.GroupId, req)
if _, err := conn.RevokeSecurityGroupIngress(req); err != nil {
return fmt.Errorf(
"Error revoking default ingress rules for Default Security Group (%s): %s",
*g.GroupId, err)
}
}
return nil
}

View File

@ -0,0 +1,185 @@
package aws
import (
"fmt"
"reflect"
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
func TestAccAWSDefaultSecurityGroup_basic(t *testing.T) {
var group ec2.SecurityGroup
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
IDRefreshName: "aws_default_security_group.web",
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSDefaultSecurityGroupDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSDefaultSecurityGroupConfig,
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSDefaultSecurityGroupExists("aws_default_security_group.web", &group),
testAccCheckAWSDefaultSecurityGroupAttributes(&group),
resource.TestCheckResourceAttr(
"aws_default_security_group.web", "name", "default"),
resource.TestCheckResourceAttr(
"aws_default_security_group.web", "ingress.3629188364.protocol", "tcp"),
resource.TestCheckResourceAttr(
"aws_default_security_group.web", "ingress.3629188364.from_port", "80"),
resource.TestCheckResourceAttr(
"aws_default_security_group.web", "ingress.3629188364.to_port", "8000"),
resource.TestCheckResourceAttr(
"aws_default_security_group.web", "ingress.3629188364.cidr_blocks.#", "1"),
resource.TestCheckResourceAttr(
"aws_default_security_group.web", "ingress.3629188364.cidr_blocks.0", "10.0.0.0/8"),
),
},
},
})
}
func TestAccAWSDefaultSecurityGroup_classic(t *testing.T) {
var group ec2.SecurityGroup
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
IDRefreshName: "aws_default_security_group.web",
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSDefaultSecurityGroupDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSDefaultSecurityGroupConfig_classic,
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSDefaultSecurityGroupExists("aws_default_security_group.web", &group),
testAccCheckAWSDefaultSecurityGroupAttributes(&group),
resource.TestCheckResourceAttr(
"aws_default_security_group.web", "name", "default"),
resource.TestCheckResourceAttr(
"aws_default_security_group.web", "ingress.3629188364.protocol", "tcp"),
resource.TestCheckResourceAttr(
"aws_default_security_group.web", "ingress.3629188364.from_port", "80"),
resource.TestCheckResourceAttr(
"aws_default_security_group.web", "ingress.3629188364.to_port", "8000"),
resource.TestCheckResourceAttr(
"aws_default_security_group.web", "ingress.3629188364.cidr_blocks.#", "1"),
resource.TestCheckResourceAttr(
"aws_default_security_group.web", "ingress.3629188364.cidr_blocks.0", "10.0.0.0/8"),
),
},
},
})
}
func testAccCheckAWSDefaultSecurityGroupDestroy(s *terraform.State) error {
// We expect Security Group to still exist
return nil
}
func testAccCheckAWSDefaultSecurityGroupExists(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 testAccCheckAWSDefaultSecurityGroupAttributes(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{&ec2.IpRange{CidrIp: aws.String("10.0.0.0/8")}},
}
if *group.GroupName != "default" {
return fmt.Errorf("Bad name: %s", *group.GroupName)
}
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
}
}
const testAccAWSDefaultSecurityGroupConfig = `
resource "aws_vpc" "foo" {
cidr_block = "10.1.0.0/16"
}
resource "aws_default_security_group" "web" {
vpc_id = "${aws_vpc.foo.id}"
ingress {
protocol = "6"
from_port = 80
to_port = 8000
cidr_blocks = ["10.0.0.0/8"]
}
egress {
protocol = "tcp"
from_port = 80
to_port = 8000
cidr_blocks = ["10.0.0.0/8"]
}
tags {
Name = "tf-acc-test"
}
}
`
const testAccAWSDefaultSecurityGroupConfig_classic = `
provider "aws" {
region = "us-east-1"
}
resource "aws_default_security_group" "web" {
ingress {
protocol = "6"
from_port = 80
to_port = 8000
cidr_blocks = ["10.0.0.0/8"]
}
tags {
Name = "tf-acc-test"
}
}`

View File

@ -0,0 +1,131 @@
---
layout: "aws"
page_title: "AWS: aws_default_security_group"
sidebar_current: "docs-aws-resource-default-security-group"
description: |-
Manage the default Security Group resource.
---
# aws\_default\_security\_group
Provides a resource to manage the default AWS Security Group.
For EC2 Classic accounts, each region comes with a Default Security Group.
Additionall, each VPC created in AWS comes with a Default Security Group that can be managed, but not
destroyed. **This is an advanced resource**, and has special caveats to be aware
of when using it. Please read this document in its entirety before using this
resource.
The `aws_default_security_group` behaves differently from normal resources, in that
Terraform does not _create_ this resource, but instead "adopts" it
into management. We can do this because these default security groups cannot be
destroyed, and are created with a known set of default ingress/egress rules.
When Terraform first adopts the Default Security Group, it **immediately removes all
ingress and egress rules in the ACL**. It then proceeds to create any rules specified in the
configuration. This step is required so that only the rules specified in the
configuration are created.
For more information about Default Security Groups, see the AWS Documentation on
[Default Security Groups][aws-default-security-groups].
## Basic Example Usage, with default rules
The following config gives the Default Security Group the same rules that AWS
provides by default, but pulls the resource under management by Terraform. This means that
any ingress or egress rules added or changed will be detected as drift.
```
resource "aws_vpc" "mainvpc" {
cidr_block = "10.1.0.0/16"
}
resource "aws_default_security_group" "default" {
vpc_id = "${aws_vpc.mainvpc.vpc_id}"
ingress {
protocol = -1
self = true
from_port = 0
to_port = 0
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
```
## Example config to deny all Egress traffic, allowing Ingress
The following denies all Egress traffic by omitting any `egress` rules, while
including the default `ingress` rule to allow all traffic.
```
resource "aws_vpc" "mainvpc" {
cidr_block = "10.1.0.0/16"
}
resource "aws_default_security_group" "default" {
vpc_id = "${aws_vpc.mainvpc.vpc_id}"
ingress {
protocol = -1
self = true
from_port = 0
to_port = 0
}
}
```
## Argument Reference
The arguments of an `aws_default_security_group` differ slightly from `aws_security_group`
resources. Namely, the `name` arguement is computed, and the `name_prefix` attribute
removed. The following arguements are still supported:
* `description` - (Optional, Forces new resource) The security group description. Defaults to
"Managed by Terraform". Cannot be "". __NOTE__: This field maps to the AWS
`GroupDescription` attribute, for which there is no Update API. If you'd like
to classify your security groups in a way that can be updated, use `tags`.
* `ingress` - (Optional) Can be specified multiple times for each
ingress rule. Each ingress block supports fields documented below.
* `egress` - (Optional, VPC only) Can be specified multiple times for each
egress rule. Each egress block supports fields documented below.
* `vpc_id` - (Optional, Forces new resource) The VPC ID. **Note that changing
the `vpc_id` will _not_ restore any default security group rules that were
modified, added, or removed.** It will be left in it's current state
* `tags` - (Optional) A mapping of tags to assign to the resource.
## Usage
With the exceptions mentioned above, `aws_default_security_group` should
identical behavior to `aws_security_group`. Please consult [AWS_SECURITY_GROUP](/docs/providers/aws/r/security_group.html)
for further usage documentation.
Removing `aws_default_security_group` from your configuration
Each AWS VPC (or region, if using EC2 Classic) comes with a Default Security
Group that cannot be deleted. The `aws_default_security_group` allows you to
manage this Security Group, but Terraform cannot destroy it. Removing this resource
from your configuration will remove it from your statefile and management, but
will not destroy the Security Group. All ingress or egress rules will be left as
they are at the time of removal. You can resume managing them via the AWS Console.
## Attributes Reference
The following attributes are exported:
* `id` - The ID of the security group
* `vpc_id` - The VPC ID.
* `owner_id` - The owner ID.
* `name` - The name of the security group
* `description` - The description of the security group
* `ingress` - The ingress rules. See above for more.
* `egress` - The egress rules. See above for more.
[aws-default-security-groups]: http://docs.aws.amazon.com/fr_fr/AWSEC2/latest/UserGuide/using-network-security.html#default-security-group

View File

@ -908,6 +908,10 @@
<a href="/docs/providers/aws/r/default_route_table.html">aws_default_route_table</a> <a href="/docs/providers/aws/r/default_route_table.html">aws_default_route_table</a>
</li> </li>
<li<%= sidebar_current("docs-aws-resource-default-security-group") %>>
<a href="/docs/providers/aws/r/default_security_group.html">aws_default_security_group</a>
</li>
<li<%= sidebar_current("docs-aws-resource-flow-log") %>> <li<%= sidebar_current("docs-aws-resource-flow-log") %>>
<a href="/docs/providers/aws/r/flow_log.html">aws_flow_log</a> <a href="/docs/providers/aws/r/flow_log.html">aws_flow_log</a>
</li> </li>