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