github.com/vtorhonen/terraform@v0.9.0-beta2.0.20170307220345-5d894e4ffda7/builtin/providers/aws/resource_aws_security_group.go (about)

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