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

[AWS] added support for multiple subnet associations to aws_network_acl #644

Closed
wants to merge 10 commits into from
79 changes: 57 additions & 22 deletions builtin/providers/aws/network_acl_entry.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package aws

import (
"github.com/mitchellh/goamz/ec2"
"fmt"
"github.com/mitchellh/goamz/ec2"
)

func expandNetworkAclEntries(configured []interface{}, entryType string) ([]ec2.NetworkAclEntry, error) {
Expand All @@ -11,22 +11,42 @@ func expandNetworkAclEntries(configured []interface{}, entryType string) ([]ec2.
data := eRaw.(map[string]interface{})
protocol := data["protocol"].(string)
_, ok := protocolIntegers()[protocol]
if(!ok){
if !ok {
return nil, fmt.Errorf("Invalid Protocol %s for rule %#v", protocol, data)
}
p := extractProtocolInteger(data["protocol"].(string))
e := ec2.NetworkAclEntry{
Protocol: p,
PortRange: ec2.PortRange{
From: data["from_port"].(int),
To: data["to_port"].(int),
},
Egress: (entryType == "egress"),
RuleAction: data["action"].(string),
RuleNumber: data["rule_no"].(int),
CidrBlock: data["cidr_block"].(string),
//In case of ICMP(1) protocol we have to provide IcmpCode
if p == 1 {
e := ec2.NetworkAclEntry{
Protocol: p,
PortRange: ec2.PortRange{
From: data["from_port"].(int),
To: data["to_port"].(int),
},
IcmpCode: ec2.IcmpCode{
Code: data["icmp_code"].(int),
Type: data["icmp_type"].(int),
},
Egress: (entryType == "egress"),
RuleAction: data["action"].(string),
RuleNumber: data["rule_no"].(int),
CidrBlock: data["cidr_block"].(string),
}
entries = append(entries, e)
} else {
e := ec2.NetworkAclEntry{
Protocol: p,
PortRange: ec2.PortRange{
From: data["from_port"].(int),
To: data["to_port"].(int),
},
Egress: (entryType == "egress"),
RuleAction: data["action"].(string),
RuleNumber: data["rule_no"].(int),
CidrBlock: data["cidr_block"].(string),
}
entries = append(entries, e)
}
entries = append(entries, e)
}

return entries, nil
Expand All @@ -37,14 +57,29 @@ func flattenNetworkAclEntries(list []ec2.NetworkAclEntry) []map[string]interface
entries := make([]map[string]interface{}, 0, len(list))

for _, entry := range list {
entries = append(entries, map[string]interface{}{
"from_port": entry.PortRange.From,
"to_port": entry.PortRange.To,
"action": entry.RuleAction,
"rule_no": entry.RuleNumber,
"protocol": extractProtocolString(entry.Protocol),
"cidr_block": entry.CidrBlock,
})
protocol := extractProtocolString(entry.Protocol)

if protocol == "icmp" {
entries = append(entries, map[string]interface{}{
"from_port": entry.PortRange.From,
"to_port": entry.PortRange.To,
"icmp_code": entry.IcmpCode.Code,
"icmp_type": entry.IcmpCode.Type,
"action": entry.RuleAction,
"rule_no": entry.RuleNumber,
"protocol": protocol,
"cidr_block": entry.CidrBlock,
})
} else {
entries = append(entries, map[string]interface{}{
"from_port": entry.PortRange.From,
"to_port": entry.PortRange.To,
"action": entry.RuleAction,
"rule_no": entry.RuleNumber,
"protocol": protocol,
"cidr_block": entry.CidrBlock,
})
}
}
return entries

Expand All @@ -69,7 +104,7 @@ func protocolIntegers() map[string]int {
"udp": 17,
"tcp": 6,
"icmp": 1,
"all": -1,
"all": -1,
}
return protocolIntegers
}
60 changes: 58 additions & 2 deletions builtin/providers/aws/network_acl_entry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,16 @@ func Test_expandNetworkAclEntry(t *testing.T) {
"action": "deny",
"rule_no": 2,
},
map[string]interface{}{
"protocol": "icmp",
"from_port": -1,
"to_port": -1,
"icmp_code": -1,
"icmp_type": -1,
"cidr_block": "0.0.0.0/0",
"action": "allow",
"rule_no": 3,
},
}
expanded, _ := expandNetworkAclEntries(input, "egress")

Expand All @@ -39,7 +49,10 @@ func Test_expandNetworkAclEntry(t *testing.T) {
RuleNumber: 1,
CidrBlock: "0.0.0.0/0",
Egress: true,
IcmpCode: ec2.IcmpCode{Code: 0, Type: 0},
IcmpCode: ec2.IcmpCode{
Code: 0,
Type: 0,
},
},
ec2.NetworkAclEntry{
Protocol: 6,
Expand All @@ -51,7 +64,25 @@ func Test_expandNetworkAclEntry(t *testing.T) {
RuleNumber: 2,
CidrBlock: "0.0.0.0/0",
Egress: true,
IcmpCode: ec2.IcmpCode{Code: 0, Type: 0},
IcmpCode: ec2.IcmpCode{
Code: 0,
Type: 0,
},
},
ec2.NetworkAclEntry{
Protocol: 1,
PortRange: ec2.PortRange{
From: -1,
To: -1,
},
RuleAction: "allow",
RuleNumber: 3,
CidrBlock: "0.0.0.0/0",
Egress: true,
IcmpCode: ec2.IcmpCode{
Code: -1,
Type: -1,
},
},
}

Expand Down Expand Up @@ -87,6 +118,21 @@ func Test_flattenNetworkAclEntry(t *testing.T) {
RuleNumber: 2,
CidrBlock: "0.0.0.0/0",
},
ec2.NetworkAclEntry{
Protocol: 1,
PortRange: ec2.PortRange{
From: -1,
To: -1,
},
RuleAction: "allow",
RuleNumber: 3,
CidrBlock: "0.0.0.0/0",
Egress: true,
IcmpCode: ec2.IcmpCode{
Code: -1,
Type: -1,
},
},
}
flattened := flattenNetworkAclEntries(apiInput)

