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