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