Expand All @@ -107,6 +153,16 @@ func Test_flattenNetworkAclEntry(t *testing.T) {
"action": "deny",
"rule_no": 2,
},
map[string]interface{}{
"protocol": "icmp",
"from_port": -1,
"to_port": -1,
"icmp_code": -1,
"icmp_type": -1,
"cidr_block": "0.0.0.0/0",
"action": "allow",
"rule_no": 3,
},
}

if !reflect.DeepEqual(flattened, expected) {
Expand Down
96 changes: 77 additions & 19 deletions builtin/providers/aws/resource_aws_network_acl.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,17 @@ func resourceAwsNetworkAcl() *schema.Resource {
ForceNew: true,
Computed: false,
},
"subnet_id": &schema.Schema{
Type: schema.TypeString,
"subnets": &schema.Schema{
Type: schema.TypeSet,
Elem: &schema.Schema{Type: schema.TypeString},
Optional: true,
ForceNew: true,
Computed: false,
Computed: true,
Set: func(v interface{}) int {
return hashcode.String(v.(string))
},
},

"ingress": &schema.Schema{
Type: schema.TypeSet,
Required: false,
Expand All @@ -47,6 +52,14 @@ func resourceAwsNetworkAcl() *schema.Resource {
Type: schema.TypeInt,
Required: true,
},
"icmp_code": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
},
"icmp_type": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
},
"rule_no": &schema.Schema{
Type: schema.TypeInt,
Required: true,
Expand Down Expand Up @@ -81,6 +94,14 @@ func resourceAwsNetworkAcl() *schema.Resource {
Type: schema.TypeInt,
Required: true,
},
"icmp_code": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
},
"icmp_type": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
},
"rule_no": &schema.Schema{
Type: schema.TypeInt,
Required: true,
Expand Down Expand Up @@ -181,19 +202,46 @@ func resourceAwsNetworkAclUpdate(d *schema.ResourceData, meta interface{}) error
}
}

