github.com/bendemaree/terraform@v0.5.4-0.20150613200311-f50d97d6eee6/builtin/providers/cloudstack/resource_cloudstack_network_acl_rule.go (about) 1 package cloudstack 2 3 import ( 4 "bytes" 5 "fmt" 6 "regexp" 7 "sort" 8 "strconv" 9 "strings" 10 11 "github.com/hashicorp/terraform/helper/hashcode" 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 "source_cidr": &schema.Schema{ 48 Type: schema.TypeString, 49 Required: true, 50 }, 51 52 "protocol": &schema.Schema{ 53 Type: schema.TypeString, 54 Required: true, 55 }, 56 57 "icmp_type": &schema.Schema{ 58 Type: schema.TypeInt, 59 Optional: true, 60 Computed: true, 61 }, 62 63 "icmp_code": &schema.Schema{ 64 Type: schema.TypeInt, 65 Optional: true, 66 Computed: true, 67 }, 68 69 "ports": &schema.Schema{ 70 Type: schema.TypeSet, 71 Optional: true, 72 Elem: &schema.Schema{Type: schema.TypeString}, 73 Set: func(v interface{}) int { 74 return hashcode.String(v.(string)) 75 }, 76 }, 77 78 "traffic_type": &schema.Schema{ 79 Type: schema.TypeString, 80 Optional: true, 81 Default: "ingress", 82 }, 83 84 "uuids": &schema.Schema{ 85 Type: schema.TypeMap, 86 Computed: true, 87 }, 88 }, 89 }, 90 Set: resourceCloudStackNetworkACLRuleHash, 91 }, 92 }, 93 } 94 } 95 96 func resourceCloudStackNetworkACLRuleCreate(d *schema.ResourceData, meta interface{}) error { 97 // Make sure all required parameters are there 98 if err := verifyNetworkACLParams(d); err != nil { 99 return err 100 } 101 102 // We need to set this upfront in order to be able to save a partial state 103 d.SetId(d.Get("aclid").(string)) 104 105 // Create all rules that are configured 106 if rs := d.Get("rule").(*schema.Set); rs.Len() > 0 { 107 108 // Create an empty schema.Set to hold all rules 109 rules := &schema.Set{ 110 F: resourceCloudStackNetworkACLRuleHash, 111 } 112 113 for _, rule := range rs.List() { 114 // Create a single rule 115 err := resourceCloudStackNetworkACLRuleCreateRule(d, meta, rule.(map[string]interface{})) 116 117 // We need to update this first to preserve the correct state 118 rules.Add(rule) 119 d.Set("rule", rules) 120 121 if err != nil { 122 return err 123 } 124 } 125 } 126 127 return resourceCloudStackNetworkACLRuleRead(d, meta) 128 } 129 130 func resourceCloudStackNetworkACLRuleCreateRule( 131 d *schema.ResourceData, meta interface{}, rule map[string]interface{}) error { 132 cs := meta.(*cloudstack.CloudStackClient) 133 uuids := rule["uuids"].(map[string]interface{}) 134 135 // Make sure all required parameters are there 136 if err := verifyNetworkACLRuleParams(d, rule); err != nil { 137 return err 138 } 139 140 // Create a new parameter struct 141 p := cs.NetworkACL.NewCreateNetworkACLParams(rule["protocol"].(string)) 142 143 // Set the acl ID 144 p.SetAclid(d.Id()) 145 146 // Set the action 147 p.SetAction(rule["action"].(string)) 148 149 // Set the CIDR list 150 p.SetCidrlist([]string{rule["source_cidr"].(string)}) 151 152 // Set the traffic type 153 p.SetTraffictype(rule["traffic_type"].(string)) 154 155 // If the protocol is ICMP set the needed ICMP parameters 156 if rule["protocol"].(string) == "icmp" { 157 p.SetIcmptype(rule["icmp_type"].(int)) 158 p.SetIcmpcode(rule["icmp_code"].(int)) 159 160 r, err := cs.NetworkACL.CreateNetworkACL(p) 161 if err != nil { 162 return err 163 } 164 uuids["icmp"] = r.Id 165 rule["uuids"] = uuids 166 } 167 168 // If the protocol is ALL set the needed parameters 169 if rule["protocol"].(string) == "all" { 170 r, err := cs.NetworkACL.CreateNetworkACL(p) 171 if err != nil { 172 return err 173 } 174 uuids["all"] = r.Id 175 rule["uuids"] = uuids 176 } 177 178 // If protocol is TCP or UDP, loop through all ports 179 if rule["protocol"].(string) == "tcp" || rule["protocol"].(string) == "udp" { 180 if ps := rule["ports"].(*schema.Set); ps.Len() > 0 { 181 182 // Create an empty schema.Set to hold all processed ports 183 ports := &schema.Set{ 184 F: func(v interface{}) int { 185 return hashcode.String(v.(string)) 186 }, 187 } 188 189 for _, port := range ps.List() { 190 re := regexp.MustCompile(`^(\d+)(?:-(\d+))?$`) 191 m := re.FindStringSubmatch(port.(string)) 192 193 startPort, err := strconv.Atoi(m[1]) 194 if err != nil { 195 return err 196 } 197 198 endPort := startPort 199 if m[2] != "" { 200 endPort, err = strconv.Atoi(m[2]) 201 if err != nil { 202 return err 203 } 204 } 205 206 p.SetStartport(startPort) 207 p.SetEndport(endPort) 208 209 r, err := cs.NetworkACL.CreateNetworkACL(p) 210 if err != nil { 211 return err 212 } 213 214 ports.Add(port) 215 rule["ports"] = ports 216 217 uuids[port.(string)] = r.Id 218 rule["uuids"] = uuids 219 } 220 } 221 } 222 223 return nil 224 } 225 226 func resourceCloudStackNetworkACLRuleRead(d *schema.ResourceData, meta interface{}) error { 227 cs := meta.(*cloudstack.CloudStackClient) 228 229 // Create an empty schema.Set to hold all rules 230 rules := &schema.Set{ 231 F: resourceCloudStackNetworkACLRuleHash, 232 } 233 234 // Read all rules that are configured 235 if rs := d.Get("rule").(*schema.Set); rs.Len() > 0 { 236 for _, rule := range rs.List() { 237 rule := rule.(map[string]interface{}) 238 uuids := rule["uuids"].(map[string]interface{}) 239 240 if rule["protocol"].(string) == "icmp" { 241 id, ok := uuids["icmp"] 242 if !ok { 243 continue 244 } 245 246 // Get the rule 247 r, count, err := cs.NetworkACL.GetNetworkACLByID(id.(string)) 248 // If the count == 0, there is no object found for this UUID 249 if err != nil { 250 if count == 0 { 251 delete(uuids, "icmp") 252 continue 253 } 254 255 return err 256 } 257 258 // Update the values 259 rule["action"] = strings.ToLower(r.Action) 260 rule["source_cidr"] = r.Cidrlist 261 rule["protocol"] = r.Protocol 262 rule["icmp_type"] = r.Icmptype 263 rule["icmp_code"] = r.Icmpcode 264 rule["traffic_type"] = strings.ToLower(r.Traffictype) 265 rules.Add(rule) 266 } 267 268 if rule["protocol"].(string) == "all" { 269 id, ok := uuids["all"] 270 if !ok { 271 continue 272 } 273 274 // Get the rule 275 r, count, err := cs.NetworkACL.GetNetworkACLByID(id.(string)) 276 // If the count == 0, there is no object found for this UUID 277 if err != nil { 278 if count == 0 { 279 delete(uuids, "all") 280 continue 281 } 282 283 return err 284 } 285 286 // Update the values 287 rule["action"] = strings.ToLower(r.Action) 288 rule["source_cidr"] = r.Cidrlist 289 rule["protocol"] = r.Protocol 290 rule["traffic_type"] = strings.ToLower(r.Traffictype) 291 rules.Add(rule) 292 } 293 294 // If protocol is tcp or udp, loop through all ports 295 if rule["protocol"].(string) == "tcp" || rule["protocol"].(string) == "udp" { 296 if ps := rule["ports"].(*schema.Set); ps.Len() > 0 { 297 298 // Create an empty schema.Set to hold all ports 299 ports := &schema.Set{ 300 F: func(v interface{}) int { 301 return hashcode.String(v.(string)) 302 }, 303 } 304 305 // Loop through all ports and retrieve their info 306 for _, port := range ps.List() { 307 id, ok := uuids[port.(string)] 308 if !ok { 309 continue 310 } 311 312 // Get the rule 313 r, count, err := cs.NetworkACL.GetNetworkACLByID(id.(string)) 314 if err != nil { 315 if count == 0 { 316 delete(uuids, port.(string)) 317 continue 318 } 319 320 return err 321 } 322 323 // Update the values 324 rule["action"] = strings.ToLower(r.Action) 325 rule["source_cidr"] = r.Cidrlist 326 rule["protocol"] = r.Protocol 327 rule["traffic_type"] = strings.ToLower(r.Traffictype) 328 ports.Add(port) 329 } 330 331 // If there is at least one port found, add this rule to the rules set 332 if ports.Len() > 0 { 333 rule["ports"] = ports 334 rules.Add(rule) 335 } 336 } 337 } 338 } 339 } 340 341 // If this is a managed firewall, add all unknown rules into a single dummy rule 342 managed := d.Get("managed").(bool) 343 if managed { 344 // Get all the rules from the running environment 345 p := cs.NetworkACL.NewListNetworkACLsParams() 346 p.SetAclid(d.Id()) 347 p.SetListall(true) 348 349 r, err := cs.NetworkACL.ListNetworkACLs(p) 350 if err != nil { 351 return err 352 } 353 354 // Add all UUIDs to the uuids map 355 uuids := make(map[string]interface{}, len(r.NetworkACLs)) 356 for _, r := range r.NetworkACLs { 357 uuids[r.Id] = r.Id 358 } 359 360 // Delete all expected UUIDs from the uuids map 361 for _, rule := range rules.List() { 362 rule := rule.(map[string]interface{}) 363 364 for _, id := range rule["uuids"].(map[string]interface{}) { 365 delete(uuids, id.(string)) 366 } 367 } 368 369 if len(uuids) > 0 { 370 // Make a dummy rule to hold all unknown UUIDs 371 rule := map[string]interface{}{ 372 "source_cidr": "N/A", 373 "protocol": "N/A", 374 "uuids": uuids, 375 } 376 377 // Add the dummy rule to the rules set 378 rules.Add(rule) 379 } 380 } 381 382 if rules.Len() > 0 { 383 d.Set("rule", rules) 384 } else if !managed { 385 d.SetId("") 386 } 387 388 return nil 389 } 390 391 func resourceCloudStackNetworkACLRuleUpdate(d *schema.ResourceData, meta interface{}) error { 392 // Make sure all required parameters are there 393 if err := verifyNetworkACLParams(d); err != nil { 394 return err 395 } 396 397 // Check if the rule set as a whole has changed 398 if d.HasChange("rule") { 399 o, n := d.GetChange("rule") 400 ors := o.(*schema.Set).Difference(n.(*schema.Set)) 401 nrs := n.(*schema.Set).Difference(o.(*schema.Set)) 402 403 // Now first loop through all the old rules and delete any obsolete ones 404 for _, rule := range ors.List() { 405 // Delete the rule as it no longer exists in the config 406 err := resourceCloudStackNetworkACLRuleDeleteRule(d, meta, rule.(map[string]interface{})) 407 if err != nil { 408 return err 409 } 410 } 411 412 // Make sure we save the state of the currently configured rules 413 rules := o.(*schema.Set).Intersection(n.(*schema.Set)) 414 d.Set("rule", rules) 415 416 // Then loop through all the currently configured rules and create the new ones 417 for _, rule := range nrs.List() { 418 // When succesfully deleted, re-create it again if it still exists 419 err := resourceCloudStackNetworkACLRuleCreateRule(d, meta, rule.(map[string]interface{})) 420 421 // We need to update this first to preserve the correct state 422 rules.Add(rule) 423 d.Set("rule", rules) 424 425 if err != nil { 426 return err 427 } 428 } 429 } 430 431 return resourceCloudStackNetworkACLRuleRead(d, meta) 432 } 433 434 func resourceCloudStackNetworkACLRuleDelete(d *schema.ResourceData, meta interface{}) error { 435 // Delete all rules 436 if rs := d.Get("rule").(*schema.Set); rs.Len() > 0 { 437 for _, rule := range rs.List() { 438 // Delete a single rule 439 err := resourceCloudStackNetworkACLRuleDeleteRule(d, meta, rule.(map[string]interface{})) 440 441 // We need to update this first to preserve the correct state 442 d.Set("rule", rs) 443 444 if err != nil { 445 return err 446 } 447 } 448 } 449 450 return nil 451 } 452 453 func resourceCloudStackNetworkACLRuleDeleteRule( 454 d *schema.ResourceData, meta interface{}, rule map[string]interface{}) error { 455 cs := meta.(*cloudstack.CloudStackClient) 456 uuids := rule["uuids"].(map[string]interface{}) 457 458 for k, id := range uuids { 459 // We don't care about the count here, so just continue 460 if k == "#" { 461 continue 462 } 463 464 // Create the parameter struct 465 p := cs.NetworkACL.NewDeleteNetworkACLParams(id.(string)) 466 467 // Delete the rule 468 if _, err := cs.NetworkACL.DeleteNetworkACL(p); err != nil { 469 470 // This is a very poor way to be told the UUID does no longer exist :( 471 if strings.Contains(err.Error(), fmt.Sprintf( 472 "Invalid parameter id value=%s due to incorrect long value format, "+ 473 "or entity does not exist", id.(string))) { 474 delete(uuids, k) 475 continue 476 } 477 478 return err 479 } 480 481 // Delete the UUID of this rule 482 delete(uuids, k) 483 } 484 485 // Update the UUIDs 486 rule["uuids"] = uuids 487 488 return nil 489 } 490 491 func resourceCloudStackNetworkACLRuleHash(v interface{}) int { 492 var buf bytes.Buffer 493 m := v.(map[string]interface{}) 494 495 // This is a little ugly, but it's needed because these arguments have 496 // a default value that needs to be part of the string to hash 497 var action, trafficType string 498 if a, ok := m["action"]; ok { 499 action = a.(string) 500 } else { 501 action = "allow" 502 } 503 if t, ok := m["traffic_type"]; ok { 504 trafficType = t.(string) 505 } else { 506 trafficType = "ingress" 507 } 508 509 buf.WriteString(fmt.Sprintf( 510 "%s-%s-%s-%s-", 511 action, 512 m["source_cidr"].(string), 513 m["protocol"].(string), 514 trafficType)) 515 516 if v, ok := m["icmp_type"]; ok { 517 buf.WriteString(fmt.Sprintf("%d-", v.(int))) 518 } 519 520 if v, ok := m["icmp_code"]; ok { 521 buf.WriteString(fmt.Sprintf("%d-", v.(int))) 522 } 523 524 // We need to make sure to sort the strings below so that we always 525 // generate the same hash code no matter what is in the set. 526 if v, ok := m["ports"]; ok { 527 vs := v.(*schema.Set).List() 528 s := make([]string, len(vs)) 529 530 for i, raw := range vs { 531 s[i] = raw.(string) 532 } 533 sort.Strings(s) 534 535 for _, v := range s { 536 buf.WriteString(fmt.Sprintf("%s-", v)) 537 } 538 } 539 540 return hashcode.String(buf.String()) 541 } 542 543 func verifyNetworkACLParams(d *schema.ResourceData) error { 544 managed := d.Get("managed").(bool) 545 _, rules := d.GetOk("rule") 546 547 if !rules && !managed { 548 return fmt.Errorf( 549 "You must supply at least one 'rule' when not using the 'managed' firewall feature") 550 } 551 552 return nil 553 } 554 555 func verifyNetworkACLRuleParams(d *schema.ResourceData, rule map[string]interface{}) error { 556 action := rule["action"].(string) 557 if action != "allow" && action != "deny" { 558 return fmt.Errorf("Parameter action only accepts 'allow' or 'deny' as values") 559 } 560 561 protocol := rule["protocol"].(string) 562 switch protocol { 563 case "icmp": 564 if _, ok := rule["icmp_type"]; !ok { 565 return fmt.Errorf( 566 "Parameter icmp_type is a required parameter when using protocol 'icmp'") 567 } 568 if _, ok := rule["icmp_code"]; !ok { 569 return fmt.Errorf( 570 "Parameter icmp_code is a required parameter when using protocol 'icmp'") 571 } 572 case "all": 573 // No additional test are needed, so just leave this empty... 574 case "tcp", "udp": 575 if _, ok := rule["ports"]; !ok { 576 return fmt.Errorf( 577 "Parameter ports is a required parameter when *not* using protocol 'icmp'") 578 } 579 default: 580 _, err := strconv.ParseInt(protocol, 0, 0) 581 if err != nil { 582 return fmt.Errorf( 583 "%s is not a valid protocol. Valid options are 'tcp', 'udp', "+ 584 "'icmp', 'all' or a valid protocol number", protocol) 585 } 586 } 587 588 traffic := rule["traffic_type"].(string) 589 if traffic != "ingress" && traffic != "egress" { 590 return fmt.Errorf( 591 "Parameter traffic_type only accepts 'ingress' or 'egress' as values") 592 } 593 594 return nil 595 }