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