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