terraform/builtin/providers/aws/resource_aws_security_group.go

1066 lines
29 KiB
Go
Raw Normal View History

2014-07-08 20:06:39 +02:00
package aws
import (
"bytes"
2014-07-08 20:06:39 +02:00
"fmt"
"log"
2014-08-22 17:46:03 +02:00
"sort"
"strconv"
"strings"
"time"
2014-07-08 20:06:39 +02:00
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/terraform/helper/hashcode"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
2014-07-08 20:06:39 +02:00
)
func resourceAwsSecurityGroup() *schema.Resource {
return &schema.Resource{
Create: resourceAwsSecurityGroupCreate,
Read: resourceAwsSecurityGroupRead,
Update: resourceAwsSecurityGroupUpdate,
Delete: resourceAwsSecurityGroupDelete,
2016-05-04 22:34:04 +02:00
Importer: &schema.ResourceImporter{
State: resourceAwsSecurityGroupImportState,
},
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
2015-12-17 18:24:24 +01:00
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
ConflictsWith: []string{"name_prefix"},
ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
if len(value) > 255 {
errors = append(errors, fmt.Errorf(
"%q cannot be longer than 255 characters", k))
}
return
},
},
"name_prefix": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
if len(value) > 100 {
errors = append(errors, fmt.Errorf(
"%q cannot be longer than 100 characters, name is limited to 255", k))
}
return
},
},
"description": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Default: "Managed by Terraform",
ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
if len(value) > 255 {
errors = append(errors, fmt.Errorf(
"%q cannot be longer than 255 characters", k))
}
return
},
},
"vpc_id": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Computed: true,
},
"ingress": &schema.Schema{
Type: schema.TypeSet,
Optional: true,
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"from_port": &schema.Schema{
Type: schema.TypeInt,
Required: true,
},
"to_port": &schema.Schema{
Type: schema.TypeInt,
Required: true,
},
"protocol": &schema.Schema{
Type: schema.TypeString,
Required: true,
StateFunc: protocolStateFunc,
},
"cidr_blocks": &schema.Schema{
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"security_groups": &schema.Schema{
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
2016-02-08 00:51:26 +01:00
Set: schema.HashString,
},
"self": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Default: false,
},
},
},
Set: resourceAwsSecurityGroupRuleHash,
},
"egress": &schema.Schema{
Type: schema.TypeSet,
Optional: true,
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"from_port": &schema.Schema{
Type: schema.TypeInt,
Required: true,
},
"to_port": &schema.Schema{
Type: schema.TypeInt,
Required: true,
},
"protocol": &schema.Schema{
Type: schema.TypeString,
Required: true,
StateFunc: protocolStateFunc,
},
"cidr_blocks": &schema.Schema{
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"prefix_list_ids": &schema.Schema{
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"security_groups": &schema.Schema{
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
2016-02-08 00:51:26 +01:00
Set: schema.HashString,
},
"self": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Default: false,
},
},
},
Set: resourceAwsSecurityGroupRuleHash,
},
"owner_id": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
2014-10-14 23:07:01 +02:00
"tags": tagsSchema(),
},
}
}
func resourceAwsSecurityGroupCreate(d *schema.ResourceData, meta interface{}) error {
2015-04-16 22:05:55 +02:00
conn := meta.(*AWSClient).ec2conn
2014-07-08 20:06:39 +02:00
2015-05-05 23:42:08 +02:00
securityGroupOpts := &ec2.CreateSecurityGroupInput{}
if v, ok := d.GetOk("vpc_id"); ok {
securityGroupOpts.VpcId = aws.String(v.(string))
2015-05-05 23:42:08 +02:00
}
if v := d.Get("description"); v != nil {
securityGroupOpts.Description = aws.String(v.(string))
2014-07-08 20:06:39 +02:00
}
var groupName string
if v, ok := d.GetOk("name"); ok {
groupName = v.(string)
} else if v, ok := d.GetOk("name_prefix"); ok {
groupName = resource.PrefixedUniqueId(v.(string))
} else {
groupName = resource.UniqueId()
}
securityGroupOpts.GroupName = aws.String(groupName)
var err error
log.Printf(
"[DEBUG] Security Group create configuration: %#v", securityGroupOpts)
createResp, err := conn.CreateSecurityGroup(securityGroupOpts)
2014-07-08 20:06:39 +02:00
if err != nil {
return fmt.Errorf("Error creating Security Group: %s", err)
2014-07-08 20:06:39 +02:00
}
d.SetId(*createResp.GroupId)
2014-07-08 20:06:39 +02:00
log.Printf("[INFO] Security Group ID: %s", d.Id())
// Wait for the security group to truly exist
log.Printf(
"[DEBUG] Waiting for Security Group (%s) to exist",
d.Id())
stateConf := &resource.StateChangeConf{
Pending: []string{""},
Target: []string{"exists"},
Refresh: SGStateRefreshFunc(conn, d.Id()),
Timeout: 1 * time.Minute,
}
2015-05-01 21:56:16 +02:00
2015-05-05 23:42:08 +02:00
resp, err := stateConf.WaitForState()
if err != nil {
return fmt.Errorf(
"Error waiting for Security Group (%s) to become available: %s",
d.Id(), err)
}
2014-07-08 20:06:39 +02:00
provider/aws: `aws_security_group` now creates tags as early as possible (#7849) in the process Fixes #7577 7577 discovered that sometimes setting tags at the end of the creation model doesn't quite work for everyone. We now move that further up the tree by calling the setTags func a second time. The setTags func in the Update is not called immediately after creation as we check for it not being a NewResource ``` % make testacc TEST=./builtin/providers/aws TESTARGS='-run=TestAccAWSSecurityGroup_' ==> Checking that code complies with gofmt requirements... go generate $(go list ./... | grep -v /terraform/vendor/) TF_ACC=1 go test ./builtin/providers/aws -v -run=TestAccAWSSecurityGroup_ -timeout 120m === RUN TestAccAWSSecurityGroup_importBasic --- PASS: TestAccAWSSecurityGroup_importBasic (60.96s) === RUN TestAccAWSSecurityGroup_importSelf --- PASS: TestAccAWSSecurityGroup_importSelf (72.72s) === RUN TestAccAWSSecurityGroup_basic --- PASS: TestAccAWSSecurityGroup_basic (62.33s) === RUN TestAccAWSSecurityGroup_namePrefix --- PASS: TestAccAWSSecurityGroup_namePrefix (22.12s) === RUN TestAccAWSSecurityGroup_self --- PASS: TestAccAWSSecurityGroup_self (64.26s) === RUN TestAccAWSSecurityGroup_vpc --- PASS: TestAccAWSSecurityGroup_vpc (58.35s) === RUN TestAccAWSSecurityGroup_vpcNegOneIngress --- PASS: TestAccAWSSecurityGroup_vpcNegOneIngress (54.95s) === RUN TestAccAWSSecurityGroup_MultiIngress --- PASS: TestAccAWSSecurityGroup_MultiIngress (64.81s) === RUN TestAccAWSSecurityGroup_Change --- PASS: TestAccAWSSecurityGroup_Change (96.86s) === RUN TestAccAWSSecurityGroup_generatedName --- PASS: TestAccAWSSecurityGroup_generatedName (60.75s) === RUN TestAccAWSSecurityGroup_DefaultEgress_VPC --- PASS: TestAccAWSSecurityGroup_DefaultEgress_VPC (57.05s) === RUN TestAccAWSSecurityGroup_DefaultEgress_Classic --- PASS: TestAccAWSSecurityGroup_DefaultEgress_Classic (20.94s) === RUN TestAccAWSSecurityGroup_drift --- PASS: TestAccAWSSecurityGroup_drift (27.39s) === RUN TestAccAWSSecurityGroup_drift_complex --- PASS: TestAccAWSSecurityGroup_drift_complex (64.62s) === RUN TestAccAWSSecurityGroup_tags --- PASS: TestAccAWSSecurityGroup_tags (87.49s) === RUN TestAccAWSSecurityGroup_CIDRandGroups --- PASS: TestAccAWSSecurityGroup_CIDRandGroups (71.62s) === RUN TestAccAWSSecurityGroup_ingressWithCidrAndSGs --- PASS: TestAccAWSSecurityGroup_ingressWithCidrAndSGs (69.60s) === RUN TestAccAWSSecurityGroup_ingressWithCidrAndSGs_classic --- PASS: TestAccAWSSecurityGroup_ingressWithCidrAndSGs_classic (25.47s) === RUN TestAccAWSSecurityGroup_egressWithPrefixList --- PASS: TestAccAWSSecurityGroup_egressWithPrefixList (64.46s) === RUN TestAccAWSSecurityGroup_failWithDiffMismatch --- PASS: TestAccAWSSecurityGroup_failWithDiffMismatch (60.21s) PASS ok github.com/hashicorp/terraform/builtin/providers/aws 1166.983s ```
2016-08-15 16:11:52 +02:00
if err := setTags(conn, d); err != nil {
return err
}
2015-05-05 23:42:08 +02:00
// AWS defaults all Security Groups to have an ALLOW ALL egress rule. Here we
// revoke that rule, so users don't unknowingly have/use it.
2015-05-05 23:42:08 +02:00
group := resp.(*ec2.SecurityGroup)
if group.VpcId != nil && *group.VpcId != "" {
2015-05-05 23:42:08 +02:00
log.Printf("[DEBUG] Revoking default egress rule for Security Group for %s", d.Id())
req := &ec2.RevokeSecurityGroupEgressInput{
GroupId: createResp.GroupId,
IpPermissions: []*ec2.IpPermission{
&ec2.IpPermission{
FromPort: aws.Int64(int64(0)),
ToPort: aws.Int64(int64(0)),
IpRanges: []*ec2.IpRange{
&ec2.IpRange{
CidrIp: aws.String("0.0.0.0/0"),
2015-05-05 23:42:08 +02:00
},
},
IpProtocol: aws.String("-1"),
2015-05-05 23:42:08 +02:00
},
},
}
if _, err = conn.RevokeSecurityGroupEgress(req); err != nil {
return fmt.Errorf(
"Error revoking default egress rule for Security Group (%s): %s",
d.Id(), err)
}
}
return resourceAwsSecurityGroupUpdate(d, meta)
2014-07-08 20:06:39 +02:00
}
func resourceAwsSecurityGroupRead(d *schema.ResourceData, meta interface{}) error {
2015-04-16 22:05:55 +02:00
conn := meta.(*AWSClient).ec2conn
sgRaw, _, err := SGStateRefreshFunc(conn, d.Id())()
if err != nil {
return err
}
if sgRaw == nil {
d.SetId("")
return nil
}
sg := sgRaw.(*ec2.SecurityGroup)
remoteIngressRules := resourceAwsSecurityGroupIPPermGather(d.Id(), sg.IpPermissions, sg.OwnerId)
remoteEgressRules := resourceAwsSecurityGroupIPPermGather(d.Id(), sg.IpPermissionsEgress, sg.OwnerId)
localIngressRules := d.Get("ingress").(*schema.Set).List()
localEgressRules := d.Get("egress").(*schema.Set).List()
// Loop through the local state of rules, doing a match against the remote
// ruleSet we built above.
ingressRules := matchRules("ingress", localIngressRules, remoteIngressRules)
egressRules := matchRules("egress", localEgressRules, remoteEgressRules)
d.Set("description", sg.Description)
d.Set("name", sg.GroupName)
d.Set("vpc_id", sg.VpcId)
d.Set("owner_id", sg.OwnerId)
if err := d.Set("ingress", ingressRules); err != nil {
log.Printf("[WARN] Error setting Ingress rule set for (%s): %s", d.Id(), err)
}
if err := d.Set("egress", egressRules); err != nil {
log.Printf("[WARN] Error setting Egress rule set for (%s): %s", d.Id(), err)
}
d.Set("tags", tagsToMap(sg.Tags))
return nil
}
func resourceAwsSecurityGroupUpdate(d *schema.ResourceData, meta interface{}) error {
2015-04-16 22:05:55 +02:00
conn := meta.(*AWSClient).ec2conn
sgRaw, _, err := SGStateRefreshFunc(conn, d.Id())()
if err != nil {
return err
}
if sgRaw == nil {
d.SetId("")
return nil
}
group := sgRaw.(*ec2.SecurityGroup)
err = resourceAwsSecurityGroupUpdateRules(d, "ingress", meta, group)
if err != nil {
return err
}
if d.Get("vpc_id") != nil {
err = resourceAwsSecurityGroupUpdateRules(d, "egress", meta, group)
if err != nil {
return err
}
}
provider/aws: `aws_security_group` now creates tags as early as possible (#7849) in the process Fixes #7577 7577 discovered that sometimes setting tags at the end of the creation model doesn't quite work for everyone. We now move that further up the tree by calling the setTags func a second time. The setTags func in the Update is not called immediately after creation as we check for it not being a NewResource ``` % make testacc TEST=./builtin/providers/aws TESTARGS='-run=TestAccAWSSecurityGroup_' ==> Checking that code complies with gofmt requirements... go generate $(go list ./... | grep -v /terraform/vendor/) TF_ACC=1 go test ./builtin/providers/aws -v -run=TestAccAWSSecurityGroup_ -timeout 120m === RUN TestAccAWSSecurityGroup_importBasic --- PASS: TestAccAWSSecurityGroup_importBasic (60.96s) === RUN TestAccAWSSecurityGroup_importSelf --- PASS: TestAccAWSSecurityGroup_importSelf (72.72s) === RUN TestAccAWSSecurityGroup_basic --- PASS: TestAccAWSSecurityGroup_basic (62.33s) === RUN TestAccAWSSecurityGroup_namePrefix --- PASS: TestAccAWSSecurityGroup_namePrefix (22.12s) === RUN TestAccAWSSecurityGroup_self --- PASS: TestAccAWSSecurityGroup_self (64.26s) === RUN TestAccAWSSecurityGroup_vpc --- PASS: TestAccAWSSecurityGroup_vpc (58.35s) === RUN TestAccAWSSecurityGroup_vpcNegOneIngress --- PASS: TestAccAWSSecurityGroup_vpcNegOneIngress (54.95s) === RUN TestAccAWSSecurityGroup_MultiIngress --- PASS: TestAccAWSSecurityGroup_MultiIngress (64.81s) === RUN TestAccAWSSecurityGroup_Change --- PASS: TestAccAWSSecurityGroup_Change (96.86s) === RUN TestAccAWSSecurityGroup_generatedName --- PASS: TestAccAWSSecurityGroup_generatedName (60.75s) === RUN TestAccAWSSecurityGroup_DefaultEgress_VPC --- PASS: TestAccAWSSecurityGroup_DefaultEgress_VPC (57.05s) === RUN TestAccAWSSecurityGroup_DefaultEgress_Classic --- PASS: TestAccAWSSecurityGroup_DefaultEgress_Classic (20.94s) === RUN TestAccAWSSecurityGroup_drift --- PASS: TestAccAWSSecurityGroup_drift (27.39s) === RUN TestAccAWSSecurityGroup_drift_complex --- PASS: TestAccAWSSecurityGroup_drift_complex (64.62s) === RUN TestAccAWSSecurityGroup_tags --- PASS: TestAccAWSSecurityGroup_tags (87.49s) === RUN TestAccAWSSecurityGroup_CIDRandGroups --- PASS: TestAccAWSSecurityGroup_CIDRandGroups (71.62s) === RUN TestAccAWSSecurityGroup_ingressWithCidrAndSGs --- PASS: TestAccAWSSecurityGroup_ingressWithCidrAndSGs (69.60s) === RUN TestAccAWSSecurityGroup_ingressWithCidrAndSGs_classic --- PASS: TestAccAWSSecurityGroup_ingressWithCidrAndSGs_classic (25.47s) === RUN TestAccAWSSecurityGroup_egressWithPrefixList --- PASS: TestAccAWSSecurityGroup_egressWithPrefixList (64.46s) === RUN TestAccAWSSecurityGroup_failWithDiffMismatch --- PASS: TestAccAWSSecurityGroup_failWithDiffMismatch (60.21s) PASS ok github.com/hashicorp/terraform/builtin/providers/aws 1166.983s ```
2016-08-15 16:11:52 +02:00
if !d.IsNewResource() {
if err := setTags(conn, d); err != nil {
return err
}
d.SetPartial("tags")
2014-10-14 23:07:01 +02:00
}
return resourceAwsSecurityGroupRead(d, meta)
}
func resourceAwsSecurityGroupDelete(d *schema.ResourceData, meta interface{}) error {
2015-04-16 22:05:55 +02:00
conn := meta.(*AWSClient).ec2conn
2014-07-08 20:06:39 +02:00
log.Printf("[DEBUG] Security Group destroy: %v", d.Id())
2014-07-08 20:06:39 +02:00
if err := deleteLingeringLambdaENIs(conn, d); err != nil {
return fmt.Errorf("Failed to delete Lambda ENIs: %s", err)
}
return resource.Retry(5*time.Minute, func() *resource.RetryError {
_, err := conn.DeleteSecurityGroup(&ec2.DeleteSecurityGroupInput{
GroupId: aws.String(d.Id()),
})
if err != nil {
ec2err, ok := err.(awserr.Error)
if !ok {
return resource.RetryableError(err)
}
switch ec2err.Code() {
case "InvalidGroup.NotFound":
return nil
case "DependencyViolation":
// If it is a dependency violation, we want to retry
return resource.RetryableError(err)
default:
// Any other error, we want to quit the retry loop immediately
return resource.NonRetryableError(err)
}
2014-07-08 20:06:39 +02:00
}
return nil
})
2014-07-08 20:06:39 +02:00
}
func resourceAwsSecurityGroupRuleHash(v interface{}) int {
var buf bytes.Buffer
m := v.(map[string]interface{})
buf.WriteString(fmt.Sprintf("%d-", m["from_port"].(int)))
buf.WriteString(fmt.Sprintf("%d-", m["to_port"].(int)))
p := protocolForValue(m["protocol"].(string))
buf.WriteString(fmt.Sprintf("%s-", p))
buf.WriteString(fmt.Sprintf("%t-", m["self"].(bool)))
// We need to make sure to sort the strings below so that we always
// generate the same hash code no matter what is in the set.
if v, ok := m["cidr_blocks"]; ok {
vs := v.([]interface{})
s := make([]string, len(vs))
for i, raw := range vs {
s[i] = raw.(string)
}
sort.Strings(s)
for _, v := range s {
buf.WriteString(fmt.Sprintf("%s-", v))
}
}
if v, ok := m["prefix_list_ids"]; ok {
vs := v.([]interface{})
s := make([]string, len(vs))
for i, raw := range vs {
s[i] = raw.(string)
}
sort.Strings(s)
for _, v := range s {
buf.WriteString(fmt.Sprintf("%s-", v))
}
}
if v, ok := m["security_groups"]; ok {
vs := v.(*schema.Set).List()
s := make([]string, len(vs))
for i, raw := range vs {
s[i] = raw.(string)
}
sort.Strings(s)
for _, v := range s {
buf.WriteString(fmt.Sprintf("%s-", v))
}
}
2014-07-08 20:06:39 +02:00
return hashcode.String(buf.String())
}
func resourceAwsSecurityGroupIPPermGather(groupId string, permissions []*ec2.IpPermission, ownerId *string) []map[string]interface{} {
2015-02-18 18:07:46 +01:00
ruleMap := make(map[string]map[string]interface{})
for _, perm := range permissions {
2015-04-20 17:27:58 +02:00
var fromPort, toPort int64
2015-03-11 14:30:43 +01:00
if v := perm.FromPort; v != nil {
2015-04-20 17:27:58 +02:00
fromPort = *v
}
2015-03-11 14:30:43 +01:00
if v := perm.ToPort; v != nil {
2015-04-20 17:27:58 +02:00
toPort = *v
}
k := fmt.Sprintf("%s-%d-%d", *perm.IpProtocol, fromPort, toPort)
2015-02-18 18:07:46 +01:00
m, ok := ruleMap[k]
if !ok {
m = make(map[string]interface{})
ruleMap[k] = m
}
m["from_port"] = fromPort
m["to_port"] = toPort
m["protocol"] = *perm.IpProtocol
2015-02-18 18:07:46 +01:00
if len(perm.IpRanges) > 0 {
2015-02-18 18:07:46 +01:00
raw, ok := m["cidr_blocks"]
if !ok {
raw = make([]string, 0, len(perm.IpRanges))
2015-02-18 18:07:46 +01:00
}
list := raw.([]string)
for _, ip := range perm.IpRanges {
list = append(list, *ip.CidrIp)
}
2015-02-18 18:07:46 +01:00
m["cidr_blocks"] = list
}
if len(perm.PrefixListIds) > 0 {
raw, ok := m["prefix_list_ids"]
if !ok {
raw = make([]string, 0, len(perm.PrefixListIds))
}
list := raw.([]string)
for _, pl := range perm.PrefixListIds {
list = append(list, *pl.PrefixListId)
}
m["prefix_list_ids"] = list
}
groups := flattenSecurityGroups(perm.UserIdGroupPairs, ownerId)
for i, g := range groups {
if *g.GroupId == groupId {
2015-02-18 18:07:46 +01:00
groups[i], groups = groups[len(groups)-1], groups[:len(groups)-1]
m["self"] = true
}
}
if len(groups) > 0 {
raw, ok := m["security_groups"]
if !ok {
raw = schema.NewSet(schema.HashString, nil)
}
list := raw.(*schema.Set)
for _, g := range groups {
if g.GroupName != nil {
list.Add(*g.GroupName)
} else {
list.Add(*g.GroupId)
}
2015-02-18 18:07:46 +01:00
}
m["security_groups"] = list
}
}
rules := make([]map[string]interface{}, 0, len(ruleMap))
for _, m := range ruleMap {
rules = append(rules, m)
}
2015-02-18 18:07:46 +01:00
return rules
}
func resourceAwsSecurityGroupUpdateRules(
d *schema.ResourceData, ruleset string,
meta interface{}, group *ec2.SecurityGroup) error {
2015-02-18 18:07:46 +01:00
if d.HasChange(ruleset) {
o, n := d.GetChange(ruleset)
if o == nil {
o = new(schema.Set)
}
if n == nil {
n = new(schema.Set)
}
os := o.(*schema.Set)
2015-05-05 23:42:08 +02:00
ns := n.(*schema.Set)
remove, err := expandIPPerms(group, os.Difference(ns).List())
if err != nil {
return err
}
add, err := expandIPPerms(group, ns.Difference(os).List())
if err != nil {
return err
}
2015-02-18 18:07:46 +01:00
2015-05-05 23:42:08 +02:00
// TODO: We need to handle partial state better in the in-between
2015-02-18 18:07:46 +01:00
// in this update.
// TODO: It'd be nicer to authorize before removing, but then we have
// to deal with complicated unrolling to get individual CIDR blocks
// to avoid authorizing already authorized sources. Removing before
// adding is easier here, and Terraform should be fast enough to
// not have service issues.
if len(remove) > 0 || len(add) > 0 {
2015-04-16 22:05:55 +02:00
conn := meta.(*AWSClient).ec2conn
2015-02-18 18:07:46 +01:00
var err error
2015-02-18 18:07:46 +01:00
if len(remove) > 0 {
log.Printf("[DEBUG] Revoking security group %#v %s rule: %#v",
group, ruleset, remove)
2015-02-18 18:07:46 +01:00
if ruleset == "egress" {
req := &ec2.RevokeSecurityGroupEgressInput{
GroupId: group.GroupId,
IpPermissions: remove,
}
_, err = conn.RevokeSecurityGroupEgress(req)
} else {
req := &ec2.RevokeSecurityGroupIngressInput{
GroupId: group.GroupId,
IpPermissions: remove,
}
if group.VpcId == nil || *group.VpcId == "" {
req.GroupId = nil
req.GroupName = group.GroupName
}
_, err = conn.RevokeSecurityGroupIngress(req)
2015-02-18 18:07:46 +01:00
}
if err != nil {
2015-02-18 18:07:46 +01:00
return fmt.Errorf(
"Error revoking security group %s rules: %s",
2015-02-18 18:07:46 +01:00
ruleset, err)
}
}
if len(add) > 0 {
log.Printf("[DEBUG] Authorizing security group %#v %s rule: %#v",
group, ruleset, add)
2015-02-18 18:07:46 +01:00
// Authorize the new rules
if ruleset == "egress" {
req := &ec2.AuthorizeSecurityGroupEgressInput{
GroupId: group.GroupId,
IpPermissions: add,
}
_, err = conn.AuthorizeSecurityGroupEgress(req)
} else {
req := &ec2.AuthorizeSecurityGroupIngressInput{
GroupId: group.GroupId,
IpPermissions: add,
}
if group.VpcId == nil || *group.VpcId == "" {
req.GroupId = nil
req.GroupName = group.GroupName
}
_, err = conn.AuthorizeSecurityGroupIngress(req)
2015-02-18 18:07:46 +01:00
}
if err != nil {
2015-02-18 18:07:46 +01:00
return fmt.Errorf(
"Error authorizing security group %s rules: %s",
ruleset, err)
}
}
}
}
return nil
}
// SGStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch
// a security group.
func SGStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
req := &ec2.DescribeSecurityGroupsInput{
GroupIds: []*string{aws.String(id)},
}
resp, err := conn.DescribeSecurityGroups(req)
if err != nil {
if ec2err, ok := err.(awserr.Error); ok {
if ec2err.Code() == "InvalidSecurityGroupID.NotFound" ||
ec2err.Code() == "InvalidGroup.NotFound" {
resp = nil
err = nil
}
}
if err != nil {
log.Printf("Error on SGStateRefresh: %s", err)
return nil, "", err
}
}
if resp == nil {
return nil, "", nil
}
group := resp.SecurityGroups[0]
return group, "exists", nil
}
}
// matchRules receives the group id, type of rules, and the local / remote maps
// of rules. We iterate through the local set of rules trying to find a matching
// remote rule, which may be structured differently because of how AWS
// aggregates the rules under the to, from, and type.
//
//
// Matching rules are written to state, with their elements removed from the
// remote set
//
// If no match is found, we'll write the remote rule to state and let the graph
// sort things out
func matchRules(rType string, local []interface{}, remote []map[string]interface{}) []map[string]interface{} {
// For each local ip or security_group, we need to match against the remote
// ruleSet until all ips or security_groups are found
// saves represents the rules that have been identified to be saved to state,
// in the appropriate d.Set("{ingress,egress}") call.
var saves []map[string]interface{}
for _, raw := range local {
l := raw.(map[string]interface{})
var selfVal bool
if v, ok := l["self"]; ok {
selfVal = v.(bool)
}
// matching against self is required to detect rules that only include self
// as the rule. resourceAwsSecurityGroupIPPermGather parses the group out
// and replaces it with self if it's ID is found
localHash := idHash(rType, l["protocol"].(string), int64(l["to_port"].(int)), int64(l["from_port"].(int)), selfVal)
// loop remote rules, looking for a matching hash
for _, r := range remote {
var remoteSelfVal bool
if v, ok := r["self"]; ok {
remoteSelfVal = v.(bool)
}
// hash this remote rule and compare it for a match consideration with the
// local rule we're examining
rHash := idHash(rType, r["protocol"].(string), r["to_port"].(int64), r["from_port"].(int64), remoteSelfVal)
if rHash == localHash {
var numExpectedCidrs, numExpectedPrefixLists, numExpectedSGs, numRemoteCidrs, numRemotePrefixLists, numRemoteSGs int
var matchingCidrs []string
var matchingSGs []string
var matchingPrefixLists []string
// grab the local/remote cidr and sg groups, capturing the expected and
// actual counts
lcRaw, ok := l["cidr_blocks"]
if ok {
numExpectedCidrs = len(l["cidr_blocks"].([]interface{}))
}
lpRaw, ok := l["prefix_list_ids"]
if ok {
numExpectedPrefixLists = len(l["prefix_list_ids"].([]interface{}))
}
lsRaw, ok := l["security_groups"]
if ok {
numExpectedSGs = len(l["security_groups"].(*schema.Set).List())
}
rcRaw, ok := r["cidr_blocks"]
if ok {
numRemoteCidrs = len(r["cidr_blocks"].([]string))
}
rpRaw, ok := r["prefix_list_ids"]
if ok {
numRemotePrefixLists = len(r["prefix_list_ids"].([]string))
}
rsRaw, ok := r["security_groups"]
if ok {
numRemoteSGs = len(r["security_groups"].(*schema.Set).List())
}
// check some early failures
if numExpectedCidrs > numRemoteCidrs {
log.Printf("[DEBUG] Local rule has more CIDR blocks, continuing (%d/%d)", numExpectedCidrs, numRemoteCidrs)
continue
}
if numExpectedPrefixLists > numRemotePrefixLists {
log.Printf("[DEBUG] Local rule has more prefix lists, continuing (%d/%d)", numExpectedPrefixLists, numRemotePrefixLists)
continue
}
if numExpectedSGs > numRemoteSGs {
log.Printf("[DEBUG] Local rule has more Security Groups, continuing (%d/%d)", numExpectedSGs, numRemoteSGs)
continue
}
// match CIDRs by converting both to sets, and using Set methods
var localCidrs []interface{}
if lcRaw != nil {
localCidrs = lcRaw.([]interface{})
}
localCidrSet := schema.NewSet(schema.HashString, localCidrs)
// remote cidrs are presented as a slice of strings, so we need to
// reformat them into a slice of interfaces to be used in creating the
// remote cidr set
var remoteCidrs []string
if rcRaw != nil {
remoteCidrs = rcRaw.([]string)
}
// convert remote cidrs to a set, for easy comparisions
var list []interface{}
for _, s := range remoteCidrs {
list = append(list, s)
}
remoteCidrSet := schema.NewSet(schema.HashString, list)
// Build up a list of local cidrs that are found in the remote set
for _, s := range localCidrSet.List() {
if remoteCidrSet.Contains(s) {
matchingCidrs = append(matchingCidrs, s.(string))
}
}
// match prefix lists by converting both to sets, and using Set methods
var localPrefixLists []interface{}
if lpRaw != nil {
localPrefixLists = lpRaw.([]interface{})
}
localPrefixListsSet := schema.NewSet(schema.HashString, localPrefixLists)
// remote prefix lists are presented as a slice of strings, so we need to
// reformat them into a slice of interfaces to be used in creating the
// remote prefix list set
var remotePrefixLists []string
if rpRaw != nil {
remotePrefixLists = rpRaw.([]string)
}
// convert remote prefix lists to a set, for easy comparison
list = nil
for _, s := range remotePrefixLists {
list = append(list, s)
}
remotePrefixListsSet := schema.NewSet(schema.HashString, list)
// Build up a list of local prefix lists that are found in the remote set
for _, s := range localPrefixListsSet.List() {
if remotePrefixListsSet.Contains(s) {
matchingPrefixLists = append(matchingPrefixLists, s.(string))
}
}
// match SGs. Both local and remote are already sets
var localSGSet *schema.Set
if lsRaw == nil {
localSGSet = schema.NewSet(schema.HashString, nil)
} else {
localSGSet = lsRaw.(*schema.Set)
}
var remoteSGSet *schema.Set
if rsRaw == nil {
remoteSGSet = schema.NewSet(schema.HashString, nil)
} else {
remoteSGSet = rsRaw.(*schema.Set)
}
// Build up a list of local security groups that are found in the remote set
for _, s := range localSGSet.List() {
if remoteSGSet.Contains(s) {
matchingSGs = append(matchingSGs, s.(string))
}
}
// compare equalities for matches.
// If we found the number of cidrs and number of sgs, we declare a
// match, and then remove those elements from the remote rule, so that
// this remote rule can still be considered by other local rules
if numExpectedCidrs == len(matchingCidrs) {
if numExpectedPrefixLists == len(matchingPrefixLists) {
if numExpectedSGs == len(matchingSGs) {
// confirm that self references match
var lSelf bool
var rSelf bool
if _, ok := l["self"]; ok {
lSelf = l["self"].(bool)
}
if _, ok := r["self"]; ok {
rSelf = r["self"].(bool)
}
if rSelf == lSelf {
delete(r, "self")
// pop local cidrs from remote
diffCidr := remoteCidrSet.Difference(localCidrSet)
var newCidr []string
for _, cRaw := range diffCidr.List() {
newCidr = append(newCidr, cRaw.(string))
}
// reassigning
if len(newCidr) > 0 {
r["cidr_blocks"] = newCidr
} else {
delete(r, "cidr_blocks")
}
// pop local prefix lists from remote
diffPrefixLists := remotePrefixListsSet.Difference(localPrefixListsSet)
var newPrefixLists []string
for _, pRaw := range diffPrefixLists.List() {
newPrefixLists = append(newPrefixLists, pRaw.(string))
}
// reassigning
if len(newPrefixLists) > 0 {
r["prefix_list_ids"] = newPrefixLists
} else {
delete(r, "prefix_list_ids")
}
// pop local sgs from remote
diffSGs := remoteSGSet.Difference(localSGSet)
if len(diffSGs.List()) > 0 {
r["security_groups"] = diffSGs
} else {
delete(r, "security_groups")
}
saves = append(saves, l)
}
}
}
}
}
}
}
// Here we catch any remote rules that have not been stripped of all self,
// cidrs, and security groups. We'll add remote rules here that have not been
// matched locally, and let the graph sort things out. This will happen when
// rules are added externally to Terraform
for _, r := range remote {
var lenCidr, lenPrefixLists, lenSGs int
if rCidrs, ok := r["cidr_blocks"]; ok {
lenCidr = len(rCidrs.([]string))
}
if rPrefixLists, ok := r["prefix_list_ids"]; ok {
lenPrefixLists = len(rPrefixLists.([]string))
}
if rawSGs, ok := r["security_groups"]; ok {
lenSGs = len(rawSGs.(*schema.Set).List())
}
if _, ok := r["self"]; ok {
if r["self"].(bool) == true {
lenSGs++
}
}
if lenSGs+lenCidr+lenPrefixLists > 0 {
log.Printf("[DEBUG] Found a remote Rule that wasn't empty: (%#v)", r)
saves = append(saves, r)
}
}
return saves
}
// Creates a unique hash for the type, ports, and protocol, used as a key in
// maps
func idHash(rType, protocol string, toPort, fromPort int64, self bool) string {
var buf bytes.Buffer
buf.WriteString(fmt.Sprintf("%s-", rType))
buf.WriteString(fmt.Sprintf("%d-", toPort))
buf.WriteString(fmt.Sprintf("%d-", fromPort))
buf.WriteString(fmt.Sprintf("%s-", strings.ToLower(protocol)))
buf.WriteString(fmt.Sprintf("%t-", self))
return fmt.Sprintf("rule-%d", hashcode.String(buf.String()))
}
// protocolStateFunc ensures we only store a string in any protocol field
func protocolStateFunc(v interface{}) string {
switch v.(type) {
case string:
p := protocolForValue(v.(string))
return p
default:
log.Printf("[WARN] Non String value given for Protocol: %#v", v)
return ""
}
}
// protocolForValue converts a valid Internet Protocol number into it's name
// representation. If a name is given, it validates that it's a proper protocol
// name. Names/numbers are as defined at
// https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml
func protocolForValue(v string) string {
// special case -1
protocol := strings.ToLower(v)
if protocol == "-1" || protocol == "all" {
return "-1"
}
// if it's a name like tcp, return that
if _, ok := protocolIntegers()[protocol]; ok {
return protocol
}
// convert to int, look for that value
p, err := strconv.Atoi(protocol)
if err != nil {
// we were unable to convert to int, suggesting a string name, but it wasn't
// found above
log.Printf("[WARN] Unable to determine valid protocol: %s", err)
return protocol
}
for k, v := range protocolIntegers() {
if p == v {
// guard against protocolIntegers sometime in the future not having lower
// case ids in the map
return strings.ToLower(k)
}
}
// fall through
log.Printf("[WARN] Unable to determine valid protocol: no matching protocols found")
return protocol
}
// The AWS Lambda service creates ENIs behind the scenes and keeps these around for a while
// which would prevent SGs attached to such ENIs from being destroyed
func deleteLingeringLambdaENIs(conn *ec2.EC2, d *schema.ResourceData) error {
// Here we carefully find the offenders
params := &ec2.DescribeNetworkInterfacesInput{
Filters: []*ec2.Filter{
&ec2.Filter{
Name: aws.String("group-id"),
Values: []*string{aws.String(d.Id())},
},
&ec2.Filter{
Name: aws.String("description"),
Values: []*string{aws.String("AWS Lambda VPC ENI: *")},
},
&ec2.Filter{
Name: aws.String("requester-id"),
Values: []*string{aws.String("*:awslambda_*")},
},
},
}
networkInterfaceResp, err := conn.DescribeNetworkInterfaces(params)
if err != nil {
return err
}
// Then we detach and finally delete those
v := networkInterfaceResp.NetworkInterfaces
for _, eni := range v {
if eni.Attachment != nil {
detachNetworkInterfaceParams := &ec2.DetachNetworkInterfaceInput{
AttachmentId: eni.Attachment.AttachmentId,
}
_, detachNetworkInterfaceErr := conn.DetachNetworkInterface(detachNetworkInterfaceParams)
if detachNetworkInterfaceErr != nil {
return detachNetworkInterfaceErr
}
log.Printf("[DEBUG] Waiting for ENI (%s) to become detached", *eni.NetworkInterfaceId)
stateConf := &resource.StateChangeConf{
Pending: []string{"true"},
Target: []string{"false"},
Refresh: networkInterfaceAttachedRefreshFunc(conn, *eni.NetworkInterfaceId),
Timeout: 10 * time.Minute,
}
if _, err := stateConf.WaitForState(); err != nil {
return fmt.Errorf(
"Error waiting for ENI (%s) to become detached: %s", *eni.NetworkInterfaceId, err)
}
}
deleteNetworkInterfaceParams := &ec2.DeleteNetworkInterfaceInput{
NetworkInterfaceId: eni.NetworkInterfaceId,
}
_, deleteNetworkInterfaceErr := conn.DeleteNetworkInterface(deleteNetworkInterfaceParams)
if deleteNetworkInterfaceErr != nil {
return deleteNetworkInterfaceErr
}
}
return nil
}
func networkInterfaceAttachedRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
describe_network_interfaces_request := &ec2.DescribeNetworkInterfacesInput{
NetworkInterfaceIds: []*string{aws.String(id)},
}
describeResp, err := conn.DescribeNetworkInterfaces(describe_network_interfaces_request)
if err != nil {
log.Printf("[ERROR] Could not find network interface %s. %s", id, err)
return nil, "", err
}
eni := describeResp.NetworkInterfaces[0]
hasAttachment := strconv.FormatBool(eni.Attachment != nil)
log.Printf("[DEBUG] ENI %s has attachment state %s", id, hasAttachment)
return eni, hasAttachment, nil
}
}