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