if d.HasChange("subnet_id") {
if d.HasChange("subnets") {
o, n := d.GetChange("subnets")
os := o.(*schema.Set)
ns := n.(*schema.Set)
remove := expandStringList(os.Difference(ns).List())
add := expandStringList(ns.Difference(os).List())

//associate new subnet with the acl.
_, n := d.GetChange("subnet_id")
newSubnet := n.(string)
association, err := findNetworkAclAssociation(newSubnet, ec2conn)
if err != nil {
return fmt.Errorf("Failed to update acl %s with subnet %s: %s", d.Id(), newSubnet, err)
if len(add) > 0 {
for _, subnet := range add {
association, err := findNetworkAclAssociation(subnet, ec2conn)
if err != nil {
return fmt.Errorf("Failed to get the ID of the current association between the original network ACL and the subnet %s: %s", subnet, err)
}
_, err = ec2conn.ReplaceNetworkAclAssociation(association.NetworkAclAssociationId, d.Id())
if err != nil {
return err
}
}
}
_, err = ec2conn.ReplaceNetworkAclAssociation(association.NetworkAclAssociationId, d.Id())
if err != nil {
return err
if len(remove) > 0 {
//we need to get a default newtwork acl and associate it with each subnet in remove list
defaultAcl, err := getDefaultNetworkAcl(d.Get("vpc_id").(string), ec2conn)
if err != nil {
return fmt.Errorf("Failed to get a default network acl for VPC %s: %s", d.Get("vpc_id"), err)
}

for _, subnet := range remove {
association, err := findNetworkAclAssociation(subnet, ec2conn)
if err != nil {
return fmt.Errorf("Failed to get the ID of the current association between the network ACL %s and the subnet %s: %s", d.Id(), subnet, err)
}
_, err = ec2conn.ReplaceNetworkAclAssociation(association.NetworkAclAssociationId, defaultAcl.NetworkAclId)
if err != nil {
return fmt.Errorf("Failed to disassociate subnet %s from network acl %s: %s", subnet, d.Id(), err)
}
}

}
d.SetPartial("subnets")

}

if err := setTags(ec2conn, d); err != nil {
Expand Down Expand Up @@ -226,6 +274,7 @@ func updateNetworkAclEntries(d *schema.ResourceData, entryType string, ec2conn *
}
for _, remove := range toBeDeleted {
// Delete old Acl
log.Printf("[DEBUG] Deleting acl rule #%d", remove.RuleNumber)
_, err := ec2conn.DeleteNetworkAclEntry(d.Id(), remove.RuleNumber, remove.Egress)
if err != nil {
return fmt.Errorf("Error deleting %s entry: %s", entryType, err)
Expand All @@ -238,6 +287,7 @@ func updateNetworkAclEntries(d *schema.ResourceData, entryType string, ec2conn *
}
for _, add := range toBeCreated {
// Add new Acl entry
log.Printf("[DEBUG] Adding acl rule #%d", add.RuleNumber)
_, err := ec2conn.CreateNetworkAclEntry(d.Id(), &add)
if err != nil {
return fmt.Errorf("Error creating %s entry: %s", entryType, err)
Expand All @@ -259,15 +309,23 @@ func resourceAwsNetworkAclDelete(d *schema.ResourceData, meta interface{}) error
case "DependencyViolation":
// In case of dependency violation, we remove the association between subnet and network acl.
// This means the subnet is attached to default acl of vpc.
association, err := findNetworkAclAssociation(d.Get("subnet_id").(string), ec2conn)
if err != nil {
return fmt.Errorf("Dependency violation: Cannot delete acl %s: %s", d.Id(), err)
}
//we need to get a default newtwork acl and associate it with each subnet in remove list
defaultAcl, err := getDefaultNetworkAcl(d.Get("vpc_id").(string), ec2conn)
if err != nil {
return fmt.Errorf("Dependency violation: Cannot delete acl %s: %s", d.Id(), err)
return fmt.Errorf("Failed to get a default network acl for VPC %s: %s", d.Get("vpc_id"), err)
}

remove := d.Get("subnets")
for _, subnet := range remove.(*schema.Set).List() {
association, err := findNetworkAclAssociation(subnet.(string), ec2conn)
if err != nil {
return fmt.Errorf("Failed to get the ID of the current association between the network ACL %s and the subnet %s: %s", d.Id(), subnet.(string), err)
}
_, err = ec2conn.ReplaceNetworkAclAssociation(association.NetworkAclAssociationId, defaultAcl.NetworkAclId)
if err != nil {
return fmt.Errorf("Failed to disassociate subnet %s from network acl %s: %s", subnet.(string), d.Id(), err)
}
}
_, err = ec2conn.ReplaceNetworkAclAssociation(association.NetworkAclAssociationId, defaultAcl.NetworkAclId)
return resource.RetryError{err}
default:
// Any other error, we want to quit the retry loop immediately
Expand Down
Loading