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