github.com/vtorhonen/terraform@v0.9.0-beta2.0.20170307220345-5d894e4ffda7/builtin/providers/aws/resource_aws_security_group.go (about) 1 package aws 2 3 import ( 4 "bytes" 5 "fmt" 6 "log" 7 "sort" 8 "strconv" 9 "strings" 10 "time" 11 12 "github.com/aws/aws-sdk-go/aws" 13 "github.com/aws/aws-sdk-go/aws/awserr" 14 "github.com/aws/aws-sdk-go/service/ec2" 15 "github.com/hashicorp/terraform/helper/hashcode" 16 "github.com/hashicorp/terraform/helper/resource" 17 "github.com/hashicorp/terraform/helper/schema" 18 ) 19 20 func resourceAwsSecurityGroup() *schema.Resource { 21 return &schema.Resource{ 22 Create: resourceAwsSecurityGroupCreate, 23 Read: resourceAwsSecurityGroupRead, 24 Update: resourceAwsSecurityGroupUpdate, 25 Delete: resourceAwsSecurityGroupDelete, 26 Importer: &schema.ResourceImporter{ 27 State: resourceAwsSecurityGroupImportState, 28 }, 29 30 Schema: map[string]*schema.Schema{ 31 "name": &schema.Schema{ 32 Type: schema.TypeString, 33 Optional: true, 34 Computed: true, 35 ForceNew: true, 36 ConflictsWith: []string{"name_prefix"}, 37 ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { 38 value := v.(string) 39 if len(value) > 255 { 40 errors = append(errors, fmt.Errorf( 41 "%q cannot be longer than 255 characters", k)) 42 } 43 return 44 }, 45 }, 46 47 "name_prefix": &schema.Schema{ 48 Type: schema.TypeString, 49 Optional: true, 50 ForceNew: true, 51 ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { 52 value := v.(string) 53 if len(value) > 100 { 54 errors = append(errors, fmt.Errorf( 55 "%q cannot be longer than 100 characters, name is limited to 255", k)) 56 } 57 return 58 }, 59 }, 60 61 "description": &schema.Schema{ 62 Type: schema.TypeString, 63 Optional: true, 64 ForceNew: true, 65 Default: "Managed by Terraform", 66 ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { 67 value := v.(string) 68 if len(value) > 255 { 69 errors = append(errors, fmt.Errorf( 70 "%q cannot be longer than 255 characters", k)) 71 } 72 return 73 }, 74 }, 75 76 "vpc_id": &schema.Schema{ 77 Type: schema.TypeString, 78 Optional: true, 79 ForceNew: true, 80 Computed: true, 81 }, 82 83 "ingress": &schema.Schema{ 84 Type: schema.TypeSet, 85 Optional: true, 86 Computed: true, 87 Elem: &schema.Resource{ 88 Schema: map[string]*schema.Schema{ 89 "from_port": &schema.Schema{ 90 Type: schema.TypeInt, 91 Required: true, 92 }, 93 94 "to_port": &schema.Schema{ 95 Type: schema.TypeInt, 96 Required: true, 97 }, 98 99 "protocol": &schema.Schema{ 100 Type: schema.TypeString, 101 Required: true, 102 StateFunc: protocolStateFunc, 103 }, 104 105 "cidr_blocks": &schema.Schema{ 106 Type: schema.TypeList, 107 Optional: true, 108 Elem: &schema.Schema{Type: schema.TypeString}, 109 }, 110 111 "security_groups": &schema.Schema{ 112 Type: schema.TypeSet, 113 Optional: true, 114 Elem: &schema.Schema{Type: schema.TypeString}, 115 Set: schema.HashString, 116 }, 117 118 "self": &schema.Schema{ 119 Type: schema.TypeBool, 120 Optional: true, 121 Default: false, 122 }, 123 }, 124 }, 125 Set: resourceAwsSecurityGroupRuleHash, 126 }, 127 128 "egress": &schema.Schema{ 129 Type: schema.TypeSet, 130 Optional: true, 131 Computed: true, 132 Elem: &schema.Resource{ 133 Schema: map[string]*schema.Schema{ 134 "from_port": &schema.Schema{ 135 Type: schema.TypeInt, 136 Required: true, 137 }, 138 139 "to_port": &schema.Schema{ 140 Type: schema.TypeInt, 141 Required: true, 142 }, 143 144 "protocol": &schema.Schema{ 145 Type: schema.TypeString, 146 Required: true, 147 StateFunc: protocolStateFunc, 148 }, 149 150 "cidr_blocks": &schema.Schema{ 151 Type: schema.TypeList, 152 Optional: true, 153 Elem: &schema.Schema{Type: schema.TypeString}, 154 }, 155 156 "prefix_list_ids": &schema.Schema{ 157 Type: schema.TypeList, 158 Optional: true, 159 Elem: &schema.Schema{Type: schema.TypeString}, 160 }, 161 162 "security_groups": &schema.Schema{ 163 Type: schema.TypeSet, 164 Optional: true, 165 Elem: &schema.Schema{Type: schema.TypeString}, 166 Set: schema.HashString, 167 }, 168 169 "self": &schema.Schema{ 170 Type: schema.TypeBool, 171 Optional: true, 172 Default: false, 173 }, 174 }, 175 }, 176 Set: resourceAwsSecurityGroupRuleHash, 177 }, 178 179 "owner_id": &schema.Schema{ 180 Type: schema.TypeString, 181 Computed: true, 182 }, 183 184 "tags": tagsSchema(), 185 }, 186 } 187 } 188 189 func resourceAwsSecurityGroupCreate(d *schema.ResourceData, meta interface{}) error { 190 conn := meta.(*AWSClient).ec2conn 191 192 securityGroupOpts := &ec2.CreateSecurityGroupInput{} 193 194 if v, ok := d.GetOk("vpc_id"); ok { 195 securityGroupOpts.VpcId = aws.String(v.(string)) 196 } 197 198 if v := d.Get("description"); v != nil { 199 securityGroupOpts.Description = aws.String(v.(string)) 200 } 201 202 var groupName string 203 if v, ok := d.GetOk("name"); ok { 204 groupName = v.(string) 205 } else if v, ok := d.GetOk("name_prefix"); ok { 206 groupName = resource.PrefixedUniqueId(v.(string)) 207 } else { 208 groupName = resource.UniqueId() 209 } 210 securityGroupOpts.GroupName = aws.String(groupName) 211 212 var err error 213 log.Printf( 214 "[DEBUG] Security Group create configuration: %#v", securityGroupOpts) 215 createResp, err := conn.CreateSecurityGroup(securityGroupOpts) 216 if err != nil { 217 return fmt.Errorf("Error creating Security Group: %s", err) 218 } 219 220 d.SetId(*createResp.GroupId) 221 222 log.Printf("[INFO] Security Group ID: %s", d.Id()) 223 224 // Wait for the security group to truly exist 225 log.Printf( 226 "[DEBUG] Waiting for Security Group (%s) to exist", 227 d.Id()) 228 stateConf := &resource.StateChangeConf{ 229 Pending: []string{""}, 230 Target: []string{"exists"}, 231 Refresh: SGStateRefreshFunc(conn, d.Id()), 232 Timeout: 1 * time.Minute, 233 } 234 235 resp, err := stateConf.WaitForState() 236 if err != nil { 237 return fmt.Errorf( 238 "Error waiting for Security Group (%s) to become available: %s", 239 d.Id(), err) 240 } 241 242 if err := setTags(conn, d); err != nil { 243 return err 244 } 245 246 // AWS defaults all Security Groups to have an ALLOW ALL egress rule. Here we 247 // revoke that rule, so users don't unknowingly have/use it. 248 group := resp.(*ec2.SecurityGroup) 249 if group.VpcId != nil && *group.VpcId != "" { 250 log.Printf("[DEBUG] Revoking default egress rule for Security Group for %s", d.Id()) 251 252 req := &ec2.RevokeSecurityGroupEgressInput{ 253 GroupId: createResp.GroupId, 254 IpPermissions: []*ec2.IpPermission{ 255 &ec2.IpPermission{ 256 FromPort: aws.Int64(int64(0)), 257 ToPort: aws.Int64(int64(0)), 258 IpRanges: []*ec2.IpRange{ 259 &ec2.IpRange{ 260 CidrIp: aws.String("0.0.0.0/0"), 261 }, 262 }, 263 IpProtocol: aws.String("-1"), 264 }, 265 }, 266 } 267 268 if _, err = conn.RevokeSecurityGroupEgress(req); err != nil { 269 return fmt.Errorf( 270 "Error revoking default egress rule for Security Group (%s): %s", 271 d.Id(), err) 272 } 273 274 } 275 276 return resourceAwsSecurityGroupUpdate(d, meta) 277 } 278 279 func resourceAwsSecurityGroupRead(d *schema.ResourceData, meta interface{}) error { 280 conn := meta.(*AWSClient).ec2conn 281 282 sgRaw, _, err := SGStateRefreshFunc(conn, d.Id())() 283 if err != nil { 284 return err 285 } 286 if sgRaw == nil { 287 d.SetId("") 288 return nil 289 } 290 291 sg := sgRaw.(*ec2.SecurityGroup) 292 293 remoteIngressRules := resourceAwsSecurityGroupIPPermGather(d.Id(), sg.IpPermissions, sg.OwnerId) 294 remoteEgressRules := resourceAwsSecurityGroupIPPermGather(d.Id(), sg.IpPermissionsEgress, sg.OwnerId) 295 296 localIngressRules := d.Get("ingress").(*schema.Set).List() 297 localEgressRules := d.Get("egress").(*schema.Set).List() 298 299 // Loop through the local state of rules, doing a match against the remote 300 // ruleSet we built above. 301 ingressRules := matchRules("ingress", localIngressRules, remoteIngressRules) 302 egressRules := matchRules("egress", localEgressRules, remoteEgressRules) 303 304 d.Set("description", sg.Description) 305 d.Set("name", sg.GroupName) 306 d.Set("vpc_id", sg.VpcId) 307 d.Set("owner_id", sg.OwnerId) 308 309 if err := d.Set("ingress", ingressRules); err != nil { 310 log.Printf("[WARN] Error setting Ingress rule set for (%s): %s", d.Id(), err) 311 } 312 313 if err := d.Set("egress", egressRules); err != nil { 314 log.Printf("[WARN] Error setting Egress rule set for (%s): %s", d.Id(), err) 315 } 316 317 d.Set("tags", tagsToMap(sg.Tags)) 318 return nil 319 } 320 321 func resourceAwsSecurityGroupUpdate(d *schema.ResourceData, meta interface{}) error { 322 conn := meta.(*AWSClient).ec2conn 323 324 sgRaw, _, err := SGStateRefreshFunc(conn, d.Id())() 325 if err != nil { 326 return err 327 } 328 if sgRaw == nil { 329 d.SetId("") 330 return nil 331 } 332 333 group := sgRaw.(*ec2.SecurityGroup) 334 335 err = resourceAwsSecurityGroupUpdateRules(d, "ingress", meta, group) 336 if err != nil { 337 return err 338 } 339 340 if d.Get("vpc_id") != nil { 341 err = resourceAwsSecurityGroupUpdateRules(d, "egress", meta, group) 342 if err != nil { 343 return err 344 } 345 } 346 347 if !d.IsNewResource() { 348 if err := setTags(conn, d); err != nil { 349 return err 350 } 351 d.SetPartial("tags") 352 } 353 354 return resourceAwsSecurityGroupRead(d, meta) 355 } 356 357 func resourceAwsSecurityGroupDelete(d *schema.ResourceData, meta interface{}) error { 358 conn := meta.(*AWSClient).ec2conn 359 360 log.Printf("[DEBUG] Security Group destroy: %v", d.Id()) 361 362 if err := deleteLingeringLambdaENIs(conn, d); err != nil { 363 return fmt.Errorf("Failed to delete Lambda ENIs: %s", err) 364 } 365 366 return resource.Retry(5*time.Minute, func() *resource.RetryError { 367 _, err := conn.DeleteSecurityGroup(&ec2.DeleteSecurityGroupInput{ 368 GroupId: aws.String(d.Id()), 369 }) 370 if err != nil { 371 ec2err, ok := err.(awserr.Error) 372 if !ok { 373 return resource.RetryableError(err) 374 } 375 376 switch ec2err.Code() { 377 case "InvalidGroup.NotFound": 378 return nil 379 case "DependencyViolation": 380 // If it is a dependency violation, we want to retry 381 return resource.RetryableError(err) 382 default: 383 // Any other error, we want to quit the retry loop immediately 384 return resource.NonRetryableError(err) 385 } 386 } 387 388 return nil 389 }) 390 } 391 392 func resourceAwsSecurityGroupRuleHash(v interface{}) int { 393 var buf bytes.Buffer 394 m := v.(map[string]interface{}) 395 buf.WriteString(fmt.Sprintf("%d-", m["from_port"].(int))) 396 buf.WriteString(fmt.Sprintf("%d-", m["to_port"].(int))) 397 p := protocolForValue(m["protocol"].(string)) 398 buf.WriteString(fmt.Sprintf("%s-", p)) 399 buf.WriteString(fmt.Sprintf("%t-", m["self"].(bool))) 400 401 // We need to make sure to sort the strings below so that we always 402 // generate the same hash code no matter what is in the set. 403 if v, ok := m["cidr_blocks"]; ok { 404 vs := v.([]interface{}) 405 s := make([]string, len(vs)) 406 for i, raw := range vs { 407 s[i] = raw.(string) 408 } 409 sort.Strings(s) 410 411 for _, v := range s { 412 buf.WriteString(fmt.Sprintf("%s-", v)) 413 } 414 } 415 if v, ok := m["prefix_list_ids"]; ok { 416 vs := v.([]interface{}) 417 s := make([]string, len(vs)) 418 for i, raw := range vs { 419 s[i] = raw.(string) 420 } 421 sort.Strings(s) 422 423 for _, v := range s { 424 buf.WriteString(fmt.Sprintf("%s-", v)) 425 } 426 } 427 if v, ok := m["security_groups"]; ok { 428 vs := v.(*schema.Set).List() 429 s := make([]string, len(vs)) 430 for i, raw := range vs { 431 s[i] = raw.(string) 432 } 433 sort.Strings(s) 434 435 for _, v := range s { 436 buf.WriteString(fmt.Sprintf("%s-", v)) 437 } 438 } 439 440 return hashcode.String(buf.String()) 441 } 442 443 func resourceAwsSecurityGroupIPPermGather(groupId string, permissions []*ec2.IpPermission, ownerId *string) []map[string]interface{} { 444 ruleMap := make(map[string]map[string]interface{}) 445 for _, perm := range permissions { 446 var fromPort, toPort int64 447 if v := perm.FromPort; v != nil { 448 fromPort = *v 449 } 450 if v := perm.ToPort; v != nil { 451 toPort = *v 452 } 453 454 k := fmt.Sprintf("%s-%d-%d", *perm.IpProtocol, fromPort, toPort) 455 m, ok := ruleMap[k] 456 if !ok { 457 m = make(map[string]interface{}) 458 ruleMap[k] = m 459 } 460 461 m["from_port"] = fromPort 462 m["to_port"] = toPort 463 m["protocol"] = *perm.IpProtocol 464 465 if len(perm.IpRanges) > 0 { 466 raw, ok := m["cidr_blocks"] 467 if !ok { 468 raw = make([]string, 0, len(perm.IpRanges)) 469 } 470 list := raw.([]string) 471 472 for _, ip := range perm.IpRanges { 473 list = append(list, *ip.CidrIp) 474 } 475 476 m["cidr_blocks"] = list 477 } 478 479 if len(perm.PrefixListIds) > 0 { 480 raw, ok := m["prefix_list_ids"] 481 if !ok { 482 raw = make([]string, 0, len(perm.PrefixListIds)) 483 } 484 list := raw.([]string) 485 486 for _, pl := range perm.PrefixListIds { 487 list = append(list, *pl.PrefixListId) 488 } 489 490 m["prefix_list_ids"] = list 491 } 492 493 groups := flattenSecurityGroups(perm.UserIdGroupPairs, ownerId) 494 for i, g := range groups { 495 if *g.GroupId == groupId { 496 groups[i], groups = groups[len(groups)-1], groups[:len(groups)-1] 497 m["self"] = true 498 } 499 } 500 501 if len(groups) > 0 { 502 raw, ok := m["security_groups"] 503 if !ok { 504 raw = schema.NewSet(schema.HashString, nil) 505 } 506 list := raw.(*schema.Set) 507 508 for _, g := range groups { 509 if g.GroupName != nil { 510 list.Add(*g.GroupName) 511 } else { 512 list.Add(*g.GroupId) 513 } 514 } 515 516 m["security_groups"] = list 517 } 518 } 519 rules := make([]map[string]interface{}, 0, len(ruleMap)) 520 for _, m := range ruleMap { 521 rules = append(rules, m) 522 } 523 524 return rules 525 } 526 527 func resourceAwsSecurityGroupUpdateRules( 528 d *schema.ResourceData, ruleset string, 529 meta interface{}, group *ec2.SecurityGroup) error { 530 531 if d.HasChange(ruleset) { 532 o, n := d.GetChange(ruleset) 533 if o == nil { 534 o = new(schema.Set) 535 } 536 if n == nil { 537 n = new(schema.Set) 538 } 539 540 os := o.(*schema.Set) 541 ns := n.(*schema.Set) 542 543 remove, err := expandIPPerms(group, os.Difference(ns).List()) 544 if err != nil { 545 return err 546 } 547 add, err := expandIPPerms(group, ns.Difference(os).List()) 548 if err != nil { 549 return err 550 } 551 552 // TODO: We need to handle partial state better in the in-between 553 // in this update. 554 555 // TODO: It'd be nicer to authorize before removing, but then we have 556 // to deal with complicated unrolling to get individual CIDR blocks 557 // to avoid authorizing already authorized sources. Removing before 558 // adding is easier here, and Terraform should be fast enough to 559 // not have service issues. 560 561 if len(remove) > 0 || len(add) > 0 { 562 conn := meta.(*AWSClient).ec2conn 563 564 var err error 565 if len(remove) > 0 { 566 log.Printf("[DEBUG] Revoking security group %#v %s rule: %#v", 567 group, ruleset, remove) 568 569 if ruleset == "egress" { 570 req := &ec2.RevokeSecurityGroupEgressInput{ 571 GroupId: group.GroupId, 572 IpPermissions: remove, 573 } 574 _, err = conn.RevokeSecurityGroupEgress(req) 575 } else { 576 req := &ec2.RevokeSecurityGroupIngressInput{ 577 GroupId: group.GroupId, 578 IpPermissions: remove, 579 } 580 if group.VpcId == nil || *group.VpcId == "" { 581 req.GroupId = nil 582 req.GroupName = group.GroupName 583 } 584 _, err = conn.RevokeSecurityGroupIngress(req) 585 } 586 587 if err != nil { 588 return fmt.Errorf( 589 "Error revoking security group %s rules: %s", 590 ruleset, err) 591 } 592 } 593 594 if len(add) > 0 { 595 log.Printf("[DEBUG] Authorizing security group %#v %s rule: %#v", 596 group, ruleset, add) 597 // Authorize the new rules 598 if ruleset == "egress" { 599 req := &ec2.AuthorizeSecurityGroupEgressInput{ 600 GroupId: group.GroupId, 601 IpPermissions: add, 602 } 603 _, err = conn.AuthorizeSecurityGroupEgress(req) 604 } else { 605 req := &ec2.AuthorizeSecurityGroupIngressInput{ 606 GroupId: group.GroupId, 607 IpPermissions: add, 608 } 609 if group.VpcId == nil || *group.VpcId == "" { 610 req.GroupId = nil 611 req.GroupName = group.GroupName 612 } 613 614 _, err = conn.AuthorizeSecurityGroupIngress(req) 615 } 616 617 if err != nil { 618 return fmt.Errorf( 619 "Error authorizing security group %s rules: %s", 620 ruleset, err) 621 } 622 } 623 } 624 } 625 return nil 626 } 627 628 // SGStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch 629 // a security group. 630 func SGStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc { 631 return func() (interface{}, string, error) { 632 req := &ec2.DescribeSecurityGroupsInput{ 633 GroupIds: []*string{aws.String(id)}, 634 } 635 resp, err := conn.DescribeSecurityGroups(req) 636 if err != nil { 637 if ec2err, ok := err.(awserr.Error); ok { 638 if ec2err.Code() == "InvalidSecurityGroupID.NotFound" || 639 ec2err.Code() == "InvalidGroup.NotFound" { 640 resp = nil 641 err = nil 642 } 643 } 644 645 if err != nil { 646 log.Printf("Error on SGStateRefresh: %s", err) 647 return nil, "", err 648 } 649 } 650 651 if resp == nil { 652 return nil, "", nil 653 } 654 655 group := resp.SecurityGroups[0] 656 return group, "exists", nil 657 } 658 } 659 660 // matchRules receives the group id, type of rules, and the local / remote maps 661 // of rules. We iterate through the local set of rules trying to find a matching 662 // remote rule, which may be structured differently because of how AWS 663 // aggregates the rules under the to, from, and type. 664 // 665 // 666 // Matching rules are written to state, with their elements removed from the 667 // remote set 668 // 669 // If no match is found, we'll write the remote rule to state and let the graph 670 // sort things out 671 func matchRules(rType string, local []interface{}, remote []map[string]interface{}) []map[string]interface{} { 672 // For each local ip or security_group, we need to match against the remote 673 // ruleSet until all ips or security_groups are found 674 675 // saves represents the rules that have been identified to be saved to state, 676 // in the appropriate d.Set("{ingress,egress}") call. 677 var saves []map[string]interface{} 678 for _, raw := range local { 679 l := raw.(map[string]interface{}) 680 681 var selfVal bool 682 if v, ok := l["self"]; ok { 683 selfVal = v.(bool) 684 } 685 686 // matching against self is required to detect rules that only include self 687 // as the rule. resourceAwsSecurityGroupIPPermGather parses the group out 688 // and replaces it with self if it's ID is found 689 localHash := idHash(rType, l["protocol"].(string), int64(l["to_port"].(int)), int64(l["from_port"].(int)), selfVal) 690 691 // loop remote rules, looking for a matching hash 692 for _, r := range remote { 693 var remoteSelfVal bool 694 if v, ok := r["self"]; ok { 695 remoteSelfVal = v.(bool) 696 } 697 698 // hash this remote rule and compare it for a match consideration with the 699 // local rule we're examining 700 rHash := idHash(rType, r["protocol"].(string), r["to_port"].(int64), r["from_port"].(int64), remoteSelfVal) 701 if rHash == localHash { 702 var numExpectedCidrs, numExpectedPrefixLists, numExpectedSGs, numRemoteCidrs, numRemotePrefixLists, numRemoteSGs int 703 var matchingCidrs []string 704 var matchingSGs []string 705 var matchingPrefixLists []string 706 707 // grab the local/remote cidr and sg groups, capturing the expected and 708 // actual counts 709 lcRaw, ok := l["cidr_blocks"] 710 if ok { 711 numExpectedCidrs = len(l["cidr_blocks"].([]interface{})) 712 } 713 lpRaw, ok := l["prefix_list_ids"] 714 if ok { 715 numExpectedPrefixLists = len(l["prefix_list_ids"].([]interface{})) 716 } 717 lsRaw, ok := l["security_groups"] 718 if ok { 719 numExpectedSGs = len(l["security_groups"].(*schema.Set).List()) 720 } 721 722 rcRaw, ok := r["cidr_blocks"] 723 if ok { 724 numRemoteCidrs = len(r["cidr_blocks"].([]string)) 725 } 726 rpRaw, ok := r["prefix_list_ids"] 727 if ok { 728 numRemotePrefixLists = len(r["prefix_list_ids"].([]string)) 729 } 730 731 rsRaw, ok := r["security_groups"] 732 if ok { 733 numRemoteSGs = len(r["security_groups"].(*schema.Set).List()) 734 } 735 736 // check some early failures 737 if numExpectedCidrs > numRemoteCidrs { 738 log.Printf("[DEBUG] Local rule has more CIDR blocks, continuing (%d/%d)", numExpectedCidrs, numRemoteCidrs) 739 continue 740 } 741 if numExpectedPrefixLists > numRemotePrefixLists { 742 log.Printf("[DEBUG] Local rule has more prefix lists, continuing (%d/%d)", numExpectedPrefixLists, numRemotePrefixLists) 743 continue 744 } 745 if numExpectedSGs > numRemoteSGs { 746 log.Printf("[DEBUG] Local rule has more Security Groups, continuing (%d/%d)", numExpectedSGs, numRemoteSGs) 747 continue 748 } 749 750 // match CIDRs by converting both to sets, and using Set methods 751 var localCidrs []interface{} 752 if lcRaw != nil { 753 localCidrs = lcRaw.([]interface{}) 754 } 755 localCidrSet := schema.NewSet(schema.HashString, localCidrs) 756 757 // remote cidrs are presented as a slice of strings, so we need to 758 // reformat them into a slice of interfaces to be used in creating the 759 // remote cidr set 760 var remoteCidrs []string 761 if rcRaw != nil { 762 remoteCidrs = rcRaw.([]string) 763 } 764 // convert remote cidrs to a set, for easy comparisons 765 var list []interface{} 766 for _, s := range remoteCidrs { 767 list = append(list, s) 768 } 769 remoteCidrSet := schema.NewSet(schema.HashString, list) 770 771 // Build up a list of local cidrs that are found in the remote set 772 for _, s := range localCidrSet.List() { 773 if remoteCidrSet.Contains(s) { 774 matchingCidrs = append(matchingCidrs, s.(string)) 775 } 776 } 777 778 // match prefix lists by converting both to sets, and using Set methods 779 var localPrefixLists []interface{} 780 if lpRaw != nil { 781 localPrefixLists = lpRaw.([]interface{}) 782 } 783 localPrefixListsSet := schema.NewSet(schema.HashString, localPrefixLists) 784 785 // remote prefix lists are presented as a slice of strings, so we need to 786 // reformat them into a slice of interfaces to be used in creating the 787 // remote prefix list set 788 var remotePrefixLists []string 789 if rpRaw != nil { 790 remotePrefixLists = rpRaw.([]string) 791 } 792 // convert remote prefix lists to a set, for easy comparison 793 list = nil 794 for _, s := range remotePrefixLists { 795 list = append(list, s) 796 } 797 remotePrefixListsSet := schema.NewSet(schema.HashString, list) 798 799 // Build up a list of local prefix lists that are found in the remote set 800 for _, s := range localPrefixListsSet.List() { 801 if remotePrefixListsSet.Contains(s) { 802 matchingPrefixLists = append(matchingPrefixLists, s.(string)) 803 } 804 } 805 806 // match SGs. Both local and remote are already sets 807 var localSGSet *schema.Set 808 if lsRaw == nil { 809 localSGSet = schema.NewSet(schema.HashString, nil) 810 } else { 811 localSGSet = lsRaw.(*schema.Set) 812 } 813 814 var remoteSGSet *schema.Set 815 if rsRaw == nil { 816 remoteSGSet = schema.NewSet(schema.HashString, nil) 817 } else { 818 remoteSGSet = rsRaw.(*schema.Set) 819 } 820 821 // Build up a list of local security groups that are found in the remote set 822 for _, s := range localSGSet.List() { 823 if remoteSGSet.Contains(s) { 824 matchingSGs = append(matchingSGs, s.(string)) 825 } 826 } 827 828 // compare equalities for matches. 829 // If we found the number of cidrs and number of sgs, we declare a 830 // match, and then remove those elements from the remote rule, so that 831 // this remote rule can still be considered by other local rules 832 if numExpectedCidrs == len(matchingCidrs) { 833 if numExpectedPrefixLists == len(matchingPrefixLists) { 834 if numExpectedSGs == len(matchingSGs) { 835 // confirm that self references match 836 var lSelf bool 837 var rSelf bool 838 if _, ok := l["self"]; ok { 839 lSelf = l["self"].(bool) 840 } 841 if _, ok := r["self"]; ok { 842 rSelf = r["self"].(bool) 843 } 844 if rSelf == lSelf { 845 delete(r, "self") 846 // pop local cidrs from remote 847 diffCidr := remoteCidrSet.Difference(localCidrSet) 848 var newCidr []string 849 for _, cRaw := range diffCidr.List() { 850 newCidr = append(newCidr, cRaw.(string)) 851 } 852 853 // reassigning 854 if len(newCidr) > 0 { 855 r["cidr_blocks"] = newCidr 856 } else { 857 delete(r, "cidr_blocks") 858 } 859 860 // pop local prefix lists from remote 861 diffPrefixLists := remotePrefixListsSet.Difference(localPrefixListsSet) 862 var newPrefixLists []string 863 for _, pRaw := range diffPrefixLists.List() { 864 newPrefixLists = append(newPrefixLists, pRaw.(string)) 865 } 866 867 // reassigning 868 if len(newPrefixLists) > 0 { 869 r["prefix_list_ids"] = newPrefixLists 870 } else { 871 delete(r, "prefix_list_ids") 872 } 873 874 // pop local sgs from remote 875 diffSGs := remoteSGSet.Difference(localSGSet) 876 if len(diffSGs.List()) > 0 { 877 r["security_groups"] = diffSGs 878 } else { 879 delete(r, "security_groups") 880 } 881 882 saves = append(saves, l) 883 } 884 } 885 } 886 } 887 } 888 } 889 } 890 891 // Here we catch any remote rules that have not been stripped of all self, 892 // cidrs, and security groups. We'll add remote rules here that have not been 893 // matched locally, and let the graph sort things out. This will happen when 894 // rules are added externally to Terraform 895 for _, r := range remote { 896 var lenCidr, lenPrefixLists, lenSGs int 897 if rCidrs, ok := r["cidr_blocks"]; ok { 898 lenCidr = len(rCidrs.([]string)) 899 } 900 if rPrefixLists, ok := r["prefix_list_ids"]; ok { 901 lenPrefixLists = len(rPrefixLists.([]string)) 902 } 903 if rawSGs, ok := r["security_groups"]; ok { 904 lenSGs = len(rawSGs.(*schema.Set).List()) 905 } 906 907 if _, ok := r["self"]; ok { 908 if r["self"].(bool) == true { 909 lenSGs++ 910 } 911 } 912 913 if lenSGs+lenCidr+lenPrefixLists > 0 { 914 log.Printf("[DEBUG] Found a remote Rule that wasn't empty: (%#v)", r) 915 saves = append(saves, r) 916 } 917 } 918 919 return saves 920 } 921 922 // Creates a unique hash for the type, ports, and protocol, used as a key in 923 // maps 924 func idHash(rType, protocol string, toPort, fromPort int64, self bool) string { 925 var buf bytes.Buffer 926 buf.WriteString(fmt.Sprintf("%s-", rType)) 927 buf.WriteString(fmt.Sprintf("%d-", toPort)) 928 buf.WriteString(fmt.Sprintf("%d-", fromPort)) 929 buf.WriteString(fmt.Sprintf("%s-", strings.ToLower(protocol))) 930 buf.WriteString(fmt.Sprintf("%t-", self)) 931 932 return fmt.Sprintf("rule-%d", hashcode.String(buf.String())) 933 } 934 935 // protocolStateFunc ensures we only store a string in any protocol field 936 func protocolStateFunc(v interface{}) string { 937 switch v.(type) { 938 case string: 939 p := protocolForValue(v.(string)) 940 return p 941 default: 942 log.Printf("[WARN] Non String value given for Protocol: %#v", v) 943 return "" 944 } 945 } 946 947 // protocolForValue converts a valid Internet Protocol number into it's name 948 // representation. If a name is given, it validates that it's a proper protocol 949 // name. Names/numbers are as defined at 950 // https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml 951 func protocolForValue(v string) string { 952 // special case -1 953 protocol := strings.ToLower(v) 954 if protocol == "-1" || protocol == "all" { 955 return "-1" 956 } 957 // if it's a name like tcp, return that 958 if _, ok := sgProtocolIntegers()[protocol]; ok { 959 return protocol 960 } 961 // convert to int, look for that value 962 p, err := strconv.Atoi(protocol) 963 if err != nil { 964 // we were unable to convert to int, suggesting a string name, but it wasn't 965 // found above 966 log.Printf("[WARN] Unable to determine valid protocol: %s", err) 967 return protocol 968 } 969 970 for k, v := range sgProtocolIntegers() { 971 if p == v { 972 // guard against protocolIntegers sometime in the future not having lower 973 // case ids in the map 974 return strings.ToLower(k) 975 } 976 } 977 978 // fall through 979 log.Printf("[WARN] Unable to determine valid protocol: no matching protocols found") 980 return protocol 981 } 982 983 // a map of protocol names and their codes, defined at 984 // https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml, 985 // documented to be supported by AWS Security Groups 986 // http://docs.aws.amazon.com/fr_fr/AWSEC2/latest/APIReference/API_IpPermission.html 987 // Similar to protocolIntegers() used by Network ACLs, but explicitly only 988 // supports "tcp", "udp", "icmp", and "all" 989 func sgProtocolIntegers() map[string]int { 990 var protocolIntegers = make(map[string]int) 991 protocolIntegers = map[string]int{ 992 "udp": 17, 993 "tcp": 6, 994 "icmp": 1, 995 "all": -1, 996 } 997 return protocolIntegers 998 } 999 1000 // The AWS Lambda service creates ENIs behind the scenes and keeps these around for a while 1001 // which would prevent SGs attached to such ENIs from being destroyed 1002 func deleteLingeringLambdaENIs(conn *ec2.EC2, d *schema.ResourceData) error { 1003 // Here we carefully find the offenders 1004 params := &ec2.DescribeNetworkInterfacesInput{ 1005 Filters: []*ec2.Filter{ 1006 &ec2.Filter{ 1007 Name: aws.String("group-id"), 1008 Values: []*string{aws.String(d.Id())}, 1009 }, 1010 &ec2.Filter{ 1011 Name: aws.String("description"), 1012 Values: []*string{aws.String("AWS Lambda VPC ENI: *")}, 1013 }, 1014 &ec2.Filter{ 1015 Name: aws.String("requester-id"), 1016 Values: []*string{aws.String("*:awslambda_*")}, 1017 }, 1018 }, 1019 } 1020 networkInterfaceResp, err := conn.DescribeNetworkInterfaces(params) 1021 if err != nil { 1022 return err 1023 } 1024 1025 // Then we detach and finally delete those 1026 v := networkInterfaceResp.NetworkInterfaces 1027 for _, eni := range v { 1028 if eni.Attachment != nil { 1029 detachNetworkInterfaceParams := &ec2.DetachNetworkInterfaceInput{ 1030 AttachmentId: eni.Attachment.AttachmentId, 1031 } 1032 _, detachNetworkInterfaceErr := conn.DetachNetworkInterface(detachNetworkInterfaceParams) 1033 1034 if detachNetworkInterfaceErr != nil { 1035 return detachNetworkInterfaceErr 1036 } 1037 1038 log.Printf("[DEBUG] Waiting for ENI (%s) to become detached", *eni.NetworkInterfaceId) 1039 stateConf := &resource.StateChangeConf{ 1040 Pending: []string{"true"}, 1041 Target: []string{"false"}, 1042 Refresh: networkInterfaceAttachedRefreshFunc(conn, *eni.NetworkInterfaceId), 1043 Timeout: 10 * time.Minute, 1044 } 1045 if _, err := stateConf.WaitForState(); err != nil { 1046 return fmt.Errorf( 1047 "Error waiting for ENI (%s) to become detached: %s", *eni.NetworkInterfaceId, err) 1048 } 1049 } 1050 1051 deleteNetworkInterfaceParams := &ec2.DeleteNetworkInterfaceInput{ 1052 NetworkInterfaceId: eni.NetworkInterfaceId, 1053 } 1054 _, deleteNetworkInterfaceErr := conn.DeleteNetworkInterface(deleteNetworkInterfaceParams) 1055 1056 if deleteNetworkInterfaceErr != nil { 1057 return deleteNetworkInterfaceErr 1058 } 1059 } 1060 1061 return nil 1062 } 1063 1064 func networkInterfaceAttachedRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc { 1065 return func() (interface{}, string, error) { 1066 1067 describe_network_interfaces_request := &ec2.DescribeNetworkInterfacesInput{ 1068 NetworkInterfaceIds: []*string{aws.String(id)}, 1069 } 1070 describeResp, err := conn.DescribeNetworkInterfaces(describe_network_interfaces_request) 1071 1072 if err != nil { 1073 log.Printf("[ERROR] Could not find network interface %s. %s", id, err) 1074 return nil, "", err 1075 } 1076 1077 eni := describeResp.NetworkInterfaces[0] 1078 hasAttachment := strconv.FormatBool(eni.Attachment != nil) 1079 log.Printf("[DEBUG] ENI %s has attachment state %s", id, hasAttachment) 1080 return eni, hasAttachment, nil 1081 } 1082 }