github.com/koding/terraform@v0.6.4-0.20170608090606-5d7e0339779d/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 default: 432 // Any other error, we want to quit the retry loop immediately 433 return resource.NonRetryableError(err) 434 } 435 } 436 437 return nil 438 }) 439 } 440 441 func resourceAwsSecurityGroupRuleHash(v interface{}) int { 442 var buf bytes.Buffer 443 m := v.(map[string]interface{}) 444 buf.WriteString(fmt.Sprintf("%d-", m["from_port"].(int))) 445 buf.WriteString(fmt.Sprintf("%d-", m["to_port"].(int))) 446 p := protocolForValue(m["protocol"].(string)) 447 buf.WriteString(fmt.Sprintf("%s-", p)) 448 buf.WriteString(fmt.Sprintf("%t-", m["self"].(bool))) 449 450 // We need to make sure to sort the strings below so that we always 451 // generate the same hash code no matter what is in the set. 452 if v, ok := m["cidr_blocks"]; ok { 453 vs := v.([]interface{}) 454 s := make([]string, len(vs)) 455 for i, raw := range vs { 456 s[i] = raw.(string) 457 } 458 sort.Strings(s) 459 460 for _, v := range s { 461 buf.WriteString(fmt.Sprintf("%s-", v)) 462 } 463 } 464 if v, ok := m["ipv6_cidr_blocks"]; ok { 465 vs := v.([]interface{}) 466 s := make([]string, len(vs)) 467 for i, raw := range vs { 468 s[i] = raw.(string) 469 } 470 sort.Strings(s) 471 472 for _, v := range s { 473 buf.WriteString(fmt.Sprintf("%s-", v)) 474 } 475 } 476 if v, ok := m["prefix_list_ids"]; ok { 477 vs := v.([]interface{}) 478 s := make([]string, len(vs)) 479 for i, raw := range vs { 480 s[i] = raw.(string) 481 } 482 sort.Strings(s) 483 484 for _, v := range s { 485 buf.WriteString(fmt.Sprintf("%s-", v)) 486 } 487 } 488 if v, ok := m["security_groups"]; ok { 489 vs := v.(*schema.Set).List() 490 s := make([]string, len(vs)) 491 for i, raw := range vs { 492 s[i] = raw.(string) 493 } 494 sort.Strings(s) 495 496 for _, v := range s { 497 buf.WriteString(fmt.Sprintf("%s-", v)) 498 } 499 } 500 501 return hashcode.String(buf.String()) 502 } 503 504 func resourceAwsSecurityGroupIPPermGather(groupId string, permissions []*ec2.IpPermission, ownerId *string) []map[string]interface{} { 505 ruleMap := make(map[string]map[string]interface{}) 506 for _, perm := range permissions { 507 var fromPort, toPort int64 508 if v := perm.FromPort; v != nil { 509 fromPort = *v 510 } 511 if v := perm.ToPort; v != nil { 512 toPort = *v 513 } 514 515 k := fmt.Sprintf("%s-%d-%d", *perm.IpProtocol, fromPort, toPort) 516 m, ok := ruleMap[k] 517 if !ok { 518 m = make(map[string]interface{}) 519 ruleMap[k] = m 520 } 521 522 m["from_port"] = fromPort 523 m["to_port"] = toPort 524 m["protocol"] = *perm.IpProtocol 525 526 if len(perm.IpRanges) > 0 { 527 raw, ok := m["cidr_blocks"] 528 if !ok { 529 raw = make([]string, 0, len(perm.IpRanges)) 530 } 531 list := raw.([]string) 532 533 for _, ip := range perm.IpRanges { 534 list = append(list, *ip.CidrIp) 535 } 536 537 m["cidr_blocks"] = list 538 } 539 540 if len(perm.Ipv6Ranges) > 0 { 541 raw, ok := m["ipv6_cidr_blocks"] 542 if !ok { 543 raw = make([]string, 0, len(perm.Ipv6Ranges)) 544 } 545 list := raw.([]string) 546 547 for _, ip := range perm.Ipv6Ranges { 548 list = append(list, *ip.CidrIpv6) 549 } 550 551 m["ipv6_cidr_blocks"] = list 552 } 553 554 if len(perm.PrefixListIds) > 0 { 555 raw, ok := m["prefix_list_ids"] 556 if !ok { 557 raw = make([]string, 0, len(perm.PrefixListIds)) 558 } 559 list := raw.([]string) 560 561 for _, pl := range perm.PrefixListIds { 562 list = append(list, *pl.PrefixListId) 563 } 564 565 m["prefix_list_ids"] = list 566 } 567 568 groups := flattenSecurityGroups(perm.UserIdGroupPairs, ownerId) 569 for i, g := range groups { 570 if *g.GroupId == groupId { 571 groups[i], groups = groups[len(groups)-1], groups[:len(groups)-1] 572 m["self"] = true 573 } 574 } 575 576 if len(groups) > 0 { 577 raw, ok := m["security_groups"] 578 if !ok { 579 raw = schema.NewSet(schema.HashString, nil) 580 } 581 list := raw.(*schema.Set) 582 583 for _, g := range groups { 584 if g.GroupName != nil { 585 list.Add(*g.GroupName) 586 } else { 587 list.Add(*g.GroupId) 588 } 589 } 590 591 m["security_groups"] = list 592 } 593 } 594 rules := make([]map[string]interface{}, 0, len(ruleMap)) 595 for _, m := range ruleMap { 596 rules = append(rules, m) 597 } 598 599 return rules 600 } 601 602 func resourceAwsSecurityGroupUpdateRules( 603 d *schema.ResourceData, ruleset string, 604 meta interface{}, group *ec2.SecurityGroup) error { 605 606 if d.HasChange(ruleset) { 607 o, n := d.GetChange(ruleset) 608 if o == nil { 609 o = new(schema.Set) 610 } 611 if n == nil { 612 n = new(schema.Set) 613 } 614 615 os := o.(*schema.Set) 616 ns := n.(*schema.Set) 617 618 remove, err := expandIPPerms(group, os.Difference(ns).List()) 619 if err != nil { 620 return err 621 } 622 add, err := expandIPPerms(group, ns.Difference(os).List()) 623 if err != nil { 624 return err 625 } 626 627 // TODO: We need to handle partial state better in the in-between 628 // in this update. 629 630 // TODO: It'd be nicer to authorize before removing, but then we have 631 // to deal with complicated unrolling to get individual CIDR blocks 632 // to avoid authorizing already authorized sources. Removing before 633 // adding is easier here, and Terraform should be fast enough to 634 // not have service issues. 635 636 if len(remove) > 0 || len(add) > 0 { 637 conn := meta.(*AWSClient).ec2conn 638 639 var err error 640 if len(remove) > 0 { 641 log.Printf("[DEBUG] Revoking security group %#v %s rule: %#v", 642 group, ruleset, remove) 643 644 if ruleset == "egress" { 645 req := &ec2.RevokeSecurityGroupEgressInput{ 646 GroupId: group.GroupId, 647 IpPermissions: remove, 648 } 649 _, err = conn.RevokeSecurityGroupEgress(req) 650 } else { 651 req := &ec2.RevokeSecurityGroupIngressInput{ 652 GroupId: group.GroupId, 653 IpPermissions: remove, 654 } 655 if group.VpcId == nil || *group.VpcId == "" { 656 req.GroupId = nil 657 req.GroupName = group.GroupName 658 } 659 _, err = conn.RevokeSecurityGroupIngress(req) 660 } 661 662 if err != nil { 663 return fmt.Errorf( 664 "Error revoking security group %s rules: %s", 665 ruleset, err) 666 } 667 } 668 669 if len(add) > 0 { 670 log.Printf("[DEBUG] Authorizing security group %#v %s rule: %#v", 671 group, ruleset, add) 672 // Authorize the new rules 673 if ruleset == "egress" { 674 req := &ec2.AuthorizeSecurityGroupEgressInput{ 675 GroupId: group.GroupId, 676 IpPermissions: add, 677 } 678 _, err = conn.AuthorizeSecurityGroupEgress(req) 679 } else { 680 req := &ec2.AuthorizeSecurityGroupIngressInput{ 681 GroupId: group.GroupId, 682 IpPermissions: add, 683 } 684 if group.VpcId == nil || *group.VpcId == "" { 685 req.GroupId = nil 686 req.GroupName = group.GroupName 687 } 688 689 _, err = conn.AuthorizeSecurityGroupIngress(req) 690 } 691 692 if err != nil { 693 return fmt.Errorf( 694 "Error authorizing security group %s rules: %s", 695 ruleset, err) 696 } 697 } 698 } 699 } 700 return nil 701 } 702 703 // SGStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch 704 // a security group. 705 func SGStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc { 706 return func() (interface{}, string, error) { 707 req := &ec2.DescribeSecurityGroupsInput{ 708 GroupIds: []*string{aws.String(id)}, 709 } 710 resp, err := conn.DescribeSecurityGroups(req) 711 if err != nil { 712 if ec2err, ok := err.(awserr.Error); ok { 713 if ec2err.Code() == "InvalidSecurityGroupID.NotFound" || 714 ec2err.Code() == "InvalidGroup.NotFound" { 715 resp = nil 716 err = nil 717 } 718 } 719 720 if err != nil { 721 log.Printf("Error on SGStateRefresh: %s", err) 722 return nil, "", err 723 } 724 } 725 726 if resp == nil { 727 return nil, "", nil 728 } 729 730 group := resp.SecurityGroups[0] 731 return group, "exists", nil 732 } 733 } 734 735 // matchRules receives the group id, type of rules, and the local / remote maps 736 // of rules. We iterate through the local set of rules trying to find a matching 737 // remote rule, which may be structured differently because of how AWS 738 // aggregates the rules under the to, from, and type. 739 // 740 // 741 // Matching rules are written to state, with their elements removed from the 742 // remote set 743 // 744 // If no match is found, we'll write the remote rule to state and let the graph 745 // sort things out 746 func matchRules(rType string, local []interface{}, remote []map[string]interface{}) []map[string]interface{} { 747 // For each local ip or security_group, we need to match against the remote 748 // ruleSet until all ips or security_groups are found 749 750 // saves represents the rules that have been identified to be saved to state, 751 // in the appropriate d.Set("{ingress,egress}") call. 752 var saves []map[string]interface{} 753 for _, raw := range local { 754 l := raw.(map[string]interface{}) 755 756 var selfVal bool 757 if v, ok := l["self"]; ok { 758 selfVal = v.(bool) 759 } 760 761 // matching against self is required to detect rules that only include self 762 // as the rule. resourceAwsSecurityGroupIPPermGather parses the group out 763 // and replaces it with self if it's ID is found 764 localHash := idHash(rType, l["protocol"].(string), int64(l["to_port"].(int)), int64(l["from_port"].(int)), selfVal) 765 766 // loop remote rules, looking for a matching hash 767 for _, r := range remote { 768 var remoteSelfVal bool 769 if v, ok := r["self"]; ok { 770 remoteSelfVal = v.(bool) 771 } 772 773 // hash this remote rule and compare it for a match consideration with the 774 // local rule we're examining 775 rHash := idHash(rType, r["protocol"].(string), r["to_port"].(int64), r["from_port"].(int64), remoteSelfVal) 776 if rHash == localHash { 777 var numExpectedCidrs, numExpectedIpv6Cidrs, numExpectedPrefixLists, numExpectedSGs, numRemoteCidrs, numRemoteIpv6Cidrs, numRemotePrefixLists, numRemoteSGs int 778 var matchingCidrs []string 779 var matchingIpv6Cidrs []string 780 var matchingSGs []string 781 var matchingPrefixLists []string 782 783 // grab the local/remote cidr and sg groups, capturing the expected and 784 // actual counts 785 lcRaw, ok := l["cidr_blocks"] 786 if ok { 787 numExpectedCidrs = len(l["cidr_blocks"].([]interface{})) 788 } 789 liRaw, ok := l["ipv6_cidr_blocks"] 790 if ok { 791 numExpectedIpv6Cidrs = len(l["ipv6_cidr_blocks"].([]interface{})) 792 } 793 lpRaw, ok := l["prefix_list_ids"] 794 if ok { 795 numExpectedPrefixLists = len(l["prefix_list_ids"].([]interface{})) 796 } 797 lsRaw, ok := l["security_groups"] 798 if ok { 799 numExpectedSGs = len(l["security_groups"].(*schema.Set).List()) 800 } 801 802 rcRaw, ok := r["cidr_blocks"] 803 if ok { 804 numRemoteCidrs = len(r["cidr_blocks"].([]string)) 805 } 806 riRaw, ok := r["ipv6_cidr_blocks"] 807 if ok { 808 numRemoteIpv6Cidrs = len(r["ipv6_cidr_blocks"].([]string)) 809 } 810 rpRaw, ok := r["prefix_list_ids"] 811 if ok { 812 numRemotePrefixLists = len(r["prefix_list_ids"].([]string)) 813 } 814 815 rsRaw, ok := r["security_groups"] 816 if ok { 817 numRemoteSGs = len(r["security_groups"].(*schema.Set).List()) 818 } 819 820 // check some early failures 821 if numExpectedCidrs > numRemoteCidrs { 822 log.Printf("[DEBUG] Local rule has more CIDR blocks, continuing (%d/%d)", numExpectedCidrs, numRemoteCidrs) 823 continue 824 } 825 if numExpectedIpv6Cidrs > numRemoteIpv6Cidrs { 826 log.Printf("[DEBUG] Local rule has more IPV6 CIDR blocks, continuing (%d/%d)", numExpectedIpv6Cidrs, numRemoteIpv6Cidrs) 827 continue 828 } 829 if numExpectedPrefixLists > numRemotePrefixLists { 830 log.Printf("[DEBUG] Local rule has more prefix lists, continuing (%d/%d)", numExpectedPrefixLists, numRemotePrefixLists) 831 continue 832 } 833 if numExpectedSGs > numRemoteSGs { 834 log.Printf("[DEBUG] Local rule has more Security Groups, continuing (%d/%d)", numExpectedSGs, numRemoteSGs) 835 continue 836 } 837 838 // match CIDRs by converting both to sets, and using Set methods 839 var localCidrs []interface{} 840 if lcRaw != nil { 841 localCidrs = lcRaw.([]interface{}) 842 } 843 localCidrSet := schema.NewSet(schema.HashString, localCidrs) 844 845 // remote cidrs are presented as a slice of strings, so we need to 846 // reformat them into a slice of interfaces to be used in creating the 847 // remote cidr set 848 var remoteCidrs []string 849 if rcRaw != nil { 850 remoteCidrs = rcRaw.([]string) 851 } 852 // convert remote cidrs to a set, for easy comparisons 853 var list []interface{} 854 for _, s := range remoteCidrs { 855 list = append(list, s) 856 } 857 remoteCidrSet := schema.NewSet(schema.HashString, list) 858 859 // Build up a list of local cidrs that are found in the remote set 860 for _, s := range localCidrSet.List() { 861 if remoteCidrSet.Contains(s) { 862 matchingCidrs = append(matchingCidrs, s.(string)) 863 } 864 } 865 866 //IPV6 CIDRs 867 var localIpv6Cidrs []interface{} 868 if liRaw != nil { 869 localIpv6Cidrs = liRaw.([]interface{}) 870 } 871 localIpv6CidrSet := schema.NewSet(schema.HashString, localIpv6Cidrs) 872 873 var remoteIpv6Cidrs []string 874 if riRaw != nil { 875 remoteIpv6Cidrs = riRaw.([]string) 876 } 877 var listIpv6 []interface{} 878 for _, s := range remoteIpv6Cidrs { 879 listIpv6 = append(listIpv6, s) 880 } 881 remoteIpv6CidrSet := schema.NewSet(schema.HashString, listIpv6) 882 883 for _, s := range localIpv6CidrSet.List() { 884 if remoteIpv6CidrSet.Contains(s) { 885 matchingIpv6Cidrs = append(matchingIpv6Cidrs, s.(string)) 886 } 887 } 888 889 // match prefix lists by converting both to sets, and using Set methods 890 var localPrefixLists []interface{} 891 if lpRaw != nil { 892 localPrefixLists = lpRaw.([]interface{}) 893 } 894 localPrefixListsSet := schema.NewSet(schema.HashString, localPrefixLists) 895 896 // remote prefix lists are presented as a slice of strings, so we need to 897 // reformat them into a slice of interfaces to be used in creating the 898 // remote prefix list set 899 var remotePrefixLists []string 900 if rpRaw != nil { 901 remotePrefixLists = rpRaw.([]string) 902 } 903 // convert remote prefix lists to a set, for easy comparison 904 list = nil 905 for _, s := range remotePrefixLists { 906 list = append(list, s) 907 } 908 remotePrefixListsSet := schema.NewSet(schema.HashString, list) 909 910 // Build up a list of local prefix lists that are found in the remote set 911 for _, s := range localPrefixListsSet.List() { 912 if remotePrefixListsSet.Contains(s) { 913 matchingPrefixLists = append(matchingPrefixLists, s.(string)) 914 } 915 } 916 917 // match SGs. Both local and remote are already sets 918 var localSGSet *schema.Set 919 if lsRaw == nil { 920 localSGSet = schema.NewSet(schema.HashString, nil) 921 } else { 922 localSGSet = lsRaw.(*schema.Set) 923 } 924 925 var remoteSGSet *schema.Set 926 if rsRaw == nil { 927 remoteSGSet = schema.NewSet(schema.HashString, nil) 928 } else { 929 remoteSGSet = rsRaw.(*schema.Set) 930 } 931 932 // Build up a list of local security groups that are found in the remote set 933 for _, s := range localSGSet.List() { 934 if remoteSGSet.Contains(s) { 935 matchingSGs = append(matchingSGs, s.(string)) 936 } 937 } 938 939 // compare equalities for matches. 940 // If we found the number of cidrs and number of sgs, we declare a 941 // match, and then remove those elements from the remote rule, so that 942 // this remote rule can still be considered by other local rules 943 if numExpectedCidrs == len(matchingCidrs) { 944 if numExpectedIpv6Cidrs == len(matchingIpv6Cidrs) { 945 if numExpectedPrefixLists == len(matchingPrefixLists) { 946 if numExpectedSGs == len(matchingSGs) { 947 // confirm that self references match 948 var lSelf bool 949 var rSelf bool 950 if _, ok := l["self"]; ok { 951 lSelf = l["self"].(bool) 952 } 953 if _, ok := r["self"]; ok { 954 rSelf = r["self"].(bool) 955 } 956 if rSelf == lSelf { 957 delete(r, "self") 958 // pop local cidrs from remote 959 diffCidr := remoteCidrSet.Difference(localCidrSet) 960 var newCidr []string 961 for _, cRaw := range diffCidr.List() { 962 newCidr = append(newCidr, cRaw.(string)) 963 } 964 965 // reassigning 966 if len(newCidr) > 0 { 967 r["cidr_blocks"] = newCidr 968 } else { 969 delete(r, "cidr_blocks") 970 } 971 972 //// IPV6 973 //// Comparison 974 diffIpv6Cidr := remoteIpv6CidrSet.Difference(localIpv6CidrSet) 975 var newIpv6Cidr []string 976 for _, cRaw := range diffIpv6Cidr.List() { 977 newIpv6Cidr = append(newIpv6Cidr, cRaw.(string)) 978 } 979 980 // reassigning 981 if len(newIpv6Cidr) > 0 { 982 r["ipv6_cidr_blocks"] = newIpv6Cidr 983 } else { 984 delete(r, "ipv6_cidr_blocks") 985 } 986 987 // pop local prefix lists from remote 988 diffPrefixLists := remotePrefixListsSet.Difference(localPrefixListsSet) 989 var newPrefixLists []string 990 for _, pRaw := range diffPrefixLists.List() { 991 newPrefixLists = append(newPrefixLists, pRaw.(string)) 992 } 993 994 // reassigning 995 if len(newPrefixLists) > 0 { 996 r["prefix_list_ids"] = newPrefixLists 997 } else { 998 delete(r, "prefix_list_ids") 999 } 1000 1001 // pop local sgs from remote 1002 diffSGs := remoteSGSet.Difference(localSGSet) 1003 if len(diffSGs.List()) > 0 { 1004 r["security_groups"] = diffSGs 1005 } else { 1006 delete(r, "security_groups") 1007 } 1008 1009 saves = append(saves, l) 1010 } 1011 } 1012 } 1013 1014 } 1015 } 1016 } 1017 } 1018 } 1019 // Here we catch any remote rules that have not been stripped of all self, 1020 // cidrs, and security groups. We'll add remote rules here that have not been 1021 // matched locally, and let the graph sort things out. This will happen when 1022 // rules are added externally to Terraform 1023 for _, r := range remote { 1024 var lenCidr, lenIpv6Cidr, lenPrefixLists, lenSGs int 1025 if rCidrs, ok := r["cidr_blocks"]; ok { 1026 lenCidr = len(rCidrs.([]string)) 1027 } 1028 if rIpv6Cidrs, ok := r["ipv6_cidr_blocks"]; ok { 1029 lenIpv6Cidr = len(rIpv6Cidrs.([]string)) 1030 } 1031 if rPrefixLists, ok := r["prefix_list_ids"]; ok { 1032 lenPrefixLists = len(rPrefixLists.([]string)) 1033 } 1034 if rawSGs, ok := r["security_groups"]; ok { 1035 lenSGs = len(rawSGs.(*schema.Set).List()) 1036 } 1037 1038 if _, ok := r["self"]; ok { 1039 if r["self"].(bool) == true { 1040 lenSGs++ 1041 } 1042 } 1043 1044 if lenSGs+lenCidr+lenIpv6Cidr+lenPrefixLists > 0 { 1045 log.Printf("[DEBUG] Found a remote Rule that wasn't empty: (%#v)", r) 1046 saves = append(saves, r) 1047 } 1048 } 1049 1050 return saves 1051 } 1052 1053 // Creates a unique hash for the type, ports, and protocol, used as a key in 1054 // maps 1055 func idHash(rType, protocol string, toPort, fromPort int64, self bool) string { 1056 var buf bytes.Buffer 1057 buf.WriteString(fmt.Sprintf("%s-", rType)) 1058 buf.WriteString(fmt.Sprintf("%d-", toPort)) 1059 buf.WriteString(fmt.Sprintf("%d-", fromPort)) 1060 buf.WriteString(fmt.Sprintf("%s-", strings.ToLower(protocol))) 1061 buf.WriteString(fmt.Sprintf("%t-", self)) 1062 1063 return fmt.Sprintf("rule-%d", hashcode.String(buf.String())) 1064 } 1065 1066 // protocolStateFunc ensures we only store a string in any protocol field 1067 func protocolStateFunc(v interface{}) string { 1068 switch v.(type) { 1069 case string: 1070 p := protocolForValue(v.(string)) 1071 return p 1072 default: 1073 log.Printf("[WARN] Non String value given for Protocol: %#v", v) 1074 return "" 1075 } 1076 } 1077 1078 // protocolForValue converts a valid Internet Protocol number into it's name 1079 // representation. If a name is given, it validates that it's a proper protocol 1080 // name. Names/numbers are as defined at 1081 // https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml 1082 func protocolForValue(v string) string { 1083 // special case -1 1084 protocol := strings.ToLower(v) 1085 if protocol == "-1" || protocol == "all" { 1086 return "-1" 1087 } 1088 // if it's a name like tcp, return that 1089 if _, ok := sgProtocolIntegers()[protocol]; ok { 1090 return protocol 1091 } 1092 // convert to int, look for that value 1093 p, err := strconv.Atoi(protocol) 1094 if err != nil { 1095 // we were unable to convert to int, suggesting a string name, but it wasn't 1096 // found above 1097 log.Printf("[WARN] Unable to determine valid protocol: %s", err) 1098 return protocol 1099 } 1100 1101 for k, v := range sgProtocolIntegers() { 1102 if p == v { 1103 // guard against protocolIntegers sometime in the future not having lower 1104 // case ids in the map 1105 return strings.ToLower(k) 1106 } 1107 } 1108 1109 // fall through 1110 log.Printf("[WARN] Unable to determine valid protocol: no matching protocols found") 1111 return protocol 1112 } 1113 1114 // a map of protocol names and their codes, defined at 1115 // https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml, 1116 // documented to be supported by AWS Security Groups 1117 // http://docs.aws.amazon.com/fr_fr/AWSEC2/latest/APIReference/API_IpPermission.html 1118 // Similar to protocolIntegers() used by Network ACLs, but explicitly only 1119 // supports "tcp", "udp", "icmp", and "all" 1120 func sgProtocolIntegers() map[string]int { 1121 var protocolIntegers = make(map[string]int) 1122 protocolIntegers = map[string]int{ 1123 "udp": 17, 1124 "tcp": 6, 1125 "icmp": 1, 1126 "all": -1, 1127 } 1128 return protocolIntegers 1129 } 1130 1131 // The AWS Lambda service creates ENIs behind the scenes and keeps these around for a while 1132 // which would prevent SGs attached to such ENIs from being destroyed 1133 func deleteLingeringLambdaENIs(conn *ec2.EC2, d *schema.ResourceData) error { 1134 // Here we carefully find the offenders 1135 params := &ec2.DescribeNetworkInterfacesInput{ 1136 Filters: []*ec2.Filter{ 1137 { 1138 Name: aws.String("group-id"), 1139 Values: []*string{aws.String(d.Id())}, 1140 }, 1141 { 1142 Name: aws.String("description"), 1143 Values: []*string{aws.String("AWS Lambda VPC ENI: *")}, 1144 }, 1145 }, 1146 } 1147 networkInterfaceResp, err := conn.DescribeNetworkInterfaces(params) 1148 if err != nil { 1149 return err 1150 } 1151 1152 // Then we detach and finally delete those 1153 v := networkInterfaceResp.NetworkInterfaces 1154 for _, eni := range v { 1155 if eni.Attachment != nil { 1156 detachNetworkInterfaceParams := &ec2.DetachNetworkInterfaceInput{ 1157 AttachmentId: eni.Attachment.AttachmentId, 1158 } 1159 _, detachNetworkInterfaceErr := conn.DetachNetworkInterface(detachNetworkInterfaceParams) 1160 1161 if detachNetworkInterfaceErr != nil { 1162 return detachNetworkInterfaceErr 1163 } 1164 1165 log.Printf("[DEBUG] Waiting for ENI (%s) to become detached", *eni.NetworkInterfaceId) 1166 stateConf := &resource.StateChangeConf{ 1167 Pending: []string{"true"}, 1168 Target: []string{"false"}, 1169 Refresh: networkInterfaceAttachedRefreshFunc(conn, *eni.NetworkInterfaceId), 1170 Timeout: 10 * time.Minute, 1171 } 1172 if _, err := stateConf.WaitForState(); err != nil { 1173 return fmt.Errorf( 1174 "Error waiting for ENI (%s) to become detached: %s", *eni.NetworkInterfaceId, err) 1175 } 1176 } 1177 1178 deleteNetworkInterfaceParams := &ec2.DeleteNetworkInterfaceInput{ 1179 NetworkInterfaceId: eni.NetworkInterfaceId, 1180 } 1181 _, deleteNetworkInterfaceErr := conn.DeleteNetworkInterface(deleteNetworkInterfaceParams) 1182 1183 if deleteNetworkInterfaceErr != nil { 1184 return deleteNetworkInterfaceErr 1185 } 1186 } 1187 1188 return nil 1189 } 1190 1191 func networkInterfaceAttachedRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc { 1192 return func() (interface{}, string, error) { 1193 1194 describe_network_interfaces_request := &ec2.DescribeNetworkInterfacesInput{ 1195 NetworkInterfaceIds: []*string{aws.String(id)}, 1196 } 1197 describeResp, err := conn.DescribeNetworkInterfaces(describe_network_interfaces_request) 1198 1199 if err != nil { 1200 log.Printf("[ERROR] Could not find network interface %s. %s", id, err) 1201 return nil, "", err 1202 } 1203 1204 eni := describeResp.NetworkInterfaces[0] 1205 hasAttachment := strconv.FormatBool(eni.Attachment != nil) 1206 log.Printf("[DEBUG] ENI %s has attachment state %s", id, hasAttachment) 1207 return eni, hasAttachment, nil 1208 } 1209 }