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