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