github.com/paulmey/terraform@v0.5.2-0.20150519145237-046e9b4c884d/builtin/providers/aws/resource_aws_security_group.go (about)

     1  package aws
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"log"
     7  	"sort"
     8  	"time"
     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/resource"
    14  	"github.com/hashicorp/terraform/helper/schema"
    15  )
    16  
    17  func resourceAwsSecurityGroup() *schema.Resource {
    18  	return &schema.Resource{
    19  		Create: resourceAwsSecurityGroupCreate,
    20  		Read:   resourceAwsSecurityGroupRead,
    21  		Update: resourceAwsSecurityGroupUpdate,
    22  		Delete: resourceAwsSecurityGroupDelete,
    23  
    24  		Schema: map[string]*schema.Schema{
    25  			"name": &schema.Schema{
    26  				Type:     schema.TypeString,
    27  				Optional: true,
    28  				Computed: true,
    29  				ForceNew: true,
    30  			},
    31  
    32  			"description": &schema.Schema{
    33  				Type:     schema.TypeString,
    34  				Optional: true,
    35  				ForceNew: true,
    36  				Default:  "Managed by Terraform",
    37  			},
    38  
    39  			"vpc_id": &schema.Schema{
    40  				Type:     schema.TypeString,
    41  				Optional: true,
    42  				ForceNew: true,
    43  				Computed: true,
    44  			},
    45  
    46  			"ingress": &schema.Schema{
    47  				Type:     schema.TypeSet,
    48  				Optional: true,
    49  				Computed: true,
    50  				Elem: &schema.Resource{
    51  					Schema: map[string]*schema.Schema{
    52  						"from_port": &schema.Schema{
    53  							Type:     schema.TypeInt,
    54  							Required: true,
    55  						},
    56  
    57  						"to_port": &schema.Schema{
    58  							Type:     schema.TypeInt,
    59  							Required: true,
    60  						},
    61  
    62  						"protocol": &schema.Schema{
    63  							Type:     schema.TypeString,
    64  							Required: true,
    65  						},
    66  
    67  						"cidr_blocks": &schema.Schema{
    68  							Type:     schema.TypeList,
    69  							Optional: true,
    70  							Elem:     &schema.Schema{Type: schema.TypeString},
    71  						},
    72  
    73  						"security_groups": &schema.Schema{
    74  							Type:     schema.TypeSet,
    75  							Optional: true,
    76  							Elem:     &schema.Schema{Type: schema.TypeString},
    77  							Set: func(v interface{}) int {
    78  								return hashcode.String(v.(string))
    79  							},
    80  						},
    81  
    82  						"self": &schema.Schema{
    83  							Type:     schema.TypeBool,
    84  							Optional: true,
    85  							Default:  false,
    86  						},
    87  					},
    88  				},
    89  				Set: resourceAwsSecurityGroupRuleHash,
    90  			},
    91  
    92  			"egress": &schema.Schema{
    93  				Type:     schema.TypeSet,
    94  				Optional: true,
    95  				Computed: true,
    96  				Elem: &schema.Resource{
    97  					Schema: map[string]*schema.Schema{
    98  						"from_port": &schema.Schema{
    99  							Type:     schema.TypeInt,
   100  							Required: true,
   101  						},
   102  
   103  						"to_port": &schema.Schema{
   104  							Type:     schema.TypeInt,
   105  							Required: true,
   106  						},
   107  
   108  						"protocol": &schema.Schema{
   109  							Type:     schema.TypeString,
   110  							Required: true,
   111  						},
   112  
   113  						"cidr_blocks": &schema.Schema{
   114  							Type:     schema.TypeList,
   115  							Optional: true,
   116  							Elem:     &schema.Schema{Type: schema.TypeString},
   117  						},
   118  
   119  						"security_groups": &schema.Schema{
   120  							Type:     schema.TypeSet,
   121  							Optional: true,
   122  							Elem:     &schema.Schema{Type: schema.TypeString},
   123  							Set: func(v interface{}) int {
   124  								return hashcode.String(v.(string))
   125  							},
   126  						},
   127  
   128  						"self": &schema.Schema{
   129  							Type:     schema.TypeBool,
   130  							Optional: true,
   131  							Default:  false,
   132  						},
   133  					},
   134  				},
   135  				Set: resourceAwsSecurityGroupRuleHash,
   136  			},
   137  
   138  			"owner_id": &schema.Schema{
   139  				Type:     schema.TypeString,
   140  				Computed: true,
   141  			},
   142  
   143  			"tags": tagsSchema(),
   144  		},
   145  	}
   146  }
   147  
   148  func resourceAwsSecurityGroupCreate(d *schema.ResourceData, meta interface{}) error {
   149  	conn := meta.(*AWSClient).ec2conn
   150  
   151  	securityGroupOpts := &ec2.CreateSecurityGroupInput{}
   152  
   153  	if v, ok := d.GetOk("vpc_id"); ok {
   154  		securityGroupOpts.VPCID = aws.String(v.(string))
   155  	}
   156  
   157  	if v := d.Get("description"); v != nil {
   158  		securityGroupOpts.Description = aws.String(v.(string))
   159  	}
   160  
   161  	var groupName string
   162  	if v, ok := d.GetOk("name"); ok {
   163  		groupName = v.(string)
   164  	} else {
   165  		groupName = resource.UniqueId()
   166  	}
   167  	securityGroupOpts.GroupName = aws.String(groupName)
   168  
   169  	log.Printf(
   170  		"[DEBUG] Security Group create configuration: %#v", securityGroupOpts)
   171  	createResp, err := conn.CreateSecurityGroup(securityGroupOpts)
   172  	if err != nil {
   173  		return fmt.Errorf("Error creating Security Group: %s", err)
   174  	}
   175  
   176  	d.SetId(*createResp.GroupID)
   177  
   178  	log.Printf("[INFO] Security Group ID: %s", d.Id())
   179  
   180  	// Wait for the security group to truly exist
   181  	log.Printf(
   182  		"[DEBUG] Waiting for Security Group (%s) to exist",
   183  		d.Id())
   184  	stateConf := &resource.StateChangeConf{
   185  		Pending: []string{""},
   186  		Target:  "exists",
   187  		Refresh: SGStateRefreshFunc(conn, d.Id()),
   188  		Timeout: 1 * time.Minute,
   189  	}
   190  
   191  	resp, err := stateConf.WaitForState()
   192  	if err != nil {
   193  		return fmt.Errorf(
   194  			"Error waiting for Security Group (%s) to become available: %s",
   195  			d.Id(), err)
   196  	}
   197  
   198  	// AWS defaults all Security Groups to have an ALLOW ALL egress rule. Here we
   199  	// revoke that rule, so users don't unknowningly have/use it.
   200  	group := resp.(*ec2.SecurityGroup)
   201  	if group.VPCID != nil && *group.VPCID != "" {
   202  		log.Printf("[DEBUG] Revoking default egress rule for Security Group for %s", d.Id())
   203  
   204  		req := &ec2.RevokeSecurityGroupEgressInput{
   205  			GroupID: createResp.GroupID,
   206  			IPPermissions: []*ec2.IPPermission{
   207  				&ec2.IPPermission{
   208  					FromPort: aws.Long(int64(0)),
   209  					ToPort:   aws.Long(int64(0)),
   210  					IPRanges: []*ec2.IPRange{
   211  						&ec2.IPRange{
   212  							CIDRIP: aws.String("0.0.0.0/0"),
   213  						},
   214  					},
   215  					IPProtocol: aws.String("-1"),
   216  				},
   217  			},
   218  		}
   219  
   220  		if _, err = conn.RevokeSecurityGroupEgress(req); err != nil {
   221  			return fmt.Errorf(
   222  				"Error revoking default egress rule for Security Group (%s): %s",
   223  				d.Id(), err)
   224  		}
   225  
   226  	}
   227  
   228  	return resourceAwsSecurityGroupUpdate(d, meta)
   229  }
   230  
   231  func resourceAwsSecurityGroupRead(d *schema.ResourceData, meta interface{}) error {
   232  	conn := meta.(*AWSClient).ec2conn
   233  
   234  	sgRaw, _, err := SGStateRefreshFunc(conn, d.Id())()
   235  	if err != nil {
   236  		return err
   237  	}
   238  	if sgRaw == nil {
   239  		d.SetId("")
   240  		return nil
   241  	}
   242  
   243  	sg := sgRaw.(*ec2.SecurityGroup)
   244  
   245  	ingressRules := resourceAwsSecurityGroupIPPermGather(d, sg.IPPermissions)
   246  	egressRules := resourceAwsSecurityGroupIPPermGather(d, sg.IPPermissionsEgress)
   247  
   248  	d.Set("description", sg.Description)
   249  	d.Set("name", sg.GroupName)
   250  	d.Set("vpc_id", sg.VPCID)
   251  	d.Set("owner_id", sg.OwnerID)
   252  	d.Set("ingress", ingressRules)
   253  	d.Set("egress", egressRules)
   254  	d.Set("tags", tagsToMap(sg.Tags))
   255  	return nil
   256  }
   257  
   258  func resourceAwsSecurityGroupUpdate(d *schema.ResourceData, meta interface{}) error {
   259  	conn := meta.(*AWSClient).ec2conn
   260  
   261  	sgRaw, _, err := SGStateRefreshFunc(conn, d.Id())()
   262  	if err != nil {
   263  		return err
   264  	}
   265  	if sgRaw == nil {
   266  		d.SetId("")
   267  		return nil
   268  	}
   269  
   270  	group := sgRaw.(*ec2.SecurityGroup)
   271  
   272  	err = resourceAwsSecurityGroupUpdateRules(d, "ingress", meta, group)
   273  	if err != nil {
   274  		return err
   275  	}
   276  
   277  	if d.Get("vpc_id") != nil {
   278  		err = resourceAwsSecurityGroupUpdateRules(d, "egress", meta, group)
   279  		if err != nil {
   280  			return err
   281  		}
   282  	}
   283  
   284  	if err := setTags(conn, d); err != nil {
   285  		return err
   286  	}
   287  
   288  	d.SetPartial("tags")
   289  
   290  	return resourceAwsSecurityGroupRead(d, meta)
   291  }
   292  
   293  func resourceAwsSecurityGroupDelete(d *schema.ResourceData, meta interface{}) error {
   294  	conn := meta.(*AWSClient).ec2conn
   295  
   296  	log.Printf("[DEBUG] Security Group destroy: %v", d.Id())
   297  
   298  	return resource.Retry(5*time.Minute, func() error {
   299  		_, err := conn.DeleteSecurityGroup(&ec2.DeleteSecurityGroupInput{
   300  			GroupID: aws.String(d.Id()),
   301  		})
   302  		if err != nil {
   303  			ec2err, ok := err.(aws.APIError)
   304  			if !ok {
   305  				return err
   306  			}
   307  
   308  			switch ec2err.Code {
   309  			case "InvalidGroup.NotFound":
   310  				return nil
   311  			case "DependencyViolation":
   312  				// If it is a dependency violation, we want to retry
   313  				return err
   314  			default:
   315  				// Any other error, we want to quit the retry loop immediately
   316  				return resource.RetryError{Err: err}
   317  			}
   318  		}
   319  
   320  		return nil
   321  	})
   322  }
   323  
   324  func resourceAwsSecurityGroupRuleHash(v interface{}) int {
   325  	var buf bytes.Buffer
   326  	m := v.(map[string]interface{})
   327  	buf.WriteString(fmt.Sprintf("%d-", m["from_port"].(int)))
   328  	buf.WriteString(fmt.Sprintf("%d-", m["to_port"].(int)))
   329  	buf.WriteString(fmt.Sprintf("%s-", m["protocol"].(string)))
   330  	buf.WriteString(fmt.Sprintf("%t-", m["self"].(bool)))
   331  
   332  	// We need to make sure to sort the strings below so that we always
   333  	// generate the same hash code no matter what is in the set.
   334  	if v, ok := m["cidr_blocks"]; ok {
   335  		vs := v.([]interface{})
   336  		s := make([]string, len(vs))
   337  		for i, raw := range vs {
   338  			s[i] = raw.(string)
   339  		}
   340  		sort.Strings(s)
   341  
   342  		for _, v := range s {
   343  			buf.WriteString(fmt.Sprintf("%s-", v))
   344  		}
   345  	}
   346  	if v, ok := m["security_groups"]; ok {
   347  		vs := v.(*schema.Set).List()
   348  		s := make([]string, len(vs))
   349  		for i, raw := range vs {
   350  			s[i] = raw.(string)
   351  		}
   352  		sort.Strings(s)
   353  
   354  		for _, v := range s {
   355  			buf.WriteString(fmt.Sprintf("%s-", v))
   356  		}
   357  	}
   358  
   359  	return hashcode.String(buf.String())
   360  }
   361  
   362  func resourceAwsSecurityGroupIPPermGather(d *schema.ResourceData, permissions []*ec2.IPPermission) []map[string]interface{} {
   363  	ruleMap := make(map[string]map[string]interface{})
   364  	for _, perm := range permissions {
   365  		var fromPort, toPort int64
   366  		if v := perm.FromPort; v != nil {
   367  			fromPort = *v
   368  		}
   369  		if v := perm.ToPort; v != nil {
   370  			toPort = *v
   371  		}
   372  
   373  		k := fmt.Sprintf("%s-%d-%d", *perm.IPProtocol, fromPort, toPort)
   374  		m, ok := ruleMap[k]
   375  		if !ok {
   376  			m = make(map[string]interface{})
   377  			ruleMap[k] = m
   378  		}
   379  
   380  		m["from_port"] = fromPort
   381  		m["to_port"] = toPort
   382  		m["protocol"] = *perm.IPProtocol
   383  
   384  		if len(perm.IPRanges) > 0 {
   385  			raw, ok := m["cidr_blocks"]
   386  			if !ok {
   387  				raw = make([]string, 0, len(perm.IPRanges))
   388  			}
   389  			list := raw.([]string)
   390  
   391  			for _, ip := range perm.IPRanges {
   392  				list = append(list, *ip.CIDRIP)
   393  			}
   394  
   395  			m["cidr_blocks"] = list
   396  		}
   397  
   398  		var groups []string
   399  		if len(perm.UserIDGroupPairs) > 0 {
   400  			groups = flattenSecurityGroups(perm.UserIDGroupPairs)
   401  		}
   402  		for i, id := range groups {
   403  			if id == d.Id() {
   404  				groups[i], groups = groups[len(groups)-1], groups[:len(groups)-1]
   405  				m["self"] = true
   406  			}
   407  		}
   408  
   409  		if len(groups) > 0 {
   410  			raw, ok := m["security_groups"]
   411  			if !ok {
   412  				raw = make([]string, 0, len(groups))
   413  			}
   414  			list := raw.([]string)
   415  
   416  			list = append(list, groups...)
   417  			m["security_groups"] = list
   418  		}
   419  	}
   420  	rules := make([]map[string]interface{}, 0, len(ruleMap))
   421  	for _, m := range ruleMap {
   422  		rules = append(rules, m)
   423  	}
   424  	return rules
   425  }
   426  
   427  func resourceAwsSecurityGroupUpdateRules(
   428  	d *schema.ResourceData, ruleset string,
   429  	meta interface{}, group *ec2.SecurityGroup) error {
   430  
   431  	if d.HasChange(ruleset) {
   432  		o, n := d.GetChange(ruleset)
   433  		if o == nil {
   434  			o = new(schema.Set)
   435  		}
   436  		if n == nil {
   437  			n = new(schema.Set)
   438  		}
   439  
   440  		os := o.(*schema.Set)
   441  		ns := n.(*schema.Set)
   442  
   443  		remove, err := expandIPPerms(group, os.Difference(ns).List())
   444  		if err != nil {
   445  			return err
   446  		}
   447  		add, err := expandIPPerms(group, ns.Difference(os).List())
   448  		if err != nil {
   449  			return err
   450  		}
   451  
   452  		// TODO: We need to handle partial state better in the in-between
   453  		// in this update.
   454  
   455  		// TODO: It'd be nicer to authorize before removing, but then we have
   456  		// to deal with complicated unrolling to get individual CIDR blocks
   457  		// to avoid authorizing already authorized sources. Removing before
   458  		// adding is easier here, and Terraform should be fast enough to
   459  		// not have service issues.
   460  
   461  		if len(remove) > 0 || len(add) > 0 {
   462  			conn := meta.(*AWSClient).ec2conn
   463  
   464  			var err error
   465  			if len(remove) > 0 {
   466  				log.Printf("[DEBUG] Revoking security group %#v %s rule: %#v",
   467  					group, ruleset, remove)
   468  
   469  				if ruleset == "egress" {
   470  					req := &ec2.RevokeSecurityGroupEgressInput{
   471  						GroupID:       group.GroupID,
   472  						IPPermissions: remove,
   473  					}
   474  					_, err = conn.RevokeSecurityGroupEgress(req)
   475  				} else {
   476  					req := &ec2.RevokeSecurityGroupIngressInput{
   477  						GroupID:       group.GroupID,
   478  						IPPermissions: remove,
   479  					}
   480  					_, err = conn.RevokeSecurityGroupIngress(req)
   481  				}
   482  
   483  				if err != nil {
   484  					return fmt.Errorf(
   485  						"Error authorizing security group %s rules: %s",
   486  						ruleset, err)
   487  				}
   488  			}
   489  
   490  			if len(add) > 0 {
   491  				log.Printf("[DEBUG] Authorizing security group %#v %s rule: %#v",
   492  					group, ruleset, add)
   493  				// Authorize the new rules
   494  				if ruleset == "egress" {
   495  					req := &ec2.AuthorizeSecurityGroupEgressInput{
   496  						GroupID:       group.GroupID,
   497  						IPPermissions: add,
   498  					}
   499  					_, err = conn.AuthorizeSecurityGroupEgress(req)
   500  				} else {
   501  					req := &ec2.AuthorizeSecurityGroupIngressInput{
   502  						GroupID:       group.GroupID,
   503  						IPPermissions: add,
   504  					}
   505  					if group.VPCID == nil || *group.VPCID == "" {
   506  						req.GroupID = nil
   507  						req.GroupName = group.GroupName
   508  					}
   509  
   510  					_, err = conn.AuthorizeSecurityGroupIngress(req)
   511  				}
   512  
   513  				if err != nil {
   514  					return fmt.Errorf(
   515  						"Error authorizing security group %s rules: %s",
   516  						ruleset, err)
   517  				}
   518  			}
   519  		}
   520  	}
   521  	return nil
   522  }
   523  
   524  // SGStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch
   525  // a security group.
   526  func SGStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc {
   527  	return func() (interface{}, string, error) {
   528  		req := &ec2.DescribeSecurityGroupsInput{
   529  			GroupIDs: []*string{aws.String(id)},
   530  		}
   531  		resp, err := conn.DescribeSecurityGroups(req)
   532  		if err != nil {
   533  			if ec2err, ok := err.(aws.APIError); ok {
   534  				if ec2err.Code == "InvalidSecurityGroupID.NotFound" ||
   535  					ec2err.Code == "InvalidGroup.NotFound" {
   536  					resp = nil
   537  					err = nil
   538  				}
   539  			}
   540  
   541  			if err != nil {
   542  				log.Printf("Error on SGStateRefresh: %s", err)
   543  				return nil, "", err
   544  			}
   545  		}
   546  
   547  		if resp == nil {
   548  			return nil, "", nil
   549  		}
   550  
   551  		group := resp.SecurityGroups[0]
   552  		return group, "exists", nil
   553  	}
   554  }