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