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