github.com/markdia/terraform@v0.5.1-0.20150508012022-f1ae920aa970/builtin/providers/aws/resource_aws_network_acl.go (about) 1 package aws 2 3 import ( 4 "bytes" 5 "fmt" 6 "log" 7 "strconv" 8 "time" 9 10 "github.com/awslabs/aws-sdk-go/aws" 11 "github.com/awslabs/aws-sdk-go/service/ec2" 12 "github.com/hashicorp/terraform/helper/hashcode" 13 "github.com/hashicorp/terraform/helper/resource" 14 "github.com/hashicorp/terraform/helper/schema" 15 ) 16 17 func resourceAwsNetworkAcl() *schema.Resource { 18 19 return &schema.Resource{ 20 Create: resourceAwsNetworkAclCreate, 21 Read: resourceAwsNetworkAclRead, 22 Delete: resourceAwsNetworkAclDelete, 23 Update: resourceAwsNetworkAclUpdate, 24 25 Schema: map[string]*schema.Schema{ 26 "vpc_id": &schema.Schema{ 27 Type: schema.TypeString, 28 Required: true, 29 ForceNew: true, 30 Computed: false, 31 }, 32 "subnet_id": &schema.Schema{ 33 Type: schema.TypeString, 34 Optional: true, 35 ForceNew: true, 36 Computed: false, 37 }, 38 "ingress": &schema.Schema{ 39 Type: schema.TypeSet, 40 Required: false, 41 Optional: true, 42 Elem: &schema.Resource{ 43 Schema: map[string]*schema.Schema{ 44 "from_port": &schema.Schema{ 45 Type: schema.TypeInt, 46 Required: true, 47 }, 48 "to_port": &schema.Schema{ 49 Type: schema.TypeInt, 50 Required: true, 51 }, 52 "rule_no": &schema.Schema{ 53 Type: schema.TypeInt, 54 Required: true, 55 }, 56 "action": &schema.Schema{ 57 Type: schema.TypeString, 58 Required: true, 59 }, 60 "protocol": &schema.Schema{ 61 Type: schema.TypeString, 62 Required: true, 63 }, 64 "cidr_block": &schema.Schema{ 65 Type: schema.TypeString, 66 Optional: true, 67 }, 68 }, 69 }, 70 Set: resourceAwsNetworkAclEntryHash, 71 }, 72 "egress": &schema.Schema{ 73 Type: schema.TypeSet, 74 Required: false, 75 Optional: true, 76 Elem: &schema.Resource{ 77 Schema: map[string]*schema.Schema{ 78 "from_port": &schema.Schema{ 79 Type: schema.TypeInt, 80 Required: true, 81 }, 82 "to_port": &schema.Schema{ 83 Type: schema.TypeInt, 84 Required: true, 85 }, 86 "rule_no": &schema.Schema{ 87 Type: schema.TypeInt, 88 Required: true, 89 }, 90 "action": &schema.Schema{ 91 Type: schema.TypeString, 92 Required: true, 93 }, 94 "protocol": &schema.Schema{ 95 Type: schema.TypeString, 96 Required: true, 97 }, 98 "cidr_block": &schema.Schema{ 99 Type: schema.TypeString, 100 Optional: true, 101 }, 102 }, 103 }, 104 Set: resourceAwsNetworkAclEntryHash, 105 }, 106 "tags": tagsSchema(), 107 }, 108 } 109 } 110 111 func resourceAwsNetworkAclCreate(d *schema.ResourceData, meta interface{}) error { 112 113 conn := meta.(*AWSClient).ec2conn 114 115 // Create the Network Acl 116 createOpts := &ec2.CreateNetworkACLInput{ 117 VPCID: aws.String(d.Get("vpc_id").(string)), 118 } 119 120 log.Printf("[DEBUG] Network Acl create config: %#v", createOpts) 121 resp, err := conn.CreateNetworkACL(createOpts) 122 if err != nil { 123 return fmt.Errorf("Error creating network acl: %s", err) 124 } 125 126 // Get the ID and store it 127 networkAcl := resp.NetworkACL 128 d.SetId(*networkAcl.NetworkACLID) 129 log.Printf("[INFO] Network Acl ID: %s", *networkAcl.NetworkACLID) 130 131 // Update rules and subnet association once acl is created 132 return resourceAwsNetworkAclUpdate(d, meta) 133 } 134 135 func resourceAwsNetworkAclRead(d *schema.ResourceData, meta interface{}) error { 136 conn := meta.(*AWSClient).ec2conn 137 138 resp, err := conn.DescribeNetworkACLs(&ec2.DescribeNetworkACLsInput{ 139 NetworkACLIDs: []*string{aws.String(d.Id())}, 140 }) 141 142 if err != nil { 143 return err 144 } 145 if resp == nil { 146 return nil 147 } 148 149 networkAcl := resp.NetworkACLs[0] 150 var ingressEntries []*ec2.NetworkACLEntry 151 var egressEntries []*ec2.NetworkACLEntry 152 153 // separate the ingress and egress rules 154 for _, e := range networkAcl.Entries { 155 // Skip the default rules added by AWS. They can be neither 156 // configured or deleted by users. 157 if *e.RuleNumber == 32767 { 158 continue 159 } 160 161 if *e.Egress == true { 162 egressEntries = append(egressEntries, e) 163 } else { 164 ingressEntries = append(ingressEntries, e) 165 } 166 } 167 168 d.Set("vpc_id", networkAcl.VPCID) 169 d.Set("tags", tagsToMapSDK(networkAcl.Tags)) 170 171 if err := d.Set("ingress", networkAclEntriesToMapList(ingressEntries)); err != nil { 172 return err 173 } 174 if err := d.Set("egress", networkAclEntriesToMapList(egressEntries)); err != nil { 175 return err 176 } 177 178 return nil 179 } 180 181 func resourceAwsNetworkAclUpdate(d *schema.ResourceData, meta interface{}) error { 182 conn := meta.(*AWSClient).ec2conn 183 d.Partial(true) 184 185 if d.HasChange("ingress") { 186 err := updateNetworkAclEntries(d, "ingress", conn) 187 if err != nil { 188 return err 189 } 190 } 191 192 if d.HasChange("egress") { 193 err := updateNetworkAclEntries(d, "egress", conn) 194 if err != nil { 195 return err 196 } 197 } 198 199 if d.HasChange("subnet_id") { 200 //associate new subnet with the acl. 201 _, n := d.GetChange("subnet_id") 202 newSubnet := n.(string) 203 association, err := findNetworkAclAssociation(newSubnet, conn) 204 if err != nil { 205 return fmt.Errorf("Failed to update acl %s with subnet %s: %s", d.Id(), newSubnet, err) 206 } 207 _, err = conn.ReplaceNetworkACLAssociation(&ec2.ReplaceNetworkACLAssociationInput{ 208 AssociationID: association.NetworkACLAssociationID, 209 NetworkACLID: aws.String(d.Id()), 210 }) 211 if err != nil { 212 return err 213 } 214 } 215 216 if err := setTagsSDK(conn, d); err != nil { 217 return err 218 } else { 219 d.SetPartial("tags") 220 } 221 222 d.Partial(false) 223 return resourceAwsNetworkAclRead(d, meta) 224 } 225 226 func updateNetworkAclEntries(d *schema.ResourceData, entryType string, conn *ec2.EC2) error { 227 228 o, n := d.GetChange(entryType) 229 230 if o == nil { 231 o = new(schema.Set) 232 } 233 if n == nil { 234 n = new(schema.Set) 235 } 236 237 os := o.(*schema.Set) 238 ns := n.(*schema.Set) 239 240 toBeDeleted, err := expandNetworkAclEntries(os.Difference(ns).List(), entryType) 241 if err != nil { 242 return err 243 } 244 for _, remove := range toBeDeleted { 245 246 // AWS includes default rules with all network ACLs that can be 247 // neither modified nor destroyed. They have a custom rule 248 // number that is out of bounds for any other rule. If we 249 // encounter it, just continue. There's no work to be done. 250 if *remove.RuleNumber == 32767 { 251 continue 252 } 253 254 // Delete old Acl 255 _, err := conn.DeleteNetworkACLEntry(&ec2.DeleteNetworkACLEntryInput{ 256 NetworkACLID: aws.String(d.Id()), 257 RuleNumber: remove.RuleNumber, 258 Egress: remove.Egress, 259 }) 260 if err != nil { 261 return fmt.Errorf("Error deleting %s entry: %s", entryType, err) 262 } 263 } 264 265 toBeCreated, err := expandNetworkAclEntries(ns.Difference(os).List(), entryType) 266 if err != nil { 267 return err 268 } 269 for _, add := range toBeCreated { 270 // Protocol -1 rules don't store ports in AWS. Thus, they'll always 271 // hash differently when being read out of the API. Force the user 272 // to set from_port and to_port to 0 for these rules, to keep the 273 // hashing consistent. 274 if *add.Protocol == "-1" { 275 to := *add.PortRange.To 276 from := *add.PortRange.From 277 expected := &expectedPortPair{ 278 to_port: 0, 279 from_port: 0, 280 } 281 if ok := validatePorts(to, from, *expected); !ok { 282 return fmt.Errorf( 283 "to_port (%d) and from_port (%d) must both be 0 to use the the 'all' \"-1\" protocol!", 284 to, from) 285 } 286 } 287 288 // AWS mutates the CIDR block into a network implied by the IP and 289 // mask provided. This results in hashing inconsistencies between 290 // the local config file and the state returned by the API. Error 291 // if the user provides a CIDR block with an inappropriate mask 292 if err := validateCIDRBlock(*add.CIDRBlock); err != nil { 293 return err 294 } 295 296 // Add new Acl entry 297 _, connErr := conn.CreateNetworkACLEntry(&ec2.CreateNetworkACLEntryInput{ 298 NetworkACLID: aws.String(d.Id()), 299 CIDRBlock: add.CIDRBlock, 300 Egress: add.Egress, 301 PortRange: add.PortRange, 302 Protocol: add.Protocol, 303 RuleAction: add.RuleAction, 304 RuleNumber: add.RuleNumber, 305 }) 306 if connErr != nil { 307 return fmt.Errorf("Error creating %s entry: %s", entryType, err) 308 } 309 } 310 return nil 311 } 312 313 func resourceAwsNetworkAclDelete(d *schema.ResourceData, meta interface{}) error { 314 conn := meta.(*AWSClient).ec2conn 315 316 log.Printf("[INFO] Deleting Network Acl: %s", d.Id()) 317 return resource.Retry(5*time.Minute, func() error { 318 _, err := conn.DeleteNetworkACL(&ec2.DeleteNetworkACLInput{ 319 NetworkACLID: aws.String(d.Id()), 320 }) 321 if err != nil { 322 ec2err := err.(aws.APIError) 323 switch ec2err.Code { 324 case "InvalidNetworkAclID.NotFound": 325 return nil 326 case "DependencyViolation": 327 // In case of dependency violation, we remove the association between subnet and network acl. 328 // This means the subnet is attached to default acl of vpc. 329 association, err := findNetworkAclAssociation(d.Get("subnet_id").(string), conn) 330 if err != nil { 331 return resource.RetryError{Err: fmt.Errorf("Dependency violation: Cannot delete acl %s: %s", d.Id(), err)} 332 } 333 defaultAcl, err := getDefaultNetworkAcl(d.Get("vpc_id").(string), conn) 334 if err != nil { 335 return resource.RetryError{Err: fmt.Errorf("Dependency violation: Cannot delete acl %s: %s", d.Id(), err)} 336 } 337 _, err = conn.ReplaceNetworkACLAssociation(&ec2.ReplaceNetworkACLAssociationInput{ 338 AssociationID: association.NetworkACLAssociationID, 339 NetworkACLID: defaultAcl.NetworkACLID, 340 }) 341 return resource.RetryError{Err: err} 342 default: 343 // Any other error, we want to quit the retry loop immediately 344 return resource.RetryError{Err: err} 345 } 346 } 347 log.Printf("[Info] Deleted network ACL %s successfully", d.Id()) 348 return nil 349 }) 350 } 351 352 func resourceAwsNetworkAclEntryHash(v interface{}) int { 353 var buf bytes.Buffer 354 m := v.(map[string]interface{}) 355 buf.WriteString(fmt.Sprintf("%d-", m["from_port"].(int))) 356 buf.WriteString(fmt.Sprintf("%d-", m["to_port"].(int))) 357 buf.WriteString(fmt.Sprintf("%d-", m["rule_no"].(int))) 358 buf.WriteString(fmt.Sprintf("%s-", m["action"].(string))) 359 360 // The AWS network ACL API only speaks protocol numbers, and that's 361 // all we store. Never hash a protocol name. 362 protocol := m["protocol"].(string) 363 if _, err := strconv.Atoi(m["protocol"].(string)); err != nil { 364 // We're a protocol name. Look up the number. 365 buf.WriteString(fmt.Sprintf("%d-", protocolIntegers()[protocol])) 366 } else { 367 // We're a protocol number. Pass the value through. 368 buf.WriteString(fmt.Sprintf("%s-", protocol)) 369 } 370 371 buf.WriteString(fmt.Sprintf("%s-", m["cidr_block"].(string))) 372 373 if v, ok := m["ssl_certificate_id"]; ok { 374 buf.WriteString(fmt.Sprintf("%s-", v.(string))) 375 } 376 377 return hashcode.String(buf.String()) 378 } 379 380 func getDefaultNetworkAcl(vpc_id string, conn *ec2.EC2) (defaultAcl *ec2.NetworkACL, err error) { 381 resp, err := conn.DescribeNetworkACLs(&ec2.DescribeNetworkACLsInput{ 382 Filters: []*ec2.Filter{ 383 &ec2.Filter{ 384 Name: aws.String("default"), 385 Values: []*string{aws.String("true")}, 386 }, 387 &ec2.Filter{ 388 Name: aws.String("vpc-id"), 389 Values: []*string{aws.String(vpc_id)}, 390 }, 391 }, 392 }) 393 394 if err != nil { 395 return nil, err 396 } 397 return resp.NetworkACLs[0], nil 398 } 399 400 func findNetworkAclAssociation(subnetId string, conn *ec2.EC2) (networkAclAssociation *ec2.NetworkACLAssociation, err error) { 401 resp, err := conn.DescribeNetworkACLs(&ec2.DescribeNetworkACLsInput{ 402 Filters: []*ec2.Filter{ 403 &ec2.Filter{ 404 Name: aws.String("association.subnet-id"), 405 Values: []*string{aws.String(subnetId)}, 406 }, 407 }, 408 }) 409 410 if err != nil { 411 return nil, err 412 } 413 if resp.NetworkACLs != nil && len(resp.NetworkACLs) > 0 { 414 for _, association := range resp.NetworkACLs[0].Associations { 415 if *association.SubnetID == subnetId { 416 return association, nil 417 } 418 } 419 } 420 return nil, fmt.Errorf("could not find association for subnet %s ", subnetId) 421 } 422 423 // networkAclEntriesToMapList turns ingress/egress rules read from AWS into a list 424 // of maps. 425 func networkAclEntriesToMapList(networkAcls []*ec2.NetworkACLEntry) []map[string]interface{} { 426 result := make([]map[string]interface{}, 0, len(networkAcls)) 427 for _, entry := range networkAcls { 428 acl := make(map[string]interface{}) 429 acl["rule_no"] = *entry.RuleNumber 430 acl["action"] = *entry.RuleAction 431 acl["cidr_block"] = *entry.CIDRBlock 432 433 // The AWS network ACL API only speaks protocol numbers, and 434 // that's all we record. 435 if _, err := strconv.Atoi(*entry.Protocol); err != nil { 436 // We're a protocol name. Look up the number. 437 acl["protocol"] = protocolIntegers()[*entry.Protocol] 438 } else { 439 // We're a protocol number. Pass through. 440 acl["protocol"] = *entry.Protocol 441 } 442 443 acl["protocol"] = *entry.Protocol 444 if entry.PortRange != nil { 445 acl["from_port"] = *entry.PortRange.From 446 acl["to_port"] = *entry.PortRange.To 447 } 448 449 result = append(result, acl) 450 } 451 452 return result 453 }