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:
parent
1bbf1eed7d
commit
79bb2e8a87
|
@ -317,6 +317,7 @@ func Provider() terraform.ResourceProvider {
|
|||
"aws_s3_bucket_policy": resourceAwsS3BucketPolicy(),
|
||||
"aws_s3_bucket_object": resourceAwsS3BucketObject(),
|
||||
"aws_s3_bucket_notification": resourceAwsS3BucketNotification(),
|
||||
"aws_default_security_group": resourceAwsDefaultSecurityGroup(),
|
||||
"aws_security_group": resourceAwsSecurityGroup(),
|
||||
"aws_security_group_rule": resourceAwsSecurityGroupRule(),
|
||||
"aws_simpledb_domain": resourceAwsSimpleDBDomain(),
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}`
|
|
@ -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
|
|
@ -908,6 +908,10 @@
|
|||
<a href="/docs/providers/aws/r/default_route_table.html">aws_default_route_table</a>
|
||||
</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") %>>
|
||||
<a href="/docs/providers/aws/r/flow_log.html">aws_flow_log</a>
|
||||
</li>
|
||||
|
|
Loading…
Reference in New Issue