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