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