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