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