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