diff --git a/builtin/providers/aws/network_acl_entry.go b/builtin/providers/aws/network_acl_entry.go new file mode 100644 index 0000000000..bf53e30b27 --- /dev/null +++ b/builtin/providers/aws/network_acl_entry.go @@ -0,0 +1,68 @@ +package aws + +import ( + "github.com/mitchellh/goamz/ec2" +) + +func expandNetworkAclEntries(configured []interface{}, entryType string) []ec2.NetworkAclEntry { + entries := make([]ec2.NetworkAclEntry, 0, len(configured)) + for _, eRaw := range configured { + data := eRaw.(map[string]interface{}) + 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), + } + entries = append(entries, e) + } + + return entries + +} + +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, + }) + } + return entries + +} + +func extractProtocolInteger(protocol string) int { + return protocolIntegers()[protocol] +} + +func extractProtocolString(protocol int) string { + for key, value := range protocolIntegers() { + if value == protocol { + return key + } + } + return "" +} + +func protocolIntegers() map[string]int { + var protocolIntegers = make(map[string]int) + protocolIntegers = map[string]int{ + "udp": 17, + "tcp": 6, + "icmp": 1, + } + return protocolIntegers +} diff --git a/builtin/providers/aws/network_acl_entry_test.go b/builtin/providers/aws/network_acl_entry_test.go new file mode 100644 index 0000000000..0cf412dfdd --- /dev/null +++ b/builtin/providers/aws/network_acl_entry_test.go @@ -0,0 +1,119 @@ +package aws + +import ( + "reflect" + "testing" + + "github.com/mitchellh/goamz/ec2" +) + +func Test_expandNetworkAclEntry(t *testing.T) { + input := []interface{}{ + map[string]interface{}{ + "protocol": "tcp", + "from_port": 22, + "to_port": 22, + "cidr_block": "0.0.0.0/0", + "action": "deny", + "rule_no": 1, + }, + map[string]interface{}{ + "protocol": "tcp", + "from_port": 443, + "to_port": 443, + "cidr_block": "0.0.0.0/0", + "action": "deny", + "rule_no": 2, + }, + } + expanded := expandNetworkAclEntries(input, "egress") + + expected := []ec2.NetworkAclEntry{ + ec2.NetworkAclEntry{ + Protocol: 6, + PortRange: ec2.PortRange{ + From: 22, + To: 22, + }, + RuleAction: "deny", + RuleNumber: 1, + CidrBlock: "0.0.0.0/0", + Egress: true, + IcmpCode: ec2.IcmpCode{Code: 0, Type: 0}, + }, + ec2.NetworkAclEntry{ + Protocol: 6, + PortRange: ec2.PortRange{ + From: 443, + To: 443, + }, + RuleAction: "deny", + RuleNumber: 2, + CidrBlock: "0.0.0.0/0", + Egress: true, + IcmpCode: ec2.IcmpCode{Code: 0, Type: 0}, + }, + } + + if !reflect.DeepEqual(expanded, expected) { + t.Fatalf( + "Got:\n\n%#v\n\nExpected:\n\n%#v\n", + expanded, + expected) + } + +} + +func Test_flattenNetworkAclEntry(t *testing.T) { + + apiInput := []ec2.NetworkAclEntry{ + ec2.NetworkAclEntry{ + Protocol: 6, + PortRange: ec2.PortRange{ + From: 22, + To: 22, + }, + RuleAction: "deny", + RuleNumber: 1, + CidrBlock: "0.0.0.0/0", + }, + ec2.NetworkAclEntry{ + Protocol: 6, + PortRange: ec2.PortRange{ + From: 443, + To: 443, + }, + RuleAction: "deny", + RuleNumber: 2, + CidrBlock: "0.0.0.0/0", + }, + } + flattened := flattenNetworkAclEntries(apiInput) + + expected := []map[string]interface{}{ + map[string]interface{}{ + "protocol": "tcp", + "from_port": 22, + "to_port": 22, + "cidr_block": "0.0.0.0/0", + "action": "deny", + "rule_no": 1, + }, + map[string]interface{}{ + "protocol": "tcp", + "from_port": 443, + "to_port": 443, + "cidr_block": "0.0.0.0/0", + "action": "deny", + "rule_no": 2, + }, + } + + if !reflect.DeepEqual(flattened, expected) { + t.Fatalf( + "Got:\n\n%#v\n\nExpected:\n\n%#v\n", + flattened[0], + expected) + } + +} diff --git a/builtin/providers/aws/provider.go b/builtin/providers/aws/provider.go index d24f916119..60ae13189d 100644 --- a/builtin/providers/aws/provider.go +++ b/builtin/providers/aws/provider.go @@ -48,6 +48,7 @@ func Provider() terraform.ResourceProvider { "aws_instance": resourceAwsInstance(), "aws_internet_gateway": resourceAwsInternetGateway(), "aws_launch_configuration": resourceAwsLaunchConfiguration(), + "aws_network_acl": resourceAwsNetworkAcl(), "aws_route53_record": resourceAwsRoute53Record(), "aws_route53_zone": resourceAwsRoute53Zone(), "aws_route_table": resourceAwsRouteTable(), diff --git a/builtin/providers/aws/resource_aws_network_acl.go b/builtin/providers/aws/resource_aws_network_acl.go new file mode 100644 index 0000000000..c26bf5f191 --- /dev/null +++ b/builtin/providers/aws/resource_aws_network_acl.go @@ -0,0 +1,312 @@ +package aws + +import ( + "bytes" + "fmt" + "log" + "time" + + "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" + "github.com/mitchellh/goamz/ec2" +) + +func resourceAwsNetworkAcl() *schema.Resource { + + return &schema.Resource{ + Create: resourceAwsNetworkAclCreate, + Read: resourceAwsNetworkAclRead, + Delete: resourceAwsNetworkAclDelete, + Update: resourceAwsNetworkAclUpdate, + + Schema: map[string]*schema.Schema{ + "vpc_id": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + Computed: false, + }, + "subnet_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Computed: false, + }, + "ingress": &schema.Schema{ + Type: schema.TypeSet, + Required: false, + 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, + }, + "rule_no": &schema.Schema{ + Type: schema.TypeInt, + Required: true, + }, + "action": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + "protocol": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + "cidr_block": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + }, + }, + Set: resourceAwsNetworkAclEntryHash, + }, + "egress": &schema.Schema{ + Type: schema.TypeSet, + Required: false, + 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, + }, + "rule_no": &schema.Schema{ + Type: schema.TypeInt, + Required: true, + }, + "action": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + "protocol": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + "cidr_block": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + }, + }, + Set: resourceAwsNetworkAclEntryHash, + }, + }, + } +} + +func resourceAwsNetworkAclCreate(d *schema.ResourceData, meta interface{}) error { + + ec2conn := meta.(*AWSClient).ec2conn + + // Create the Network Acl + createOpts := &ec2.CreateNetworkAcl{ + VpcId: d.Get("vpc_id").(string), + } + + log.Printf("[DEBUG] Network Acl create config: %#v", createOpts) + resp, err := ec2conn.CreateNetworkAcl(createOpts) + if err != nil { + return fmt.Errorf("Error creating network acl: %s", err) + } + + // Get the ID and store it + networkAcl := &resp.NetworkAcl + d.SetId(networkAcl.NetworkAclId) + log.Printf("[INFO] Network Acl ID: %s", networkAcl.NetworkAclId) + + // Update rules and subnet association once acl is created + return resourceAwsNetworkAclUpdate(d, meta) +} + +func resourceAwsNetworkAclRead(d *schema.ResourceData, meta interface{}) error { + ec2conn := meta.(*AWSClient).ec2conn + + resp, err := ec2conn.NetworkAcls([]string{d.Id()}, ec2.NewFilter()) + + if err != nil { + return err + } + if resp == nil { + return nil + } + + networkAcl := &resp.NetworkAcls[0] + var ingressEntries []ec2.NetworkAclEntry + var egressEntries []ec2.NetworkAclEntry + + // separate the ingress and egress rules + for _, e := range networkAcl.EntrySet { + if e.Egress == true { + egressEntries = append(egressEntries, e) + } else { + ingressEntries = append(ingressEntries, e) + } + } + + d.Set("vpc_id", networkAcl.VpcId) + d.Set("ingress", ingressEntries) + d.Set("egress", egressEntries) + + return nil +} + +func resourceAwsNetworkAclUpdate(d *schema.ResourceData, meta interface{}) error { + ec2conn := meta.(*AWSClient).ec2conn + d.Partial(true) + + if d.HasChange("ingress") { + err := updateNetworkAclEntries(d, "ingress", ec2conn) + if err != nil { + return err + } + } + + if d.HasChange("egress") { + err := updateNetworkAclEntries(d, "egress", ec2conn) + if err != nil { + return err + } + } + + if d.HasChange("subnet_id") { + + //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: ", d.Id(), newSubnet, err) + } + _, err = ec2conn.ReplaceNetworkAclAssociation(association.NetworkAclAssociationId, d.Id()) + if err != nil { + return err + } + } + + d.Partial(false) + return resourceAwsNetworkAclRead(d, meta) +} + +func updateNetworkAclEntries(d *schema.ResourceData, entryType string, ec2conn *ec2.EC2) error { + + o, n := d.GetChange(entryType) + + if o == nil { + o = new(schema.Set) + } + if n == nil { + n = new(schema.Set) + } + + os := o.(*schema.Set) + ns := n.(*schema.Set) + toBeDeleted := expandNetworkAclEntries(os.Difference(ns).List(), entryType) + toBeCreated := expandNetworkAclEntries(ns.Difference(os).List(), entryType) + for _, remove := range toBeDeleted { + // Delete old Acl + _, err := ec2conn.DeleteNetworkAclEntry(d.Id(), remove.RuleNumber, remove.Egress) + if err != nil { + return fmt.Errorf("Error deleting %s entry: %s", entryType, err) + } + } + + for _, add := range toBeCreated { + // Add new Acl entry + _, err := ec2conn.CreateNetworkAclEntry(d.Id(), &add) + if err != nil { + return fmt.Errorf("Error creating %s entry: %s", entryType, err) + } + } + return nil +} + +func resourceAwsNetworkAclDelete(d *schema.ResourceData, meta interface{}) error { + ec2conn := meta.(*AWSClient).ec2conn + + log.Printf("[INFO] Deleting Network Acl: %s", d.Id()) + return resource.Retry(5*time.Minute, func() error { + if _, err := ec2conn.DeleteNetworkAcl(d.Id()); err != nil { + ec2err := err.(*ec2.Error) + switch ec2err.Code { + case "InvalidNetworkAclID.NotFound": + return nil + 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("Depedency voilation: Can not delete acl: %s", d.Id(), err) + } + defaultAcl, err := getDefaultNetworkAcl(d.Get("vpc_id").(string), ec2conn) + if err != nil { + return fmt.Errorf("Depedency voilation: Can not delete acl %s", 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 + return resource.RetryError{err} + } + } + log.Printf("[Info] Deleted network ACL %s successfully", d.Id()) + return nil + }) +} + +func resourceAwsNetworkAclEntryHash(v interface{}) int { + var buf bytes.Buffer + m := v.(map[string]interface{}) + buf.WriteString(fmt.Sprintf("%d-", m["from_port"].(int))) + buf.WriteString(fmt.Sprintf("%s-", m["to_port"].(int))) + buf.WriteString(fmt.Sprintf("%d-", m["rule_no"].(int))) + buf.WriteString(fmt.Sprintf("%s-", m["action"].(string))) + buf.WriteString(fmt.Sprintf("%s-", m["protocol"].(string))) + buf.WriteString(fmt.Sprintf("%s-", m["cidr_block"].(string))) + + if v, ok := m["ssl_certificate_id"]; ok { + buf.WriteString(fmt.Sprintf("%s-", v.(string))) + } + + return hashcode.String(buf.String()) +} + +func getDefaultNetworkAcl(vpc_id string, ec2conn *ec2.EC2) (defaultAcl *ec2.NetworkAcl, err error) { + filter := ec2.NewFilter() + filter.Add("default", "true") + filter.Add("vpc-id", vpc_id) + + resp, err := ec2conn.NetworkAcls([]string{}, filter) + + if err != nil { + return nil, err + } + return &resp.NetworkAcls[0], nil +} + +func findNetworkAclAssociation(subnetId string, ec2conn *ec2.EC2) (networkAclAssociation *ec2.NetworkAclAssociation, err error) { + filter := ec2.NewFilter() + filter.Add("association.subnet-id", subnetId) + + resp, err := ec2conn.NetworkAcls([]string{}, filter) + + if err != nil { + return nil, err + } + for _, association := range resp.NetworkAcls[0].AssociationSet { + if association.SubnetId == subnetId { + return &association, nil + } + } + return nil, fmt.Errorf("could not find association for subnet %s ", subnetId) +} diff --git a/builtin/providers/aws/resource_aws_network_acl_test.go b/builtin/providers/aws/resource_aws_network_acl_test.go new file mode 100644 index 0000000000..2186aa74ee --- /dev/null +++ b/builtin/providers/aws/resource_aws_network_acl_test.go @@ -0,0 +1,470 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/terraform" + "github.com/mitchellh/goamz/ec2" + // "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/resource" + // "github.com/hashicorp/terraform/helper/schema" +) + +func TestAccAWSNetworkAclsWithEgressAndIngressRules(t *testing.T) { + var networkAcl ec2.NetworkAcl + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSNetworkAclDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSNetworkAclEgressNIngressConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSNetworkAclExists("aws_network_acl.bar", &networkAcl), + resource.TestCheckResourceAttr( + "aws_network_acl.bar", "ingress.0.protocol", "tcp"), + resource.TestCheckResourceAttr( + "aws_network_acl.bar", "ingress.0.rule_no", "1"), + resource.TestCheckResourceAttr( + "aws_network_acl.bar", "ingress.0.from_port", "80"), + resource.TestCheckResourceAttr( + "aws_network_acl.bar", "ingress.0.to_port", "80"), + resource.TestCheckResourceAttr( + "aws_network_acl.bar", "ingress.0.action", "allow"), + resource.TestCheckResourceAttr( + "aws_network_acl.bar", "ingress.0.cidr_block", "10.3.10.3/18"), + resource.TestCheckResourceAttr( + "aws_network_acl.bar", "egress.0.protocol", "tcp"), + resource.TestCheckResourceAttr( + "aws_network_acl.bar", "egress.0.rule_no", "2"), + resource.TestCheckResourceAttr( + "aws_network_acl.bar", "egress.0.from_port", "443"), + resource.TestCheckResourceAttr( + "aws_network_acl.bar", "egress.0.to_port", "443"), + resource.TestCheckResourceAttr( + "aws_network_acl.bar", "egress.0.cidr_block", "10.3.2.3/18"), + resource.TestCheckResourceAttr( + "aws_network_acl.bar", "egress.0.action", "allow"), + ), + }, + }, + }) +} + +func TestAccAWSNetworkAclsOnlyIngressRules(t *testing.T) { + var networkAcl ec2.NetworkAcl + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSNetworkAclDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSNetworkAclIngressConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSNetworkAclExists("aws_network_acl.foos", &networkAcl), + // testAccCheckSubnetAssociation("aws_network_acl.foos", "aws_subnet.blob"), + resource.TestCheckResourceAttr( + "aws_network_acl.foos", "ingress.0.protocol", "tcp"), + resource.TestCheckResourceAttr( + "aws_network_acl.foos", "ingress.0.rule_no", "2"), + resource.TestCheckResourceAttr( + "aws_network_acl.foos", "ingress.0.from_port", "0"), + resource.TestCheckResourceAttr( + "aws_network_acl.foos", "ingress.0.to_port", "22"), + resource.TestCheckResourceAttr( + "aws_network_acl.foos", "ingress.0.action", "deny"), + resource.TestCheckResourceAttr( + "aws_network_acl.foos", "ingress.0.cidr_block", "10.2.2.3/18"), + ), + }, + }, + }) +} + +const testAccAWSNetworkAclIngressConfig = ` +resource "aws_vpc" "foo" { + cidr_block = "10.1.0.0/16" +} +resource "aws_subnet" "blob" { + cidr_block = "10.1.1.0/24" + vpc_id = "${aws_vpc.foo.id}" + map_public_ip_on_launch = true +} +resource "aws_network_acl" "foos" { + vpc_id = "${aws_vpc.foo.id}" + ingress = { + protocol = "tcp" + rule_no = 1 + action = "deny" + cidr_block = "10.2.2.3/18" + from_port = 0 + to_port = 22 + } + ingress = { + protocol = "tcp" + rule_no = 2 + action = "deny" + cidr_block = "10.2.2.3/18" + from_port = 443 + to_port = 443 + } + subnet_id = "${aws_subnet.blob.id}" +} +` +const testAccAWSNetworkAclIngressConfigChange = ` +resource "aws_vpc" "foo" { + cidr_block = "10.1.0.0/16" +} +resource "aws_subnet" "blob" { + cidr_block = "10.1.1.0/24" + vpc_id = "${aws_vpc.foo.id}" + map_public_ip_on_launch = true +} +resource "aws_network_acl" "foos" { + vpc_id = "${aws_vpc.foo.id}" + ingress = { + protocol = "tcp" + rule_no = 1 + action = "deny" + cidr_block = "10.2.2.3/18" + from_port = 0 + to_port = 22 + } + subnet_id = "${aws_subnet.blob.id}" +} +` + +func TestAccAWSNetworkAclsOnlyIngressRulesChange(t *testing.T) { + var networkAcl ec2.NetworkAcl + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSNetworkAclDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSNetworkAclIngressConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSNetworkAclExists("aws_network_acl.foos", &networkAcl), + testIngressRuleLength(&networkAcl, 2), + resource.TestCheckResourceAttr( + "aws_network_acl.foos", "ingress.0.protocol", "tcp"), + resource.TestCheckResourceAttr( + "aws_network_acl.foos", "ingress.0.rule_no", "1"), + resource.TestCheckResourceAttr( + "aws_network_acl.foos", "ingress.0.from_port", "0"), + resource.TestCheckResourceAttr( + "aws_network_acl.foos", "ingress.0.to_port", "22"), + resource.TestCheckResourceAttr( + "aws_network_acl.foos", "ingress.0.action", "deny"), + resource.TestCheckResourceAttr( + "aws_network_acl.foos", "ingress.0.cidr_block", "10.2.2.3/18"), + resource.TestCheckResourceAttr( + "aws_network_acl.foos", "ingress.1.from_port", "443"), + resource.TestCheckResourceAttr( + "aws_network_acl.foos", "ingress.1.rule_no", "2"), + ), + }, + resource.TestStep{ + Config: testAccAWSNetworkAclIngressConfigChange, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSNetworkAclExists("aws_network_acl.foos", &networkAcl), + testIngressRuleLength(&networkAcl, 1), + resource.TestCheckResourceAttr( + "aws_network_acl.foos", "ingress.0.protocol", "tcp"), + resource.TestCheckResourceAttr( + "aws_network_acl.foos", "ingress.0.rule_no", "2"), + resource.TestCheckResourceAttr( + "aws_network_acl.foos", "ingress.0.from_port", "0"), + resource.TestCheckResourceAttr( + "aws_network_acl.foos", "ingress.0.to_port", "22"), + resource.TestCheckResourceAttr( + "aws_network_acl.foos", "ingress.0.action", "deny"), + resource.TestCheckResourceAttr( + "aws_network_acl.foos", "ingress.0.cidr_block", "10.2.2.3/18"), + ), + }, + }, + }) +} + +func TestAccAWSNetworkAclsOnlyEgressRules(t *testing.T) { + var networkAcl ec2.NetworkAcl + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSNetworkAclDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSNetworkAclEgressConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSNetworkAclExists("aws_network_acl.bond", &networkAcl), + ), + }, + }, + }) +} + + + +func TestAccNetworkAcl_SubnetChange(t *testing.T) { + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSNetworkAclDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSNetworkAclSubnetConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckSubnetIsAssociatedWithAcl("aws_network_acl.bar", "aws_subnet.old"), + ), + }, + resource.TestStep{ + Config: testAccAWSNetworkAclSubnetConfigChange, + Check: resource.ComposeTestCheckFunc( + testAccCheckSubnetIsNotAssociatedWithAcl("aws_network_acl.bar", "aws_subnet.old"), + testAccCheckSubnetIsAssociatedWithAcl("aws_network_acl.bar", "aws_subnet.new"), + ), + }, + }, + }) + +} + +func testAccCheckAWSNetworkAclDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).ec2conn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_network" { + continue + } + + // Retrieve the network acl + resp, err := conn.NetworkAcls([]string{rs.Primary.ID}, ec2.NewFilter()) + if err == nil { + if len(resp.NetworkAcls) > 0 && resp.NetworkAcls[0].NetworkAclId == rs.Primary.ID { + return fmt.Errorf("Network Acl (%s) still exists.", rs.Primary.ID) + } + + return nil + } + + ec2err, ok := err.(*ec2.Error) + if !ok { + return err + } + // Confirm error code is what we want + if ec2err.Code != "InvalidNetworkAclID.NotFound" { + return err + } + } + + return nil +} + +func testAccCheckAWSNetworkAclExists(n string, networkAcl *ec2.NetworkAcl) 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 + + resp, err := conn.NetworkAcls([]string{rs.Primary.ID}, nil) + if err != nil { + return err + } + + if len(resp.NetworkAcls) > 0 && resp.NetworkAcls[0].NetworkAclId == rs.Primary.ID { + *networkAcl = resp.NetworkAcls[0] + return nil + } + + return fmt.Errorf("Network Acls not found") + } +} + +func testIngressRuleLength(networkAcl *ec2.NetworkAcl, length int) resource.TestCheckFunc { + return func(s *terraform.State) error{ + var ingressEntries []ec2.NetworkAclEntry + for _, e := range networkAcl.EntrySet { + if e.Egress == false { + ingressEntries = append(ingressEntries, e) + } + } + if len(ingressEntries) != length { + return fmt.Errorf("Invalid number of ingress entries found; count = %s", len(ingressEntries)) + } + return nil + } +} + +func testAccCheckSubnetIsAssociatedWithAcl(acl string, sub string) resource.TestCheckFunc { + return func(s *terraform.State) error { + networkAcl := s.RootModule().Resources[acl] + subnet := s.RootModule().Resources[sub] + + conn := testAccProvider.Meta().(*AWSClient).ec2conn + filter := ec2.NewFilter() + filter.Add("association.subnet-id", subnet.Primary.ID) + resp, err := conn.NetworkAcls([]string{networkAcl.Primary.ID}, filter) + + if err != nil { + return err + } + if len(resp.NetworkAcls) > 0 { + return nil + } + + r, _ := conn.NetworkAcls([]string{}, ec2.NewFilter()) + fmt.Printf("\n\nall acls\n %s\n\n", r.NetworkAcls) + conn.NetworkAcls([]string{}, filter) + + return fmt.Errorf("Network Acl %s is not associated with subnet %s", acl, sub) + } +} + +func testAccCheckSubnetIsNotAssociatedWithAcl(acl string, subnet string) resource.TestCheckFunc { + return func(s *terraform.State) error { + networkAcl := s.RootModule().Resources[acl] + subnet := s.RootModule().Resources[subnet] + + conn := testAccProvider.Meta().(*AWSClient).ec2conn + filter := ec2.NewFilter() + filter.Add("association.subnet-id", subnet.Primary.ID) + resp, err := conn.NetworkAcls([]string{networkAcl.Primary.ID}, filter) + + if err != nil { + return err + } + if len(resp.NetworkAcls) > 0 { + return fmt.Errorf("Network Acl %s is still associated with subnet %s", acl, subnet) + } + return nil + } +} + + + +const testAccAWSNetworkAclEgressConfig = ` +resource "aws_vpc" "foo" { + cidr_block = "10.2.0.0/16" +} +resource "aws_subnet" "blob" { + cidr_block = "10.2.0.0/24" + vpc_id = "${aws_vpc.foo.id}" + map_public_ip_on_launch = true +} +resource "aws_network_acl" "bond" { + vpc_id = "${aws_vpc.foo.id}" + egress = { + protocol = "tcp" + rule_no = 2 + action = "allow" + cidr_block = "10.2.2.3/18" + from_port = 443 + to_port = 443 + } + + egress = { + protocol = "tcp" + rule_no = 1 + action = "allow" + cidr_block = "10.2.10.3/18" + from_port = 80 + to_port = 80 + } + + egress = { + protocol = "tcp" + rule_no = 3 + action = "allow" + cidr_block = "10.2.10.3/18" + from_port = 22 + to_port = 22 + } +} +` + +const testAccAWSNetworkAclEgressNIngressConfig = ` +resource "aws_vpc" "foo" { + cidr_block = "10.3.0.0/16" +} +resource "aws_subnet" "blob" { + cidr_block = "10.3.0.0/24" + vpc_id = "${aws_vpc.foo.id}" + map_public_ip_on_launch = true +} +resource "aws_network_acl" "bar" { + vpc_id = "${aws_vpc.foo.id}" + egress = { + protocol = "tcp" + rule_no = 2 + action = "allow" + cidr_block = "10.3.2.3/18" + from_port = 443 + to_port = 443 + } + + ingress = { + protocol = "tcp" + rule_no = 1 + action = "allow" + cidr_block = "10.3.10.3/18" + from_port = 80 + to_port = 80 + } +} +` +const testAccAWSNetworkAclSubnetConfig = ` +resource "aws_vpc" "foo" { + cidr_block = "10.1.0.0/16" +} +resource "aws_subnet" "old" { + cidr_block = "10.1.111.0/24" + vpc_id = "${aws_vpc.foo.id}" + map_public_ip_on_launch = true +} +resource "aws_subnet" "new" { + cidr_block = "10.1.1.0/24" + vpc_id = "${aws_vpc.foo.id}" + map_public_ip_on_launch = true +} +resource "aws_network_acl" "roll" { + vpc_id = "${aws_vpc.foo.id}" + subnet_id = "${aws_subnet.new.id}" +} +resource "aws_network_acl" "bar" { + vpc_id = "${aws_vpc.foo.id}" + subnet_id = "${aws_subnet.old.id}" +} +` + +const testAccAWSNetworkAclSubnetConfigChange = ` +resource "aws_vpc" "foo" { + cidr_block = "10.1.0.0/16" +} +resource "aws_subnet" "old" { + cidr_block = "10.1.111.0/24" + vpc_id = "${aws_vpc.foo.id}" + map_public_ip_on_launch = true +} +resource "aws_subnet" "new" { + cidr_block = "10.1.1.0/24" + vpc_id = "${aws_vpc.foo.id}" + map_public_ip_on_launch = true +} +resource "aws_network_acl" "bar" { + vpc_id = "${aws_vpc.foo.id}" + subnet_id = "${aws_subnet.new.id}" +} +`