github.com/articulate/terraform@v0.6.13-0.20160303003731-8d31c93862de/builtin/providers/aws/resource_aws_security_group.go (about)

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