github.com/leeprovoost/terraform@v0.6.10-0.20160119085442-96f3f76118e7/builtin/providers/cloudstack/resource_cloudstack_network_acl_rule.go (about) 1 package cloudstack 2 3 import ( 4 "fmt" 5 "regexp" 6 "strconv" 7 "strings" 8 "sync" 9 "time" 10 11 "github.com/hashicorp/go-multierror" 12 "github.com/hashicorp/terraform/helper/schema" 13 "github.com/xanzy/go-cloudstack/cloudstack" 14 ) 15 16 func resourceCloudStackNetworkACLRule() *schema.Resource { 17 return &schema.Resource{ 18 Create: resourceCloudStackNetworkACLRuleCreate, 19 Read: resourceCloudStackNetworkACLRuleRead, 20 Update: resourceCloudStackNetworkACLRuleUpdate, 21 Delete: resourceCloudStackNetworkACLRuleDelete, 22 23 Schema: map[string]*schema.Schema{ 24 "aclid": &schema.Schema{ 25 Type: schema.TypeString, 26 Required: true, 27 ForceNew: true, 28 }, 29 30 "managed": &schema.Schema{ 31 Type: schema.TypeBool, 32 Optional: true, 33 Default: false, 34 }, 35 36 "rule": &schema.Schema{ 37 Type: schema.TypeSet, 38 Optional: true, 39 Elem: &schema.Resource{ 40 Schema: map[string]*schema.Schema{ 41 "action": &schema.Schema{ 42 Type: schema.TypeString, 43 Optional: true, 44 Default: "allow", 45 }, 46 47 "cidr_list": &schema.Schema{ 48 Type: schema.TypeSet, 49 Optional: true, 50 Elem: &schema.Schema{Type: schema.TypeString}, 51 Set: schema.HashString, 52 }, 53 54 "source_cidr": &schema.Schema{ 55 Type: schema.TypeString, 56 Optional: true, 57 Deprecated: "Please use the `cidr_list` field instead", 58 }, 59 60 "protocol": &schema.Schema{ 61 Type: schema.TypeString, 62 Required: true, 63 }, 64 65 "icmp_type": &schema.Schema{ 66 Type: schema.TypeInt, 67 Optional: true, 68 Computed: true, 69 }, 70 71 "icmp_code": &schema.Schema{ 72 Type: schema.TypeInt, 73 Optional: true, 74 Computed: true, 75 }, 76 77 "ports": &schema.Schema{ 78 Type: schema.TypeSet, 79 Optional: true, 80 Elem: &schema.Schema{Type: schema.TypeString}, 81 Set: schema.HashString, 82 }, 83 84 "traffic_type": &schema.Schema{ 85 Type: schema.TypeString, 86 Optional: true, 87 Default: "ingress", 88 }, 89 90 "uuids": &schema.Schema{ 91 Type: schema.TypeMap, 92 Computed: true, 93 }, 94 }, 95 }, 96 }, 97 }, 98 } 99 } 100 101 func resourceCloudStackNetworkACLRuleCreate(d *schema.ResourceData, meta interface{}) error { 102 // Make sure all required parameters are there 103 if err := verifyNetworkACLParams(d); err != nil { 104 return err 105 } 106 107 // We need to set this upfront in order to be able to save a partial state 108 d.SetId(d.Get("aclid").(string)) 109 110 // Create all rules that are configured 111 if nrs := d.Get("rule").(*schema.Set); nrs.Len() > 0 { 112 // Create an empty rule set to hold all newly created rules 113 rules := resourceCloudStackNetworkACLRule().Schema["rule"].ZeroValue().(*schema.Set) 114 115 err := createNetworkACLRules(d, meta, rules, nrs) 116 117 // We need to update this first to preserve the correct state 118 d.Set("rule", rules) 119 120 if err != nil { 121 return err 122 } 123 } 124 125 return resourceCloudStackNetworkACLRuleRead(d, meta) 126 } 127 128 func createNetworkACLRules( 129 d *schema.ResourceData, 130 meta interface{}, 131 rules *schema.Set, 132 nrs *schema.Set) error { 133 var errs *multierror.Error 134 135 var wg sync.WaitGroup 136 wg.Add(nrs.Len()) 137 138 sem := make(chan struct{}, 10) 139 for _, rule := range nrs.List() { 140 // Put in a tiny sleep here to avoid DoS'ing the API 141 time.Sleep(500 * time.Millisecond) 142 143 go func(rule map[string]interface{}) { 144 defer wg.Done() 145 sem <- struct{}{} 146 147 // Create a single rule 148 err := createNetworkACLRule(d, meta, rule) 149 150 // If we have at least one UUID, we need to save the rule 151 if len(rule["uuids"].(map[string]interface{})) > 0 { 152 rules.Add(rule) 153 } 154 155 if err != nil { 156 errs = multierror.Append(errs, err) 157 } 158 159 <-sem 160 }(rule.(map[string]interface{})) 161 } 162 163 wg.Wait() 164 165 return errs.ErrorOrNil() 166 } 167 168 func createNetworkACLRule( 169 d *schema.ResourceData, 170 meta interface{}, 171 rule map[string]interface{}) error { 172 cs := meta.(*cloudstack.CloudStackClient) 173 uuids := rule["uuids"].(map[string]interface{}) 174 175 // Make sure all required parameters are there 176 if err := verifyNetworkACLRuleParams(d, rule); err != nil { 177 return err 178 } 179 180 // Create a new parameter struct 181 p := cs.NetworkACL.NewCreateNetworkACLParams(rule["protocol"].(string)) 182 183 // Set the acl ID 184 p.SetAclid(d.Id()) 185 186 // Set the action 187 p.SetAction(rule["action"].(string)) 188 189 // Set the CIDR list 190 p.SetCidrlist(retrieveCidrList(rule)) 191 192 // Set the traffic type 193 p.SetTraffictype(rule["traffic_type"].(string)) 194 195 // If the protocol is ICMP set the needed ICMP parameters 196 if rule["protocol"].(string) == "icmp" { 197 p.SetIcmptype(rule["icmp_type"].(int)) 198 p.SetIcmpcode(rule["icmp_code"].(int)) 199 200 r, err := Retry(4, retryableACLCreationFunc(cs, p)) 201 if err != nil { 202 return err 203 } 204 205 uuids["icmp"] = r.(*cloudstack.CreateNetworkACLResponse).Id 206 rule["uuids"] = uuids 207 } 208 209 // If the protocol is ALL set the needed parameters 210 if rule["protocol"].(string) == "all" { 211 r, err := Retry(4, retryableACLCreationFunc(cs, p)) 212 if err != nil { 213 return err 214 } 215 216 uuids["all"] = r.(*cloudstack.CreateNetworkACLResponse).Id 217 rule["uuids"] = uuids 218 } 219 220 // If protocol is TCP or UDP, loop through all ports 221 if rule["protocol"].(string) == "tcp" || rule["protocol"].(string) == "udp" { 222 if ps := rule["ports"].(*schema.Set); ps.Len() > 0 { 223 224 // Create an empty schema.Set to hold all processed ports 225 ports := &schema.Set{F: schema.HashString} 226 227 // Define a regexp for parsing the port 228 re := regexp.MustCompile(`^(\d+)(?:-(\d+))?$`) 229 230 for _, port := range ps.List() { 231 if _, ok := uuids[port.(string)]; ok { 232 ports.Add(port) 233 rule["ports"] = ports 234 continue 235 } 236 237 m := re.FindStringSubmatch(port.(string)) 238 239 startPort, err := strconv.Atoi(m[1]) 240 if err != nil { 241 return err 242 } 243 244 endPort := startPort 245 if m[2] != "" { 246 endPort, err = strconv.Atoi(m[2]) 247 if err != nil { 248 return err 249 } 250 } 251 252 p.SetStartport(startPort) 253 p.SetEndport(endPort) 254 255 r, err := Retry(4, retryableACLCreationFunc(cs, p)) 256 if err != nil { 257 return err 258 } 259 260 ports.Add(port) 261 rule["ports"] = ports 262 263 uuids[port.(string)] = r.(*cloudstack.CreateNetworkACLResponse).Id 264 rule["uuids"] = uuids 265 } 266 } 267 } 268 269 return nil 270 } 271 272 func resourceCloudStackNetworkACLRuleRead(d *schema.ResourceData, meta interface{}) error { 273 cs := meta.(*cloudstack.CloudStackClient) 274 275 // Get all the rules from the running environment 276 p := cs.NetworkACL.NewListNetworkACLsParams() 277 p.SetAclid(d.Id()) 278 p.SetListall(true) 279 280 l, err := cs.NetworkACL.ListNetworkACLs(p) 281 if err != nil { 282 return err 283 } 284 285 // Make a map of all the rules so we can easily find a rule 286 ruleMap := make(map[string]*cloudstack.NetworkACL, l.Count) 287 for _, r := range l.NetworkACLs { 288 ruleMap[r.Id] = r 289 } 290 291 // Create an empty schema.Set to hold all rules 292 rules := resourceCloudStackNetworkACLRule().Schema["rule"].ZeroValue().(*schema.Set) 293 294 // Read all rules that are configured 295 if rs := d.Get("rule").(*schema.Set); rs.Len() > 0 { 296 for _, rule := range rs.List() { 297 rule := rule.(map[string]interface{}) 298 uuids := rule["uuids"].(map[string]interface{}) 299 300 if rule["protocol"].(string) == "icmp" { 301 id, ok := uuids["icmp"] 302 if !ok { 303 continue 304 } 305 306 // Get the rule 307 r, ok := ruleMap[id.(string)] 308 if !ok { 309 delete(uuids, "icmp") 310 continue 311 } 312 313 // Delete the known rule so only unknown rules remain in the ruleMap 314 delete(ruleMap, id.(string)) 315 316 // Update the values 317 rule["action"] = strings.ToLower(r.Action) 318 rule["protocol"] = r.Protocol 319 rule["icmp_type"] = r.Icmptype 320 rule["icmp_code"] = r.Icmpcode 321 rule["traffic_type"] = strings.ToLower(r.Traffictype) 322 setCidrList(rule, r.Cidrlist) 323 rules.Add(rule) 324 } 325 326 if rule["protocol"].(string) == "all" { 327 id, ok := uuids["all"] 328 if !ok { 329 continue 330 } 331 332 // Get the rule 333 r, ok := ruleMap[id.(string)] 334 if !ok { 335 delete(uuids, "all") 336 continue 337 } 338 339 // Delete the known rule so only unknown rules remain in the ruleMap 340 delete(ruleMap, id.(string)) 341 342 // Update the values 343 rule["action"] = strings.ToLower(r.Action) 344 rule["protocol"] = r.Protocol 345 rule["traffic_type"] = strings.ToLower(r.Traffictype) 346 setCidrList(rule, r.Cidrlist) 347 rules.Add(rule) 348 } 349 350 // If protocol is tcp or udp, loop through all ports 351 if rule["protocol"].(string) == "tcp" || rule["protocol"].(string) == "udp" { 352 if ps := rule["ports"].(*schema.Set); ps.Len() > 0 { 353 354 // Create an empty schema.Set to hold all ports 355 ports := &schema.Set{F: schema.HashString} 356 357 // Loop through all ports and retrieve their info 358 for _, port := range ps.List() { 359 id, ok := uuids[port.(string)] 360 if !ok { 361 continue 362 } 363 364 // Get the rule 365 r, ok := ruleMap[id.(string)] 366 if !ok { 367 delete(uuids, port.(string)) 368 continue 369 } 370 371 // Delete the known rule so only unknown rules remain in the ruleMap 372 delete(ruleMap, id.(string)) 373 374 // Update the values 375 rule["action"] = strings.ToLower(r.Action) 376 rule["protocol"] = r.Protocol 377 rule["traffic_type"] = strings.ToLower(r.Traffictype) 378 setCidrList(rule, r.Cidrlist) 379 ports.Add(port) 380 } 381 382 // If there is at least one port found, add this rule to the rules set 383 if ports.Len() > 0 { 384 rule["ports"] = ports 385 rules.Add(rule) 386 } 387 } 388 } 389 } 390 } 391 392 // If this is a managed firewall, add all unknown rules into dummy rules 393 managed := d.Get("managed").(bool) 394 if managed && len(ruleMap) > 0 { 395 for uuid := range ruleMap { 396 // We need to create and add a dummy value to a schema.Set as the 397 // cidr_list is a required field and thus needs a value 398 cidrs := &schema.Set{F: schema.HashString} 399 cidrs.Add(uuid) 400 401 // Make a dummy rule to hold the unknown UUID 402 rule := map[string]interface{}{ 403 "cidr_list": cidrs, 404 "protocol": uuid, 405 "uuids": map[string]interface{}{uuid: uuid}, 406 } 407 408 // Add the dummy rule to the rules set 409 rules.Add(rule) 410 } 411 } 412 413 if rules.Len() > 0 { 414 d.Set("rule", rules) 415 } else if !managed { 416 d.SetId("") 417 } 418 419 return nil 420 } 421 422 func resourceCloudStackNetworkACLRuleUpdate(d *schema.ResourceData, meta interface{}) error { 423 // Make sure all required parameters are there 424 if err := verifyNetworkACLParams(d); err != nil { 425 return err 426 } 427 428 // Check if the rule set as a whole has changed 429 if d.HasChange("rule") { 430 o, n := d.GetChange("rule") 431 ors := o.(*schema.Set).Difference(n.(*schema.Set)) 432 nrs := n.(*schema.Set).Difference(o.(*schema.Set)) 433 434 // We need to start with a rule set containing all the rules we 435 // already have and want to keep. Any rules that are not deleted 436 // correctly and any newly created rules, will be added to this 437 // set to make sure we end up in a consistent state 438 rules := o.(*schema.Set).Intersection(n.(*schema.Set)) 439 440 // First loop through all the new rules and create (before destroy) them 441 if nrs.Len() > 0 { 442 err := createNetworkACLRules(d, meta, rules, nrs) 443 444 // We need to update this first to preserve the correct state 445 d.Set("rule", rules) 446 447 if err != nil { 448 return err 449 } 450 } 451 452 // Then loop through all the old rules and delete them 453 if ors.Len() > 0 { 454 err := deleteNetworkACLRules(d, meta, rules, ors) 455 456 // We need to update this first to preserve the correct state 457 d.Set("rule", rules) 458 459 if err != nil { 460 return err 461 } 462 } 463 } 464 465 return resourceCloudStackNetworkACLRuleRead(d, meta) 466 } 467 468 func resourceCloudStackNetworkACLRuleDelete(d *schema.ResourceData, meta interface{}) error { 469 // Create an empty rule set to hold all rules that where 470 // not deleted correctly 471 rules := resourceCloudStackNetworkACLRule().Schema["rule"].ZeroValue().(*schema.Set) 472 473 // Delete all rules 474 if ors := d.Get("rule").(*schema.Set); ors.Len() > 0 { 475 err := deleteNetworkACLRules(d, meta, rules, ors) 476 477 // We need to update this first to preserve the correct state 478 d.Set("rule", rules) 479 480 if err != nil { 481 return err 482 } 483 } 484 485 return nil 486 } 487 488 func deleteNetworkACLRules( 489 d *schema.ResourceData, 490 meta interface{}, 491 rules *schema.Set, 492 ors *schema.Set) error { 493 var errs *multierror.Error 494 495 var wg sync.WaitGroup 496 wg.Add(ors.Len()) 497 498 sem := make(chan struct{}, 10) 499 for _, rule := range ors.List() { 500 // Put a sleep here to avoid DoS'ing the API 501 time.Sleep(500 * time.Millisecond) 502 503 go func(rule map[string]interface{}) { 504 defer wg.Done() 505 sem <- struct{}{} 506 507 // Delete a single rule 508 err := deleteNetworkACLRule(d, meta, rule) 509 510 // If we have at least one UUID, we need to save the rule 511 if len(rule["uuids"].(map[string]interface{})) > 0 { 512 rules.Add(rule) 513 } 514 515 if err != nil { 516 errs = multierror.Append(errs, err) 517 } 518 519 <-sem 520 }(rule.(map[string]interface{})) 521 } 522 523 wg.Wait() 524 525 return errs.ErrorOrNil() 526 } 527 528 func deleteNetworkACLRule( 529 d *schema.ResourceData, 530 meta interface{}, 531 rule map[string]interface{}) error { 532 cs := meta.(*cloudstack.CloudStackClient) 533 uuids := rule["uuids"].(map[string]interface{}) 534 535 for k, id := range uuids { 536 // We don't care about the count here, so just continue 537 if k == "#" { 538 continue 539 } 540 541 // Create the parameter struct 542 p := cs.NetworkACL.NewDeleteNetworkACLParams(id.(string)) 543 544 // Delete the rule 545 if _, err := cs.NetworkACL.DeleteNetworkACL(p); err != nil { 546 547 // This is a very poor way to be told the ID does no longer exist :( 548 if strings.Contains(err.Error(), fmt.Sprintf( 549 "Invalid parameter id value=%s due to incorrect long value format, "+ 550 "or entity does not exist", id.(string))) { 551 delete(uuids, k) 552 rule["uuids"] = uuids 553 continue 554 } 555 556 return err 557 } 558 559 // Delete the UUID of this rule 560 delete(uuids, k) 561 rule["uuids"] = uuids 562 } 563 564 return nil 565 } 566 567 func verifyNetworkACLParams(d *schema.ResourceData) error { 568 managed := d.Get("managed").(bool) 569 _, rules := d.GetOk("rule") 570 571 if !rules && !managed { 572 return fmt.Errorf( 573 "You must supply at least one 'rule' when not using the 'managed' firewall feature") 574 } 575 576 return nil 577 } 578 579 func verifyNetworkACLRuleParams(d *schema.ResourceData, rule map[string]interface{}) error { 580 action := rule["action"].(string) 581 if action != "allow" && action != "deny" { 582 return fmt.Errorf("Parameter action only accepts 'allow' or 'deny' as values") 583 } 584 585 cidrList := rule["cidr_list"].(*schema.Set) 586 sourceCidr := rule["source_cidr"].(string) 587 if cidrList.Len() == 0 && sourceCidr == "" { 588 return fmt.Errorf( 589 "Parameter cidr_list is a required parameter") 590 } 591 if cidrList.Len() > 0 && sourceCidr != "" { 592 return fmt.Errorf( 593 "Parameter source_cidr is deprecated and cannot be used together with cidr_list") 594 } 595 596 protocol := rule["protocol"].(string) 597 switch protocol { 598 case "icmp": 599 if _, ok := rule["icmp_type"]; !ok { 600 return fmt.Errorf( 601 "Parameter icmp_type is a required parameter when using protocol 'icmp'") 602 } 603 if _, ok := rule["icmp_code"]; !ok { 604 return fmt.Errorf( 605 "Parameter icmp_code is a required parameter when using protocol 'icmp'") 606 } 607 case "all": 608 // No additional test are needed, so just leave this empty... 609 case "tcp", "udp": 610 if _, ok := rule["ports"]; !ok { 611 return fmt.Errorf( 612 "Parameter ports is a required parameter when *not* using protocol 'icmp'") 613 } 614 default: 615 _, err := strconv.ParseInt(protocol, 0, 0) 616 if err != nil { 617 return fmt.Errorf( 618 "%s is not a valid protocol. Valid options are 'tcp', 'udp', "+ 619 "'icmp', 'all' or a valid protocol number", protocol) 620 } 621 } 622 623 traffic := rule["traffic_type"].(string) 624 if traffic != "ingress" && traffic != "egress" { 625 return fmt.Errorf( 626 "Parameter traffic_type only accepts 'ingress' or 'egress' as values") 627 } 628 629 return nil 630 } 631 632 func retryableACLCreationFunc( 633 cs *cloudstack.CloudStackClient, 634 p *cloudstack.CreateNetworkACLParams) func() (interface{}, error) { 635 return func() (interface{}, error) { 636 r, err := cs.NetworkACL.CreateNetworkACL(p) 637 if err != nil { 638 return nil, err 639 } 640 return r, nil 641 } 642 }