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