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