github.com/recobe182/terraform@v0.8.5-0.20170117231232-49ab22a935b7/builtin/providers/aws/resource_aws_security_group_rule.go (about)

     1  package aws
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"log"
     7  	"sort"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/aws/aws-sdk-go/aws"
    12  	"github.com/aws/aws-sdk-go/aws/awserr"
    13  	"github.com/aws/aws-sdk-go/service/ec2"
    14  	"github.com/hashicorp/errwrap"
    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 resourceAwsSecurityGroupRule() *schema.Resource {
    21  	return &schema.Resource{
    22  		Create: resourceAwsSecurityGroupRuleCreate,
    23  		Read:   resourceAwsSecurityGroupRuleRead,
    24  		Delete: resourceAwsSecurityGroupRuleDelete,
    25  
    26  		SchemaVersion: 2,
    27  		MigrateState:  resourceAwsSecurityGroupRuleMigrateState,
    28  
    29  		Schema: map[string]*schema.Schema{
    30  			"type": {
    31  				Type:         schema.TypeString,
    32  				Required:     true,
    33  				ForceNew:     true,
    34  				Description:  "Type of rule, ingress (inbound) or egress (outbound).",
    35  				ValidateFunc: validateSecurityRuleType,
    36  			},
    37  
    38  			"from_port": {
    39  				Type:     schema.TypeInt,
    40  				Required: true,
    41  				ForceNew: true,
    42  			},
    43  
    44  			"to_port": {
    45  				Type:     schema.TypeInt,
    46  				Required: true,
    47  				ForceNew: true,
    48  			},
    49  
    50  			"protocol": {
    51  				Type:      schema.TypeString,
    52  				Required:  true,
    53  				ForceNew:  true,
    54  				StateFunc: protocolStateFunc,
    55  			},
    56  
    57  			"cidr_blocks": {
    58  				Type:     schema.TypeList,
    59  				Optional: true,
    60  				ForceNew: true,
    61  				Elem:     &schema.Schema{Type: schema.TypeString},
    62  			},
    63  
    64  			"prefix_list_ids": {
    65  				Type:     schema.TypeList,
    66  				Optional: true,
    67  				ForceNew: true,
    68  				Elem:     &schema.Schema{Type: schema.TypeString},
    69  			},
    70  
    71  			"security_group_id": {
    72  				Type:     schema.TypeString,
    73  				Required: true,
    74  				ForceNew: true,
    75  			},
    76  
    77  			"source_security_group_id": {
    78  				Type:          schema.TypeString,
    79  				Optional:      true,
    80  				ForceNew:      true,
    81  				Computed:      true,
    82  				ConflictsWith: []string{"cidr_blocks", "self"},
    83  			},
    84  
    85  			"self": {
    86  				Type:          schema.TypeBool,
    87  				Optional:      true,
    88  				Default:       false,
    89  				ForceNew:      true,
    90  				ConflictsWith: []string{"cidr_blocks"},
    91  			},
    92  		},
    93  	}
    94  }
    95  
    96  func resourceAwsSecurityGroupRuleCreate(d *schema.ResourceData, meta interface{}) error {
    97  	conn := meta.(*AWSClient).ec2conn
    98  	sg_id := d.Get("security_group_id").(string)
    99  
   100  	awsMutexKV.Lock(sg_id)
   101  	defer awsMutexKV.Unlock(sg_id)
   102  
   103  	sg, err := findResourceSecurityGroup(conn, sg_id)
   104  	if err != nil {
   105  		return err
   106  	}
   107  
   108  	perm, err := expandIPPerm(d, sg)
   109  	if err != nil {
   110  		return err
   111  	}
   112  
   113  	ruleType := d.Get("type").(string)
   114  	isVPC := sg.VpcId != nil && *sg.VpcId != ""
   115  
   116  	var autherr error
   117  	switch ruleType {
   118  	case "ingress":
   119  		log.Printf("[DEBUG] Authorizing security group %s %s rule: %s",
   120  			sg_id, "Ingress", perm)
   121  
   122  		req := &ec2.AuthorizeSecurityGroupIngressInput{
   123  			GroupId:       sg.GroupId,
   124  			IpPermissions: []*ec2.IpPermission{perm},
   125  		}
   126  
   127  		if !isVPC {
   128  			req.GroupId = nil
   129  			req.GroupName = sg.GroupName
   130  		}
   131  
   132  		_, autherr = conn.AuthorizeSecurityGroupIngress(req)
   133  
   134  	case "egress":
   135  		log.Printf("[DEBUG] Authorizing security group %s %s rule: %#v",
   136  			sg_id, "Egress", perm)
   137  
   138  		req := &ec2.AuthorizeSecurityGroupEgressInput{
   139  			GroupId:       sg.GroupId,
   140  			IpPermissions: []*ec2.IpPermission{perm},
   141  		}
   142  
   143  		_, autherr = conn.AuthorizeSecurityGroupEgress(req)
   144  
   145  	default:
   146  		return fmt.Errorf("Security Group Rule must be type 'ingress' or type 'egress'")
   147  	}
   148  
   149  	if autherr != nil {
   150  		if awsErr, ok := autherr.(awserr.Error); ok {
   151  			if awsErr.Code() == "InvalidPermission.Duplicate" {
   152  				return fmt.Errorf(`[WARN] A duplicate Security Group rule was found on (%s). This may be
   153  a side effect of a now-fixed Terraform issue causing two security groups with
   154  identical attributes but different source_security_group_ids to overwrite each
   155  other in the state. See https://github.com/hashicorp/terraform/pull/2376 for more
   156  information and instructions for recovery. Error message: %s`, sg_id, awsErr.Message())
   157  			}
   158  		}
   159  
   160  		return fmt.Errorf(
   161  			"Error authorizing security group rule type %s: %s",
   162  			ruleType, autherr)
   163  	}
   164  
   165  	id := ipPermissionIDHash(sg_id, ruleType, perm)
   166  	log.Printf("[DEBUG] Computed group rule ID %s", id)
   167  
   168  	retErr := resource.Retry(5*time.Minute, func() *resource.RetryError {
   169  		sg, err := findResourceSecurityGroup(conn, sg_id)
   170  
   171  		if err != nil {
   172  			log.Printf("[DEBUG] Error finding Security Group (%s) for Rule (%s): %s", sg_id, id, err)
   173  			return resource.NonRetryableError(err)
   174  		}
   175  
   176  		var rules []*ec2.IpPermission
   177  		switch ruleType {
   178  		case "ingress":
   179  			rules = sg.IpPermissions
   180  		default:
   181  			rules = sg.IpPermissionsEgress
   182  		}
   183  
   184  		rule := findRuleMatch(perm, rules, isVPC)
   185  
   186  		if rule == nil {
   187  			log.Printf("[DEBUG] Unable to find matching %s Security Group Rule (%s) for Group %s",
   188  				ruleType, id, sg_id)
   189  			return resource.RetryableError(fmt.Errorf("No match found"))
   190  		}
   191  
   192  		log.Printf("[DEBUG] Found rule for Security Group Rule (%s): %s", id, rule)
   193  		return nil
   194  	})
   195  
   196  	if retErr != nil {
   197  		log.Printf("[DEBUG] Error finding matching %s Security Group Rule (%s) for Group %s -- NO STATE WILL BE SAVED",
   198  			ruleType, id, sg_id)
   199  		return nil
   200  	}
   201  
   202  	d.SetId(id)
   203  	return nil
   204  }
   205  
   206  func resourceAwsSecurityGroupRuleRead(d *schema.ResourceData, meta interface{}) error {
   207  	conn := meta.(*AWSClient).ec2conn
   208  	sg_id := d.Get("security_group_id").(string)
   209  	sg, err := findResourceSecurityGroup(conn, sg_id)
   210  	if _, notFound := err.(securityGroupNotFound); notFound {
   211  		// The security group containing this rule no longer exists.
   212  		d.SetId("")
   213  		return nil
   214  	}
   215  	if err != nil {
   216  		return fmt.Errorf("Error finding security group (%s) for rule (%s): %s", sg_id, d.Id(), err)
   217  	}
   218  
   219  	isVPC := sg.VpcId != nil && *sg.VpcId != ""
   220  
   221  	var rule *ec2.IpPermission
   222  	var rules []*ec2.IpPermission
   223  	ruleType := d.Get("type").(string)
   224  	switch ruleType {
   225  	case "ingress":
   226  		rules = sg.IpPermissions
   227  	default:
   228  		rules = sg.IpPermissionsEgress
   229  	}
   230  
   231  	p, err := expandIPPerm(d, sg)
   232  	if err != nil {
   233  		return err
   234  	}
   235  
   236  	if len(rules) == 0 {
   237  		log.Printf("[WARN] No %s rules were found for Security Group (%s) looking for Security Group Rule (%s)",
   238  			ruleType, *sg.GroupName, d.Id())
   239  		d.SetId("")
   240  		return nil
   241  	}
   242  
   243  	rule = findRuleMatch(p, rules, isVPC)
   244  
   245  	if rule == nil {
   246  		log.Printf("[DEBUG] Unable to find matching %s Security Group Rule (%s) for Group %s",
   247  			ruleType, d.Id(), sg_id)
   248  		d.SetId("")
   249  		return nil
   250  	}
   251  
   252  	log.Printf("[DEBUG] Found rule for Security Group Rule (%s): %s", d.Id(), rule)
   253  
   254  	d.Set("type", ruleType)
   255  	if err := setFromIPPerm(d, sg, p); err != nil {
   256  		return errwrap.Wrapf("Error setting IP Permission for Security Group Rule: {{err}}", err)
   257  	}
   258  	return nil
   259  }
   260  
   261  func resourceAwsSecurityGroupRuleDelete(d *schema.ResourceData, meta interface{}) error {
   262  	conn := meta.(*AWSClient).ec2conn
   263  	sg_id := d.Get("security_group_id").(string)
   264  
   265  	awsMutexKV.Lock(sg_id)
   266  	defer awsMutexKV.Unlock(sg_id)
   267  
   268  	sg, err := findResourceSecurityGroup(conn, sg_id)
   269  	if err != nil {
   270  		return err
   271  	}
   272  
   273  	perm, err := expandIPPerm(d, sg)
   274  	if err != nil {
   275  		return err
   276  	}
   277  	ruleType := d.Get("type").(string)
   278  	switch ruleType {
   279  	case "ingress":
   280  		log.Printf("[DEBUG] Revoking rule (%s) from security group %s:\n%s",
   281  			"ingress", sg_id, perm)
   282  		req := &ec2.RevokeSecurityGroupIngressInput{
   283  			GroupId:       sg.GroupId,
   284  			IpPermissions: []*ec2.IpPermission{perm},
   285  		}
   286  
   287  		_, err = conn.RevokeSecurityGroupIngress(req)
   288  
   289  		if err != nil {
   290  			return fmt.Errorf(
   291  				"Error revoking security group %s rules: %s",
   292  				sg_id, err)
   293  		}
   294  	case "egress":
   295  
   296  		log.Printf("[DEBUG] Revoking security group %#v %s rule: %#v",
   297  			sg_id, "egress", perm)
   298  		req := &ec2.RevokeSecurityGroupEgressInput{
   299  			GroupId:       sg.GroupId,
   300  			IpPermissions: []*ec2.IpPermission{perm},
   301  		}
   302  
   303  		_, err = conn.RevokeSecurityGroupEgress(req)
   304  
   305  		if err != nil {
   306  			return fmt.Errorf(
   307  				"Error revoking security group %s rules: %s",
   308  				sg_id, err)
   309  		}
   310  	}
   311  
   312  	d.SetId("")
   313  
   314  	return nil
   315  }
   316  
   317  func findResourceSecurityGroup(conn *ec2.EC2, id string) (*ec2.SecurityGroup, error) {
   318  	req := &ec2.DescribeSecurityGroupsInput{
   319  		GroupIds: []*string{aws.String(id)},
   320  	}
   321  	resp, err := conn.DescribeSecurityGroups(req)
   322  	if err, ok := err.(awserr.Error); ok && err.Code() == "InvalidGroup.NotFound" {
   323  		return nil, securityGroupNotFound{id, nil}
   324  	}
   325  	if err != nil {
   326  		return nil, err
   327  	}
   328  	if resp == nil {
   329  		return nil, securityGroupNotFound{id, nil}
   330  	}
   331  	if len(resp.SecurityGroups) != 1 || resp.SecurityGroups[0] == nil {
   332  		return nil, securityGroupNotFound{id, resp.SecurityGroups}
   333  	}
   334  
   335  	return resp.SecurityGroups[0], nil
   336  }
   337  
   338  type securityGroupNotFound struct {
   339  	id             string
   340  	securityGroups []*ec2.SecurityGroup
   341  }
   342  
   343  func (err securityGroupNotFound) Error() string {
   344  	if err.securityGroups == nil {
   345  		return fmt.Sprintf("No security group with ID %q", err.id)
   346  	}
   347  	return fmt.Sprintf("Expected to find one security group with ID %q, got: %#v",
   348  		err.id, err.securityGroups)
   349  }
   350  
   351  // ByGroupPair implements sort.Interface for []*ec2.UserIDGroupPairs based on
   352  // GroupID or GroupName field (only one should be set).
   353  type ByGroupPair []*ec2.UserIdGroupPair
   354  
   355  func (b ByGroupPair) Len() int      { return len(b) }
   356  func (b ByGroupPair) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
   357  func (b ByGroupPair) Less(i, j int) bool {
   358  	if b[i].GroupId != nil && b[j].GroupId != nil {
   359  		return *b[i].GroupId < *b[j].GroupId
   360  	}
   361  	if b[i].GroupName != nil && b[j].GroupName != nil {
   362  		return *b[i].GroupName < *b[j].GroupName
   363  	}
   364  
   365  	panic("mismatched security group rules, may be a terraform bug")
   366  }
   367  
   368  func findRuleMatch(p *ec2.IpPermission, rules []*ec2.IpPermission, isVPC bool) *ec2.IpPermission {
   369  	var rule *ec2.IpPermission
   370  	for _, r := range rules {
   371  		if r.ToPort != nil && *p.ToPort != *r.ToPort {
   372  			continue
   373  		}
   374  
   375  		if r.FromPort != nil && *p.FromPort != *r.FromPort {
   376  			continue
   377  		}
   378  
   379  		if r.IpProtocol != nil && *p.IpProtocol != *r.IpProtocol {
   380  			continue
   381  		}
   382  
   383  		remaining := len(p.IpRanges)
   384  		for _, ip := range p.IpRanges {
   385  			for _, rip := range r.IpRanges {
   386  				if *ip.CidrIp == *rip.CidrIp {
   387  					remaining--
   388  				}
   389  			}
   390  		}
   391  
   392  		if remaining > 0 {
   393  			continue
   394  		}
   395  
   396  		remaining = len(p.PrefixListIds)
   397  		for _, pl := range p.PrefixListIds {
   398  			for _, rpl := range r.PrefixListIds {
   399  				if *pl.PrefixListId == *rpl.PrefixListId {
   400  					remaining--
   401  				}
   402  			}
   403  		}
   404  
   405  		if remaining > 0 {
   406  			continue
   407  		}
   408  
   409  		remaining = len(p.UserIdGroupPairs)
   410  		for _, ip := range p.UserIdGroupPairs {
   411  			for _, rip := range r.UserIdGroupPairs {
   412  				if isVPC {
   413  					if *ip.GroupId == *rip.GroupId {
   414  						remaining--
   415  					}
   416  				} else {
   417  					if *ip.GroupName == *rip.GroupName {
   418  						remaining--
   419  					}
   420  				}
   421  			}
   422  		}
   423  
   424  		if remaining > 0 {
   425  			continue
   426  		}
   427  
   428  		rule = r
   429  	}
   430  	return rule
   431  }
   432  
   433  func ipPermissionIDHash(sg_id, ruleType string, ip *ec2.IpPermission) string {
   434  	var buf bytes.Buffer
   435  	buf.WriteString(fmt.Sprintf("%s-", sg_id))
   436  	if ip.FromPort != nil && *ip.FromPort > 0 {
   437  		buf.WriteString(fmt.Sprintf("%d-", *ip.FromPort))
   438  	}
   439  	if ip.ToPort != nil && *ip.ToPort > 0 {
   440  		buf.WriteString(fmt.Sprintf("%d-", *ip.ToPort))
   441  	}
   442  	buf.WriteString(fmt.Sprintf("%s-", *ip.IpProtocol))
   443  	buf.WriteString(fmt.Sprintf("%s-", ruleType))
   444  
   445  	// We need to make sure to sort the strings below so that we always
   446  	// generate the same hash code no matter what is in the set.
   447  	if len(ip.IpRanges) > 0 {
   448  		s := make([]string, len(ip.IpRanges))
   449  		for i, r := range ip.IpRanges {
   450  			s[i] = *r.CidrIp
   451  		}
   452  		sort.Strings(s)
   453  
   454  		for _, v := range s {
   455  			buf.WriteString(fmt.Sprintf("%s-", v))
   456  		}
   457  	}
   458  
   459  	if len(ip.PrefixListIds) > 0 {
   460  		s := make([]string, len(ip.PrefixListIds))
   461  		for i, pl := range ip.PrefixListIds {
   462  			s[i] = *pl.PrefixListId
   463  		}
   464  		sort.Strings(s)
   465  
   466  		for _, v := range s {
   467  			buf.WriteString(fmt.Sprintf("%s-", v))
   468  		}
   469  	}
   470  
   471  	if len(ip.UserIdGroupPairs) > 0 {
   472  		sort.Sort(ByGroupPair(ip.UserIdGroupPairs))
   473  		for _, pair := range ip.UserIdGroupPairs {
   474  			if pair.GroupId != nil {
   475  				buf.WriteString(fmt.Sprintf("%s-", *pair.GroupId))
   476  			} else {
   477  				buf.WriteString("-")
   478  			}
   479  			if pair.GroupName != nil {
   480  				buf.WriteString(fmt.Sprintf("%s-", *pair.GroupName))
   481  			} else {
   482  				buf.WriteString("-")
   483  			}
   484  		}
   485  	}
   486  
   487  	return fmt.Sprintf("sgrule-%d", hashcode.String(buf.String()))
   488  }
   489  
   490  func expandIPPerm(d *schema.ResourceData, sg *ec2.SecurityGroup) (*ec2.IpPermission, error) {
   491  	var perm ec2.IpPermission
   492  
   493  	perm.FromPort = aws.Int64(int64(d.Get("from_port").(int)))
   494  	perm.ToPort = aws.Int64(int64(d.Get("to_port").(int)))
   495  	protocol := protocolForValue(d.Get("protocol").(string))
   496  	perm.IpProtocol = aws.String(protocol)
   497  
   498  	// build a group map that behaves like a set
   499  	groups := make(map[string]bool)
   500  	if raw, ok := d.GetOk("source_security_group_id"); ok {
   501  		groups[raw.(string)] = true
   502  	}
   503  
   504  	if v, ok := d.GetOk("self"); ok && v.(bool) {
   505  		if sg.VpcId != nil && *sg.VpcId != "" {
   506  			groups[*sg.GroupId] = true
   507  		} else {
   508  			groups[*sg.GroupName] = true
   509  		}
   510  	}
   511  
   512  	if len(groups) > 0 {
   513  		perm.UserIdGroupPairs = make([]*ec2.UserIdGroupPair, len(groups))
   514  		// build string list of group name/ids
   515  		var gl []string
   516  		for k, _ := range groups {
   517  			gl = append(gl, k)
   518  		}
   519  
   520  		for i, name := range gl {
   521  			ownerId, id := "", name
   522  			if items := strings.Split(id, "/"); len(items) > 1 {
   523  				ownerId, id = items[0], items[1]
   524  			}
   525  
   526  			perm.UserIdGroupPairs[i] = &ec2.UserIdGroupPair{
   527  				GroupId: aws.String(id),
   528  				UserId:  aws.String(ownerId),
   529  			}
   530  
   531  			if sg.VpcId == nil || *sg.VpcId == "" {
   532  				perm.UserIdGroupPairs[i].GroupId = nil
   533  				perm.UserIdGroupPairs[i].GroupName = aws.String(id)
   534  				perm.UserIdGroupPairs[i].UserId = nil
   535  			}
   536  		}
   537  	}
   538  
   539  	if raw, ok := d.GetOk("cidr_blocks"); ok {
   540  		list := raw.([]interface{})
   541  		perm.IpRanges = make([]*ec2.IpRange, len(list))
   542  		for i, v := range list {
   543  			cidrIP, ok := v.(string)
   544  			if !ok {
   545  				return nil, fmt.Errorf("empty element found in cidr_blocks - consider using the compact function")
   546  			}
   547  			perm.IpRanges[i] = &ec2.IpRange{CidrIp: aws.String(cidrIP)}
   548  		}
   549  	}
   550  
   551  	if raw, ok := d.GetOk("prefix_list_ids"); ok {
   552  		list := raw.([]interface{})
   553  		perm.PrefixListIds = make([]*ec2.PrefixListId, len(list))
   554  		for i, v := range list {
   555  			prefixListID, ok := v.(string)
   556  			if !ok {
   557  				return nil, fmt.Errorf("empty element found in prefix_list_ids - consider using the compact function")
   558  			}
   559  			perm.PrefixListIds[i] = &ec2.PrefixListId{PrefixListId: aws.String(prefixListID)}
   560  		}
   561  	}
   562  
   563  	return &perm, nil
   564  }
   565  
   566  func setFromIPPerm(d *schema.ResourceData, sg *ec2.SecurityGroup, rule *ec2.IpPermission) error {
   567  	isVPC := sg.VpcId != nil && *sg.VpcId != ""
   568  
   569  	d.Set("from_port", rule.FromPort)
   570  	d.Set("to_port", rule.ToPort)
   571  	d.Set("protocol", rule.IpProtocol)
   572  
   573  	var cb []string
   574  	for _, c := range rule.IpRanges {
   575  		cb = append(cb, *c.CidrIp)
   576  	}
   577  
   578  	d.Set("cidr_blocks", cb)
   579  
   580  	var pl []string
   581  	for _, p := range rule.PrefixListIds {
   582  		pl = append(pl, *p.PrefixListId)
   583  	}
   584  	d.Set("prefix_list_ids", pl)
   585  
   586  	if len(rule.UserIdGroupPairs) > 0 {
   587  		s := rule.UserIdGroupPairs[0]
   588  
   589  		if isVPC {
   590  			d.Set("source_security_group_id", *s.GroupId)
   591  		} else {
   592  			d.Set("source_security_group_id", *s.GroupName)
   593  		}
   594  	}
   595  
   596  	return nil
   597  }