github.com/i0n/terraform@v0.4.3-0.20150506151324-010a39a58ec1/builtin/providers/aws/resource_aws_security_group.go (about) 1 package aws 2 3 import ( 4 "bytes" 5 "fmt" 6 "log" 7 "sort" 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 resourceAwsSecurityGroup() *schema.Resource { 18 return &schema.Resource{ 19 Create: resourceAwsSecurityGroupCreate, 20 Read: resourceAwsSecurityGroupRead, 21 Update: resourceAwsSecurityGroupUpdate, 22 Delete: resourceAwsSecurityGroupDelete, 23 24 Schema: map[string]*schema.Schema{ 25 "name": &schema.Schema{ 26 Type: schema.TypeString, 27 Optional: true, 28 Computed: true, 29 ForceNew: true, 30 }, 31 32 "description": &schema.Schema{ 33 Type: schema.TypeString, 34 Optional: true, 35 Default: "Managed by Terraform", 36 }, 37 38 "vpc_id": &schema.Schema{ 39 Type: schema.TypeString, 40 Optional: true, 41 ForceNew: true, 42 Computed: true, 43 }, 44 45 "ingress": &schema.Schema{ 46 Type: schema.TypeSet, 47 Optional: true, 48 Computed: true, 49 Elem: &schema.Resource{ 50 Schema: map[string]*schema.Schema{ 51 "from_port": &schema.Schema{ 52 Type: schema.TypeInt, 53 Required: true, 54 }, 55 56 "to_port": &schema.Schema{ 57 Type: schema.TypeInt, 58 Required: true, 59 }, 60 61 "protocol": &schema.Schema{ 62 Type: schema.TypeString, 63 Required: true, 64 }, 65 66 "cidr_blocks": &schema.Schema{ 67 Type: schema.TypeList, 68 Optional: true, 69 Elem: &schema.Schema{Type: schema.TypeString}, 70 }, 71 72 "security_groups": &schema.Schema{ 73 Type: schema.TypeSet, 74 Optional: true, 75 Elem: &schema.Schema{Type: schema.TypeString}, 76 Set: func(v interface{}) int { 77 return hashcode.String(v.(string)) 78 }, 79 }, 80 81 "self": &schema.Schema{ 82 Type: schema.TypeBool, 83 Optional: true, 84 Default: false, 85 }, 86 }, 87 }, 88 Set: resourceAwsSecurityGroupRuleHash, 89 }, 90 91 "egress": &schema.Schema{ 92 Type: schema.TypeSet, 93 Optional: true, 94 Computed: true, 95 Elem: &schema.Resource{ 96 Schema: map[string]*schema.Schema{ 97 "from_port": &schema.Schema{ 98 Type: schema.TypeInt, 99 Required: true, 100 }, 101 102 "to_port": &schema.Schema{ 103 Type: schema.TypeInt, 104 Required: true, 105 }, 106 107 "protocol": &schema.Schema{ 108 Type: schema.TypeString, 109 Required: true, 110 }, 111 112 "cidr_blocks": &schema.Schema{ 113 Type: schema.TypeList, 114 Optional: true, 115 Elem: &schema.Schema{Type: schema.TypeString}, 116 }, 117 118 "security_groups": &schema.Schema{ 119 Type: schema.TypeSet, 120 Optional: true, 121 Elem: &schema.Schema{Type: schema.TypeString}, 122 Set: func(v interface{}) int { 123 return hashcode.String(v.(string)) 124 }, 125 }, 126 127 "self": &schema.Schema{ 128 Type: schema.TypeBool, 129 Optional: true, 130 Default: false, 131 }, 132 }, 133 }, 134 Set: resourceAwsSecurityGroupRuleHash, 135 }, 136 137 "owner_id": &schema.Schema{ 138 Type: schema.TypeString, 139 Computed: true, 140 }, 141 142 "tags": tagsSchema(), 143 }, 144 } 145 } 146 147 func resourceAwsSecurityGroupCreate(d *schema.ResourceData, meta interface{}) error { 148 conn := meta.(*AWSClient).ec2conn 149 150 securityGroupOpts := &ec2.CreateSecurityGroupInput{} 151 152 if v := d.Get("vpc_id"); v != nil { 153 securityGroupOpts.VPCID = aws.String(v.(string)) 154 } 155 156 if v := d.Get("description"); v != nil { 157 securityGroupOpts.Description = aws.String(v.(string)) 158 } 159 160 var groupName string 161 if v, ok := d.GetOk("name"); ok { 162 groupName = v.(string) 163 } else { 164 groupName = resource.UniqueId() 165 } 166 securityGroupOpts.GroupName = aws.String(groupName) 167 168 log.Printf( 169 "[DEBUG] Security Group create configuration: %#v", securityGroupOpts) 170 createResp, err := conn.CreateSecurityGroup(securityGroupOpts) 171 if err != nil { 172 return fmt.Errorf("Error creating Security Group: %s", err) 173 } 174 175 d.SetId(*createResp.GroupID) 176 177 log.Printf("[INFO] Security Group ID: %s", d.Id()) 178 179 // Wait for the security group to truly exist 180 log.Printf( 181 "[DEBUG] Waiting for Security Group (%s) to exist", 182 d.Id()) 183 stateConf := &resource.StateChangeConf{ 184 Pending: []string{""}, 185 Target: "exists", 186 Refresh: SGStateRefreshFunc(conn, d.Id()), 187 Timeout: 1 * time.Minute, 188 } 189 190 resp, err := stateConf.WaitForState() 191 if err != nil { 192 return fmt.Errorf( 193 "Error waiting for Security Group (%s) to become available: %s", 194 d.Id(), err) 195 } 196 197 // AWS defaults all Security Groups to have an ALLOW ALL egress rule. Here we 198 // revoke that rule, so users don't unknowningly have/use it. 199 group := resp.(*ec2.SecurityGroup) 200 if group.VPCID != nil && *group.VPCID != "" { 201 log.Printf("[DEBUG] Revoking default egress rule for Security Group for %s", d.Id()) 202 203 req := &ec2.RevokeSecurityGroupEgressInput{ 204 GroupID: createResp.GroupID, 205 IPPermissions: []*ec2.IPPermission{ 206 &ec2.IPPermission{ 207 FromPort: aws.Long(int64(0)), 208 ToPort: aws.Long(int64(0)), 209 IPRanges: []*ec2.IPRange{ 210 &ec2.IPRange{ 211 CIDRIP: aws.String("0.0.0.0/0"), 212 }, 213 }, 214 IPProtocol: aws.String("-1"), 215 }, 216 }, 217 } 218 219 if _, err = conn.RevokeSecurityGroupEgress(req); err != nil { 220 return fmt.Errorf( 221 "Error revoking default egress rule for Security Group (%s): %s", 222 d.Id(), err) 223 } 224 225 } 226 227 return resourceAwsSecurityGroupUpdate(d, meta) 228 } 229 230 func resourceAwsSecurityGroupRead(d *schema.ResourceData, meta interface{}) error { 231 conn := meta.(*AWSClient).ec2conn 232 233 sgRaw, _, err := SGStateRefreshFunc(conn, d.Id())() 234 if err != nil { 235 return err 236 } 237 if sgRaw == nil { 238 d.SetId("") 239 return nil 240 } 241 242 sg := sgRaw.(*ec2.SecurityGroup) 243 244 ingressRules := resourceAwsSecurityGroupIPPermGather(d, sg.IPPermissions) 245 egressRules := resourceAwsSecurityGroupIPPermGather(d, sg.IPPermissionsEgress) 246 247 d.Set("description", sg.Description) 248 d.Set("name", sg.GroupName) 249 d.Set("vpc_id", sg.VPCID) 250 d.Set("owner_id", sg.OwnerID) 251 d.Set("ingress", ingressRules) 252 d.Set("egress", egressRules) 253 d.Set("tags", tagsToMapSDK(sg.Tags)) 254 return nil 255 } 256 257 func resourceAwsSecurityGroupUpdate(d *schema.ResourceData, meta interface{}) error { 258 conn := meta.(*AWSClient).ec2conn 259 260 sgRaw, _, err := SGStateRefreshFunc(conn, d.Id())() 261 if err != nil { 262 return err 263 } 264 if sgRaw == nil { 265 d.SetId("") 266 return nil 267 } 268 269 group := sgRaw.(*ec2.SecurityGroup) 270 271 err = resourceAwsSecurityGroupUpdateRules(d, "ingress", meta, group) 272 if err != nil { 273 return err 274 } 275 276 if d.Get("vpc_id") != nil { 277 err = resourceAwsSecurityGroupUpdateRules(d, "egress", meta, group) 278 if err != nil { 279 return err 280 } 281 } 282 283 if err := setTagsSDK(conn, d); err != nil { 284 return err 285 } 286 287 d.SetPartial("tags") 288 289 return resourceAwsSecurityGroupRead(d, meta) 290 } 291 292 func resourceAwsSecurityGroupDelete(d *schema.ResourceData, meta interface{}) error { 293 conn := meta.(*AWSClient).ec2conn 294 295 log.Printf("[DEBUG] Security Group destroy: %v", d.Id()) 296 297 return resource.Retry(2*time.Minute, func() error { 298 _, err := conn.DeleteSecurityGroup(&ec2.DeleteSecurityGroupInput{ 299 GroupID: aws.String(d.Id()), 300 }) 301 if err != nil { 302 ec2err, ok := err.(aws.APIError) 303 if !ok { 304 return err 305 } 306 307 switch ec2err.Code { 308 case "InvalidGroup.NotFound": 309 return nil 310 case "DependencyViolation": 311 // If it is a dependency violation, we want to retry 312 return err 313 default: 314 // Any other error, we want to quit the retry loop immediately 315 return resource.RetryError{Err: err} 316 } 317 } 318 319 return nil 320 }) 321 } 322 323 func resourceAwsSecurityGroupRuleHash(v interface{}) int { 324 var buf bytes.Buffer 325 m := v.(map[string]interface{}) 326 buf.WriteString(fmt.Sprintf("%d-", m["from_port"].(int))) 327 buf.WriteString(fmt.Sprintf("%d-", m["to_port"].(int))) 328 buf.WriteString(fmt.Sprintf("%s-", m["protocol"].(string))) 329 buf.WriteString(fmt.Sprintf("%t-", m["self"].(bool))) 330 331 // We need to make sure to sort the strings below so that we always 332 // generate the same hash code no matter what is in the set. 333 if v, ok := m["cidr_blocks"]; ok { 334 vs := v.([]interface{}) 335 s := make([]string, len(vs)) 336 for i, raw := range vs { 337 s[i] = raw.(string) 338 } 339 sort.Strings(s) 340 341 for _, v := range s { 342 buf.WriteString(fmt.Sprintf("%s-", v)) 343 } 344 } 345 if v, ok := m["security_groups"]; ok { 346 vs := v.(*schema.Set).List() 347 s := make([]string, len(vs)) 348 for i, raw := range vs { 349 s[i] = raw.(string) 350 } 351 sort.Strings(s) 352 353 for _, v := range s { 354 buf.WriteString(fmt.Sprintf("%s-", v)) 355 } 356 } 357 358 return hashcode.String(buf.String()) 359 } 360 361 func resourceAwsSecurityGroupIPPermGather(d *schema.ResourceData, permissions []*ec2.IPPermission) []map[string]interface{} { 362 ruleMap := make(map[string]map[string]interface{}) 363 for _, perm := range permissions { 364 var fromPort, toPort int64 365 if v := perm.FromPort; v != nil { 366 fromPort = *v 367 } 368 if v := perm.ToPort; v != nil { 369 toPort = *v 370 } 371 372 k := fmt.Sprintf("%s-%d-%d", *perm.IPProtocol, fromPort, toPort) 373 m, ok := ruleMap[k] 374 if !ok { 375 m = make(map[string]interface{}) 376 ruleMap[k] = m 377 } 378 379 m["from_port"] = fromPort 380 m["to_port"] = toPort 381 m["protocol"] = *perm.IPProtocol 382 383 if len(perm.IPRanges) > 0 { 384 raw, ok := m["cidr_blocks"] 385 if !ok { 386 raw = make([]string, 0, len(perm.IPRanges)) 387 } 388 list := raw.([]string) 389 390 for _, ip := range perm.IPRanges { 391 list = append(list, *ip.CIDRIP) 392 } 393 394 m["cidr_blocks"] = list 395 } 396 397 var groups []string 398 if len(perm.UserIDGroupPairs) > 0 { 399 groups = flattenSecurityGroups(perm.UserIDGroupPairs) 400 } 401 for i, id := range groups { 402 if id == d.Id() { 403 groups[i], groups = groups[len(groups)-1], groups[:len(groups)-1] 404 m["self"] = true 405 } 406 } 407 408 if len(groups) > 0 { 409 raw, ok := m["security_groups"] 410 if !ok { 411 raw = make([]string, 0, len(groups)) 412 } 413 list := raw.([]string) 414 415 list = append(list, groups...) 416 m["security_groups"] = list 417 } 418 } 419 rules := make([]map[string]interface{}, 0, len(ruleMap)) 420 for _, m := range ruleMap { 421 rules = append(rules, m) 422 } 423 return rules 424 } 425 426 func resourceAwsSecurityGroupUpdateRules( 427 d *schema.ResourceData, ruleset string, 428 meta interface{}, group *ec2.SecurityGroup) error { 429 430 if d.HasChange(ruleset) { 431 o, n := d.GetChange(ruleset) 432 if o == nil { 433 o = new(schema.Set) 434 } 435 if n == nil { 436 n = new(schema.Set) 437 } 438 439 os := o.(*schema.Set) 440 ns := n.(*schema.Set) 441 442 remove := expandIPPerms(group, os.Difference(ns).List()) 443 add := expandIPPerms(group, ns.Difference(os).List()) 444 445 // TODO: We need to handle partial state better in the in-between 446 // in this update. 447 448 // TODO: It'd be nicer to authorize before removing, but then we have 449 // to deal with complicated unrolling to get individual CIDR blocks 450 // to avoid authorizing already authorized sources. Removing before 451 // adding is easier here, and Terraform should be fast enough to 452 // not have service issues. 453 454 if len(remove) > 0 || len(add) > 0 { 455 conn := meta.(*AWSClient).ec2conn 456 457 var err error 458 if len(remove) > 0 { 459 log.Printf("[DEBUG] Revoking security group %#v %s rule: %#v", 460 group, ruleset, remove) 461 462 if ruleset == "egress" { 463 req := &ec2.RevokeSecurityGroupEgressInput{ 464 GroupID: group.GroupID, 465 IPPermissions: remove, 466 } 467 _, err = conn.RevokeSecurityGroupEgress(req) 468 } else { 469 req := &ec2.RevokeSecurityGroupIngressInput{ 470 GroupID: group.GroupID, 471 IPPermissions: remove, 472 } 473 _, err = conn.RevokeSecurityGroupIngress(req) 474 } 475 476 if err != nil { 477 return fmt.Errorf( 478 "Error authorizing security group %s rules: %s", 479 ruleset, err) 480 } 481 } 482 483 if len(add) > 0 { 484 log.Printf("[DEBUG] Authorizing security group %#v %s rule: %#v", 485 group, ruleset, add) 486 // Authorize the new rules 487 if ruleset == "egress" { 488 req := &ec2.AuthorizeSecurityGroupEgressInput{ 489 GroupID: group.GroupID, 490 IPPermissions: add, 491 } 492 _, err = conn.AuthorizeSecurityGroupEgress(req) 493 } else { 494 req := &ec2.AuthorizeSecurityGroupIngressInput{ 495 GroupID: group.GroupID, 496 IPPermissions: add, 497 } 498 if group.VPCID == nil || *group.VPCID == "" { 499 req.GroupID = nil 500 req.GroupName = group.GroupName 501 } 502 503 _, err = conn.AuthorizeSecurityGroupIngress(req) 504 } 505 506 if err != nil { 507 return fmt.Errorf( 508 "Error authorizing security group %s rules: %s", 509 ruleset, err) 510 } 511 } 512 } 513 } 514 return nil 515 } 516 517 // SGStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch 518 // a security group. 519 func SGStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc { 520 return func() (interface{}, string, error) { 521 req := &ec2.DescribeSecurityGroupsInput{ 522 GroupIDs: []*string{aws.String(id)}, 523 } 524 resp, err := conn.DescribeSecurityGroups(req) 525 if err != nil { 526 if ec2err, ok := err.(aws.APIError); ok { 527 if ec2err.Code == "InvalidSecurityGroupID.NotFound" || 528 ec2err.Code == "InvalidGroup.NotFound" { 529 resp = nil 530 err = nil 531 } 532 } 533 534 if err != nil { 535 log.Printf("Error on SGStateRefresh: %s", err) 536 return nil, "", err 537 } 538 } 539 540 if resp == nil { 541 return nil, "", nil 542 } 543 544 group := resp.SecurityGroups[0] 545 return group, "exists", nil 546 } 547 }