Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

adding support for egress rules in AWS Security Groups #856

Merged
merged 2 commits into from
Feb 17, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
179 changes: 130 additions & 49 deletions builtin/providers/aws/resource_aws_security_group.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,52 @@ func resourceAwsSecurityGroup() *schema.Resource {
},
},
},
Set: resourceAwsSecurityGroupIngressHash,
Set: resourceAwsSecurityGroupRuleHash,
},

"egress": &schema.Schema{
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"from_port": &schema.Schema{
Type: schema.TypeInt,
Required: true,
},

"to_port": &schema.Schema{
Type: schema.TypeInt,
Required: true,
},

"protocol": &schema.Schema{
Type: schema.TypeString,
Required: true,
},

"cidr_blocks": &schema.Schema{
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},

"security_groups": &schema.Schema{
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: func(v interface{}) int {
return hashcode.String(v.(string))
},
},

"self": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Default: false,
},
},
},
Set: resourceAwsSecurityGroupRuleHash,
},

"owner_id": &schema.Schema{
Expand Down Expand Up @@ -139,28 +184,14 @@ func resourceAwsSecurityGroupCreate(d *schema.ResourceData, meta interface{}) er
return resourceAwsSecurityGroupUpdate(d, meta)
}

func resourceAwsSecurityGroupRead(d *schema.ResourceData, meta interface{}) error {
ec2conn := meta.(*AWSClient).ec2conn

sgRaw, _, err := SGStateRefreshFunc(ec2conn, d.Id())()
if err != nil {
return err
}
if sgRaw == nil {
d.SetId("")
return nil
}

sg := sgRaw.(*ec2.SecurityGroupInfo)

// Gather our ingress rules
ingressMap := make(map[string]map[string]interface{})
for _, perm := range sg.IPPerms {
func resourceAwsSecurityGroupIPPermGather(d *schema.ResourceData, permissions []ec2.IPPerm) []map[string]interface{} {
ruleMap := make(map[string]map[string]interface{})
for _, perm := range permissions {
k := fmt.Sprintf("%s-%d-%d", perm.Protocol, perm.FromPort, perm.ToPort)
m, ok := ingressMap[k]
m, ok := ruleMap[k]
if !ok {
m = make(map[string]interface{})
ingressMap[k] = m
ruleMap[k] = m
}

m["from_port"] = perm.FromPort
Expand Down Expand Up @@ -200,22 +231,15 @@ func resourceAwsSecurityGroupRead(d *schema.ResourceData, meta interface{}) erro
m["security_groups"] = list
}
}
ingressRules := make([]map[string]interface{}, 0, len(ingressMap))
for _, m := range ingressMap {
ingressRules = append(ingressRules, m)
rules := make([]map[string]interface{}, 0, len(ruleMap))
for _, m := range ruleMap {
rules = append(rules, m)
}

d.Set("description", sg.Description)
d.Set("name", sg.Name)
d.Set("vpc_id", sg.VpcId)
d.Set("owner_id", sg.OwnerId)
d.Set("ingress", ingressRules)
d.Set("tags", tagsToMap(sg.Tags))

return nil
return rules
}

func resourceAwsSecurityGroupUpdate(d *schema.ResourceData, meta interface{}) error {
func resourceAwsSecurityGroupRead(d *schema.ResourceData, meta interface{}) error {
ec2conn := meta.(*AWSClient).ec2conn

sgRaw, _, err := SGStateRefreshFunc(ec2conn, d.Id())()
Expand All @@ -226,10 +250,26 @@ func resourceAwsSecurityGroupUpdate(d *schema.ResourceData, meta interface{}) er
d.SetId("")
return nil
}
group := sgRaw.(*ec2.SecurityGroupInfo).SecurityGroup

if d.HasChange("ingress") {
o, n := d.GetChange("ingress")
sg := sgRaw.(*ec2.SecurityGroupInfo)

ingressRules := resourceAwsSecurityGroupIPPermGather(d, sg.IPPerms)
egressRules := resourceAwsSecurityGroupIPPermGather(d, sg.IPPermsEgress)

d.Set("description", sg.Description)
d.Set("name", sg.Name)
d.Set("vpc_id", sg.VpcId)
d.Set("owner_id", sg.OwnerId)
d.Set("ingress", ingressRules)
d.Set("egress", egressRules)
d.Set("tags", tagsToMap(sg.Tags))

return nil
}

func resourceAwsSecurityGroupUpdateRules(d *schema.ResourceData, ruleset string, meta interface{}, group ec2.SecurityGroup) error {
if d.HasChange(ruleset) {
o, n := d.GetChange(ruleset)
if o == nil {
o = new(schema.Set)
}
Expand All @@ -252,29 +292,70 @@ func resourceAwsSecurityGroupUpdate(d *schema.ResourceData, meta interface{}) er
// adding is easier here, and Terraform should be fast enough to
// not have service issues.

if len(remove) > 0 {
// Revoke the old rules
_, err = ec2conn.RevokeSecurityGroup(group, remove)
if err != nil {
return fmt.Errorf("Error authorizing security group ingress rules: %s", err)
if len(remove) > 0 || len(add) > 0 {

ec2conn := meta.(*AWSClient).ec2conn

if len(remove) > 0 {
// Revoke the old rules
revoke := ec2conn.RevokeSecurityGroup
if ruleset == "egress" {
revoke = ec2conn.RevokeSecurityGroupEgress
}
_, err := revoke(group, remove)
if err != nil {
return fmt.Errorf("Error revoking security group %s rules: %s", ruleset, err)
}
}
}

if len(add) > 0 {
// Authorize the new rules
_, err := ec2conn.AuthorizeSecurityGroup(group, add)
if err != nil {
return fmt.Errorf("Error authorizing security group ingress rules: %s", err)
if len(add) > 0 {
// Authorize the new rules
authorize := ec2conn.AuthorizeSecurityGroup
if ruleset == "egress" {
authorize = ec2conn.AuthorizeSecurityGroupEgress
}
_, err := authorize(group, add)
if err != nil {
return fmt.Errorf("Error authorizing security group %s rules: %s", ruleset, err)
}
}
}
}

return nil
}

func resourceAwsSecurityGroupUpdate(d *schema.ResourceData, meta interface{}) error {
ec2conn := meta.(*AWSClient).ec2conn

sgRaw, _, err := SGStateRefreshFunc(ec2conn, d.Id())()
if err != nil {
return err
}
if sgRaw == nil {
d.SetId("")
return nil
}
group := sgRaw.(*ec2.SecurityGroupInfo).SecurityGroup

err = resourceAwsSecurityGroupUpdateRules(d, "ingress", ec2conn, group)
if err != nil {
return err
}

if d.Get("vpc_id") != nil {
err = resourceAwsSecurityGroupUpdateRules(d, "egress", ec2conn, group)
if err != nil {
return err
}
}

if err := setTags(ec2conn, d); err != nil {
return err
} else {
d.SetPartial("tags")
}

d.SetPartial("tags")

return resourceAwsSecurityGroupRead(d, meta)
}

Expand Down Expand Up @@ -307,7 +388,7 @@ func resourceAwsSecurityGroupDelete(d *schema.ResourceData, meta interface{}) er
})
}

func resourceAwsSecurityGroupIngressHash(v interface{}) int {
func resourceAwsSecurityGroupRuleHash(v interface{}) int {
var buf bytes.Buffer
m := v.(map[string]interface{})
buf.WriteString(fmt.Sprintf("%d-", m["from_port"].(int)))
Expand Down
17 changes: 17 additions & 0 deletions builtin/providers/aws/resource_aws_security_group_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,16 @@ func TestAccAWSSecurityGroup_vpc(t *testing.T) {
"aws_security_group.web", "ingress.332851786.cidr_blocks.#", "1"),
resource.TestCheckResourceAttr(
"aws_security_group.web", "ingress.332851786.cidr_blocks.0", "10.0.0.0/8"),
resource.TestCheckResourceAttr(
"aws_security_group.web", "egress.332851786.protocol", "tcp"),
resource.TestCheckResourceAttr(
"aws_security_group.web", "egress.332851786.from_port", "80"),
resource.TestCheckResourceAttr(
"aws_security_group.web", "egress.332851786.to_port", "8000"),
resource.TestCheckResourceAttr(
"aws_security_group.web", "egress.332851786.cidr_blocks.#", "1"),
resource.TestCheckResourceAttr(
"aws_security_group.web", "egress.332851786.cidr_blocks.0", "10.0.0.0/8"),
testCheck,
),
},
Expand Down Expand Up @@ -418,6 +428,13 @@ resource "aws_security_group" "web" {
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"]
}
}
`

Expand Down
22 changes: 22 additions & 0 deletions website/source/docs/providers/aws/r/security_group.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ resource "aws_security_group" "allow_all" {
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 65535
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
```

Expand Down Expand Up @@ -56,6 +63,9 @@ The following arguments are supported:
* `description` - (Required) The security group description.
* `ingress` - (Optional) Can be specified multiple times for each
ingress rule. Each ingress block supports fields documented below.
* `egress` - (Optional) Can be specified multiple times for each
egress rule. Each egress block supports fields documented below.
VPC only.
* `vpc_id` - (Optional) The VPC ID.
* `owner_id` - (Optional) The AWS Owner ID.

Expand All @@ -70,6 +80,17 @@ The `ingress` block supports:
* `to_port` - (Required) The end range port.
* `tags` - (Optional) A mapping of tags to assign to the resource.

The `egress` block supports:

* `cidr_blocks` - (Optional) List of CIDR blocks. Cannot be used with `security_groups`.
* `from_port` - (Required) The start port.
* `protocol` - (Required) The protocol.
* `security_groups` - (Optional) List of security group IDs. Cannot be used with `cidr_blocks`.
* `self` - (Optional) If true, the security group itself will be added as
a source to this egress rule.
* `to_port` - (Required) The end range port.
* `tags` - (Optional) A mapping of tags to assign to the resource.

## Attributes Reference

The following attributes are exported:
Expand All @@ -80,3 +101,4 @@ The following attributes are exported:
* `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.