github.com/bendemaree/terraform@v0.5.4-0.20150613200311-f50d97d6eee6/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/aws/awsutil"
    13  	"github.com/aws/aws-sdk-go/service/ec2"
    14  	"github.com/hashicorp/terraform/helper/hashcode"
    15  	"github.com/hashicorp/terraform/helper/schema"
    16  )
    17  
    18  func resourceAwsSecurityGroupRule() *schema.Resource {
    19  	return &schema.Resource{
    20  		Create: resourceAwsSecurityGroupRuleCreate,
    21  		Read:   resourceAwsSecurityGroupRuleRead,
    22  		Delete: resourceAwsSecurityGroupRuleDelete,
    23  
    24  		Schema: map[string]*schema.Schema{
    25  			"type": &schema.Schema{
    26  				Type:        schema.TypeString,
    27  				Required:    true,
    28  				ForceNew:    true,
    29  				Description: "Type of rule, ingress (inbound) or egress (outbound).",
    30  			},
    31  
    32  			"from_port": &schema.Schema{
    33  				Type:     schema.TypeInt,
    34  				Required: true,
    35  				ForceNew: true,
    36  			},
    37  
    38  			"to_port": &schema.Schema{
    39  				Type:     schema.TypeInt,
    40  				Required: true,
    41  				ForceNew: true,
    42  			},
    43  
    44  			"protocol": &schema.Schema{
    45  				Type:     schema.TypeString,
    46  				Required: true,
    47  				ForceNew: true,
    48  			},
    49  
    50  			"cidr_blocks": &schema.Schema{
    51  				Type:     schema.TypeList,
    52  				Optional: true,
    53  				ForceNew: true,
    54  				Elem:     &schema.Schema{Type: schema.TypeString},
    55  			},
    56  
    57  			"security_group_id": &schema.Schema{
    58  				Type:     schema.TypeString,
    59  				Required: true,
    60  				ForceNew: true,
    61  			},
    62  
    63  			"source_security_group_id": &schema.Schema{
    64  				Type:     schema.TypeString,
    65  				Optional: true,
    66  				ForceNew: true,
    67  				Computed: true,
    68  			},
    69  
    70  			"self": &schema.Schema{
    71  				Type:     schema.TypeBool,
    72  				Optional: true,
    73  				Default:  false,
    74  				ForceNew: true,
    75  			},
    76  		},
    77  	}
    78  }
    79  
    80  func resourceAwsSecurityGroupRuleCreate(d *schema.ResourceData, meta interface{}) error {
    81  	conn := meta.(*AWSClient).ec2conn
    82  	sg_id := d.Get("security_group_id").(string)
    83  	sg, err := findResourceSecurityGroup(conn, sg_id)
    84  
    85  	if err != nil {
    86  		return fmt.Errorf("sorry")
    87  	}
    88  
    89  	perm := expandIPPerm(d, sg)
    90  
    91  	ruleType := d.Get("type").(string)
    92  
    93  	switch ruleType {
    94  	case "ingress":
    95  		log.Printf("[DEBUG] Authorizing security group %s %s rule: %s",
    96  			sg_id, "Ingress", awsutil.StringValue(perm))
    97  
    98  		req := &ec2.AuthorizeSecurityGroupIngressInput{
    99  			GroupID:       sg.GroupID,
   100  			IPPermissions: []*ec2.IPPermission{perm},
   101  		}
   102  
   103  		if sg.VPCID == nil || *sg.VPCID == "" {
   104  			req.GroupID = nil
   105  			req.GroupName = sg.GroupName
   106  		}
   107  
   108  		_, err := conn.AuthorizeSecurityGroupIngress(req)
   109  
   110  		if err != nil {
   111  			return fmt.Errorf(
   112  				"Error authorizing security group %s rules: %s",
   113  				"rules", err)
   114  		}
   115  
   116  	case "egress":
   117  		log.Printf("[DEBUG] Authorizing security group %s %s rule: %#v",
   118  			sg_id, "Egress", perm)
   119  
   120  		req := &ec2.AuthorizeSecurityGroupEgressInput{
   121  			GroupID:       sg.GroupID,
   122  			IPPermissions: []*ec2.IPPermission{perm},
   123  		}
   124  
   125  		_, err = conn.AuthorizeSecurityGroupEgress(req)
   126  
   127  		if err != nil {
   128  			return fmt.Errorf(
   129  				"Error authorizing security group %s rules: %s",
   130  				"rules", err)
   131  		}
   132  
   133  	default:
   134  		return fmt.Errorf("Security Group Rule must be type 'ingress' or type 'egress'")
   135  	}
   136  
   137  	d.SetId(ipPermissionIDHash(ruleType, perm))
   138  
   139  	return resourceAwsSecurityGroupRuleRead(d, meta)
   140  }
   141  
   142  func resourceAwsSecurityGroupRuleRead(d *schema.ResourceData, meta interface{}) error {
   143  	conn := meta.(*AWSClient).ec2conn
   144  	sg_id := d.Get("security_group_id").(string)
   145  	sg, err := findResourceSecurityGroup(conn, sg_id)
   146  	if err != nil {
   147  		d.SetId("")
   148  	}
   149  
   150  	var rule *ec2.IPPermission
   151  	ruleType := d.Get("type").(string)
   152  	var rl []*ec2.IPPermission
   153  	switch ruleType {
   154  	case "ingress":
   155  		rl = sg.IPPermissions
   156  	default:
   157  		rl = sg.IPPermissionsEgress
   158  	}
   159  
   160  	for _, r := range rl {
   161  		if d.Id() == ipPermissionIDHash(ruleType, r) {
   162  			rule = r
   163  		}
   164  	}
   165  
   166  	if rule == nil {
   167  		log.Printf("[DEBUG] Unable to find matching %s Security Group Rule for Group %s",
   168  			ruleType, sg_id)
   169  		d.SetId("")
   170  		return nil
   171  	}
   172  
   173  	d.Set("from_port", rule.FromPort)
   174  	d.Set("to_port", rule.ToPort)
   175  	d.Set("protocol", rule.IPProtocol)
   176  	d.Set("type", ruleType)
   177  
   178  	var cb []string
   179  	for _, c := range rule.IPRanges {
   180  		cb = append(cb, *c.CIDRIP)
   181  	}
   182  
   183  	d.Set("cidr_blocks", cb)
   184  
   185  	if len(rule.UserIDGroupPairs) > 0 {
   186  		s := rule.UserIDGroupPairs[0]
   187  		d.Set("source_security_group_id", *s.GroupID)
   188  	}
   189  
   190  	return nil
   191  }
   192  
   193  func resourceAwsSecurityGroupRuleDelete(d *schema.ResourceData, meta interface{}) error {
   194  	conn := meta.(*AWSClient).ec2conn
   195  	sg_id := d.Get("security_group_id").(string)
   196  	sg, err := findResourceSecurityGroup(conn, sg_id)
   197  
   198  	if err != nil {
   199  		return fmt.Errorf("sorry")
   200  	}
   201  
   202  	perm := expandIPPerm(d, sg)
   203  	ruleType := d.Get("type").(string)
   204  	switch ruleType {
   205  	case "ingress":
   206  		log.Printf("[DEBUG] Revoking rule (%s) from security group %s:\n%s",
   207  			"ingress", sg_id, awsutil.StringValue(perm))
   208  		req := &ec2.RevokeSecurityGroupIngressInput{
   209  			GroupID:       sg.GroupID,
   210  			IPPermissions: []*ec2.IPPermission{perm},
   211  		}
   212  
   213  		_, err = conn.RevokeSecurityGroupIngress(req)
   214  
   215  		if err != nil {
   216  			return fmt.Errorf(
   217  				"Error revoking security group %s rules: %s",
   218  				sg_id, err)
   219  		}
   220  	case "egress":
   221  
   222  		log.Printf("[DEBUG] Revoking security group %#v %s rule: %#v",
   223  			sg_id, "egress", perm)
   224  		req := &ec2.RevokeSecurityGroupEgressInput{
   225  			GroupID:       sg.GroupID,
   226  			IPPermissions: []*ec2.IPPermission{perm},
   227  		}
   228  
   229  		_, err = conn.RevokeSecurityGroupEgress(req)
   230  
   231  		if err != nil {
   232  			return fmt.Errorf(
   233  				"Error revoking security group %s rules: %s",
   234  				sg_id, err)
   235  		}
   236  	}
   237  
   238  	d.SetId("")
   239  
   240  	return nil
   241  }
   242  
   243  func findResourceSecurityGroup(conn *ec2.EC2, id string) (*ec2.SecurityGroup, error) {
   244  	req := &ec2.DescribeSecurityGroupsInput{
   245  		GroupIDs: []*string{aws.String(id)},
   246  	}
   247  	resp, err := conn.DescribeSecurityGroups(req)
   248  	if err != nil {
   249  		if ec2err, ok := err.(awserr.Error); ok {
   250  			if ec2err.Code() == "InvalidSecurityGroupID.NotFound" ||
   251  				ec2err.Code() == "InvalidGroup.NotFound" {
   252  				resp = nil
   253  				err = nil
   254  			}
   255  		}
   256  
   257  		if err != nil {
   258  			log.Printf("Error on findResourceSecurityGroup: %s", err)
   259  			return nil, err
   260  		}
   261  	}
   262  
   263  	return resp.SecurityGroups[0], nil
   264  }
   265  
   266  func ipPermissionIDHash(ruleType string, ip *ec2.IPPermission) string {
   267  	var buf bytes.Buffer
   268  	// for egress rules, an TCP rule of -1 is automatically added, in which case
   269  	// the to and from ports will be nil. We don't record this rule locally.
   270  	if ip.IPProtocol != nil && *ip.IPProtocol != "-1" {
   271  		buf.WriteString(fmt.Sprintf("%d-", *ip.FromPort))
   272  		buf.WriteString(fmt.Sprintf("%d-", *ip.ToPort))
   273  		buf.WriteString(fmt.Sprintf("%s-", *ip.IPProtocol))
   274  	}
   275  	buf.WriteString(fmt.Sprintf("%s-", ruleType))
   276  
   277  	// We need to make sure to sort the strings below so that we always
   278  	// generate the same hash code no matter what is in the set.
   279  	if len(ip.IPRanges) > 0 {
   280  		s := make([]string, len(ip.IPRanges))
   281  		for i, r := range ip.IPRanges {
   282  			s[i] = *r.CIDRIP
   283  		}
   284  		sort.Strings(s)
   285  
   286  		for _, v := range s {
   287  			buf.WriteString(fmt.Sprintf("%s-", v))
   288  		}
   289  	}
   290  
   291  	return fmt.Sprintf("sg-%d", hashcode.String(buf.String()))
   292  }
   293  
   294  func expandIPPerm(d *schema.ResourceData, sg *ec2.SecurityGroup) *ec2.IPPermission {
   295  	var perm ec2.IPPermission
   296  
   297  	perm.FromPort = aws.Long(int64(d.Get("from_port").(int)))
   298  	perm.ToPort = aws.Long(int64(d.Get("to_port").(int)))
   299  	perm.IPProtocol = aws.String(d.Get("protocol").(string))
   300  
   301  	// build a group map that behaves like a set
   302  	groups := make(map[string]bool)
   303  	if raw, ok := d.GetOk("source_security_group_id"); ok {
   304  		groups[raw.(string)] = true
   305  	}
   306  
   307  	if v, ok := d.GetOk("self"); ok && v.(bool) {
   308  		if sg.VPCID != nil && *sg.VPCID != "" {
   309  			groups[*sg.GroupID] = true
   310  		} else {
   311  			groups[*sg.GroupName] = true
   312  		}
   313  	}
   314  
   315  	if len(groups) > 0 {
   316  		perm.UserIDGroupPairs = make([]*ec2.UserIDGroupPair, len(groups))
   317  		// build string list of group name/ids
   318  		var gl []string
   319  		for k, _ := range groups {
   320  			gl = append(gl, k)
   321  		}
   322  
   323  		for i, name := range gl {
   324  			ownerId, id := "", name
   325  			if items := strings.Split(id, "/"); len(items) > 1 {
   326  				ownerId, id = items[0], items[1]
   327  			}
   328  
   329  			perm.UserIDGroupPairs[i] = &ec2.UserIDGroupPair{
   330  				GroupID: aws.String(id),
   331  				UserID:  aws.String(ownerId),
   332  			}
   333  
   334  			if sg.VPCID == nil || *sg.VPCID == "" {
   335  				perm.UserIDGroupPairs[i].GroupID = nil
   336  				perm.UserIDGroupPairs[i].GroupName = aws.String(id)
   337  				perm.UserIDGroupPairs[i].UserID = nil
   338  			}
   339  		}
   340  	}
   341  
   342  	if raw, ok := d.GetOk("cidr_blocks"); ok {
   343  		list := raw.([]interface{})
   344  		perm.IPRanges = make([]*ec2.IPRange, len(list))
   345  		for i, v := range list {
   346  			perm.IPRanges[i] = &ec2.IPRange{CIDRIP: aws.String(v.(string))}
   347  		}
   348  	}
   349  
   350  	return &perm
   351  }