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