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  }