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