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