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