github.com/econnell/terraform@v0.5.4-0.20150722160631-78eb236786a4/builtin/providers/aws/resource_aws_security_group_rule.go (about) 1 package aws 2 3 import ( 4 "bytes" 5 "fmt" 6 "log" 7 "sort" 8 "strings" 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/aws/awsutil" 13 "github.com/aws/aws-sdk-go/service/ec2" 14 "github.com/hashicorp/terraform/helper/hashcode" 15 "github.com/hashicorp/terraform/helper/schema" 16 ) 17 18 func resourceAwsSecurityGroupRule() *schema.Resource { 19 return &schema.Resource{ 20 Create: resourceAwsSecurityGroupRuleCreate, 21 Read: resourceAwsSecurityGroupRuleRead, 22 Delete: resourceAwsSecurityGroupRuleDelete, 23 24 SchemaVersion: 1, 25 MigrateState: resourceAwsSecurityGroupRuleMigrateState, 26 27 Schema: map[string]*schema.Schema{ 28 "type": &schema.Schema{ 29 Type: schema.TypeString, 30 Required: true, 31 ForceNew: true, 32 Description: "Type of rule, ingress (inbound) or egress (outbound).", 33 }, 34 35 "from_port": &schema.Schema{ 36 Type: schema.TypeInt, 37 Required: true, 38 ForceNew: true, 39 }, 40 41 "to_port": &schema.Schema{ 42 Type: schema.TypeInt, 43 Required: true, 44 ForceNew: true, 45 }, 46 47 "protocol": &schema.Schema{ 48 Type: schema.TypeString, 49 Required: true, 50 ForceNew: true, 51 }, 52 53 "cidr_blocks": &schema.Schema{ 54 Type: schema.TypeList, 55 Optional: true, 56 ForceNew: true, 57 Elem: &schema.Schema{Type: schema.TypeString}, 58 }, 59 60 "security_group_id": &schema.Schema{ 61 Type: schema.TypeString, 62 Required: true, 63 ForceNew: true, 64 }, 65 66 "source_security_group_id": &schema.Schema{ 67 Type: schema.TypeString, 68 Optional: true, 69 ForceNew: true, 70 Computed: true, 71 ConflictsWith: []string{"cidr_blocks"}, 72 }, 73 74 "self": &schema.Schema{ 75 Type: schema.TypeBool, 76 Optional: true, 77 Default: false, 78 ForceNew: true, 79 }, 80 }, 81 } 82 } 83 84 func resourceAwsSecurityGroupRuleCreate(d *schema.ResourceData, meta interface{}) error { 85 conn := meta.(*AWSClient).ec2conn 86 sg_id := d.Get("security_group_id").(string) 87 sg, err := findResourceSecurityGroup(conn, sg_id) 88 89 if err != nil { 90 return err 91 } 92 93 perm := expandIPPerm(d, sg) 94 95 ruleType := d.Get("type").(string) 96 97 var autherr error 98 switch ruleType { 99 case "ingress": 100 log.Printf("[DEBUG] Authorizing security group %s %s rule: %s", 101 sg_id, "Ingress", awsutil.StringValue(perm)) 102 103 req := &ec2.AuthorizeSecurityGroupIngressInput{ 104 GroupID: sg.GroupID, 105 IPPermissions: []*ec2.IPPermission{perm}, 106 } 107 108 if sg.VPCID == nil || *sg.VPCID == "" { 109 req.GroupID = nil 110 req.GroupName = sg.GroupName 111 } 112 113 _, autherr = conn.AuthorizeSecurityGroupIngress(req) 114 115 case "egress": 116 log.Printf("[DEBUG] Authorizing security group %s %s rule: %#v", 117 sg_id, "Egress", perm) 118 119 req := &ec2.AuthorizeSecurityGroupEgressInput{ 120 GroupID: sg.GroupID, 121 IPPermissions: []*ec2.IPPermission{perm}, 122 } 123 124 _, autherr = conn.AuthorizeSecurityGroupEgress(req) 125 126 default: 127 return fmt.Errorf("Security Group Rule must be type 'ingress' or type 'egress'") 128 } 129 130 if autherr != nil { 131 if awsErr, ok := autherr.(awserr.Error); ok { 132 if awsErr.Code() == "InvalidPermission.Duplicate" { 133 return fmt.Errorf(`[WARN] A duplicate Security Group rule was found. This may be 134 a side effect of a now-fixed Terraform issue causing two security groups with 135 identical attributes but different source_security_group_ids to overwrite each 136 other in the state. See https://github.com/hashicorp/terraform/pull/2376 for more 137 information and instructions for recovery. Error message: %s`, awsErr.Message()) 138 } 139 } 140 141 return fmt.Errorf( 142 "Error authorizing security group rule type %s: %s", 143 ruleType, autherr) 144 } 145 146 d.SetId(ipPermissionIDHash(ruleType, perm)) 147 148 return resourceAwsSecurityGroupRuleRead(d, meta) 149 } 150 151 func resourceAwsSecurityGroupRuleRead(d *schema.ResourceData, meta interface{}) error { 152 conn := meta.(*AWSClient).ec2conn 153 sg_id := d.Get("security_group_id").(string) 154 sg, err := findResourceSecurityGroup(conn, sg_id) 155 if err != nil { 156 d.SetId("") 157 } 158 159 var rule *ec2.IPPermission 160 ruleType := d.Get("type").(string) 161 var rl []*ec2.IPPermission 162 switch ruleType { 163 case "ingress": 164 rl = sg.IPPermissions 165 default: 166 rl = sg.IPPermissionsEgress 167 } 168 169 for _, r := range rl { 170 if d.Id() == ipPermissionIDHash(ruleType, r) { 171 rule = r 172 } 173 } 174 175 if rule == nil { 176 log.Printf("[DEBUG] Unable to find matching %s Security Group Rule for Group %s", 177 ruleType, sg_id) 178 d.SetId("") 179 return nil 180 } 181 182 d.Set("from_port", rule.FromPort) 183 d.Set("to_port", rule.ToPort) 184 d.Set("protocol", rule.IPProtocol) 185 d.Set("type", ruleType) 186 187 var cb []string 188 for _, c := range rule.IPRanges { 189 cb = append(cb, *c.CIDRIP) 190 } 191 192 d.Set("cidr_blocks", cb) 193 194 if len(rule.UserIDGroupPairs) > 0 { 195 s := rule.UserIDGroupPairs[0] 196 d.Set("source_security_group_id", *s.GroupID) 197 } 198 199 return nil 200 } 201 202 func resourceAwsSecurityGroupRuleDelete(d *schema.ResourceData, meta interface{}) error { 203 conn := meta.(*AWSClient).ec2conn 204 sg_id := d.Get("security_group_id").(string) 205 sg, err := findResourceSecurityGroup(conn, sg_id) 206 207 if err != nil { 208 return err 209 } 210 211 perm := expandIPPerm(d, sg) 212 ruleType := d.Get("type").(string) 213 switch ruleType { 214 case "ingress": 215 log.Printf("[DEBUG] Revoking rule (%s) from security group %s:\n%s", 216 "ingress", sg_id, awsutil.StringValue(perm)) 217 req := &ec2.RevokeSecurityGroupIngressInput{ 218 GroupID: sg.GroupID, 219 IPPermissions: []*ec2.IPPermission{perm}, 220 } 221 222 _, err = conn.RevokeSecurityGroupIngress(req) 223 224 if err != nil { 225 return fmt.Errorf( 226 "Error revoking security group %s rules: %s", 227 sg_id, err) 228 } 229 case "egress": 230 231 log.Printf("[DEBUG] Revoking security group %#v %s rule: %#v", 232 sg_id, "egress", perm) 233 req := &ec2.RevokeSecurityGroupEgressInput{ 234 GroupID: sg.GroupID, 235 IPPermissions: []*ec2.IPPermission{perm}, 236 } 237 238 _, err = conn.RevokeSecurityGroupEgress(req) 239 240 if err != nil { 241 return fmt.Errorf( 242 "Error revoking security group %s rules: %s", 243 sg_id, err) 244 } 245 } 246 247 d.SetId("") 248 249 return nil 250 } 251 252 func findResourceSecurityGroup(conn *ec2.EC2, id string) (*ec2.SecurityGroup, error) { 253 req := &ec2.DescribeSecurityGroupsInput{ 254 GroupIDs: []*string{aws.String(id)}, 255 } 256 resp, err := conn.DescribeSecurityGroups(req) 257 if err != nil { 258 return nil, err 259 } 260 if resp == nil || len(resp.SecurityGroups) != 1 || resp.SecurityGroups[0] == nil { 261 return nil, fmt.Errorf( 262 "Expected to find one security group with ID %q, got: %#v", 263 id, resp.SecurityGroups) 264 } 265 266 return resp.SecurityGroups[0], nil 267 } 268 269 // ByGroupPair implements sort.Interface for []*ec2.UserIDGroupPairs based on 270 // GroupID or GroupName field (only one should be set). 271 type ByGroupPair []*ec2.UserIDGroupPair 272 273 func (b ByGroupPair) Len() int { return len(b) } 274 func (b ByGroupPair) Swap(i, j int) { b[i], b[j] = b[j], b[i] } 275 func (b ByGroupPair) Less(i, j int) bool { 276 if b[i].GroupID != nil && b[j].GroupID != nil { 277 return *b[i].GroupID < *b[j].GroupID 278 } 279 if b[i].GroupName != nil && b[j].GroupName != nil { 280 return *b[i].GroupName < *b[j].GroupName 281 } 282 283 panic("mismatched security group rules, may be a terraform bug") 284 } 285 286 func ipPermissionIDHash(ruleType string, ip *ec2.IPPermission) string { 287 var buf bytes.Buffer 288 if ip.FromPort != nil && *ip.FromPort > 0 { 289 buf.WriteString(fmt.Sprintf("%d-", *ip.FromPort)) 290 } 291 if ip.ToPort != nil && *ip.ToPort > 0 { 292 buf.WriteString(fmt.Sprintf("%d-", *ip.ToPort)) 293 } 294 buf.WriteString(fmt.Sprintf("%s-", *ip.IPProtocol)) 295 buf.WriteString(fmt.Sprintf("%s-", ruleType)) 296 297 // We need to make sure to sort the strings below so that we always 298 // generate the same hash code no matter what is in the set. 299 if len(ip.IPRanges) > 0 { 300 s := make([]string, len(ip.IPRanges)) 301 for i, r := range ip.IPRanges { 302 s[i] = *r.CIDRIP 303 } 304 sort.Strings(s) 305 306 for _, v := range s { 307 buf.WriteString(fmt.Sprintf("%s-", v)) 308 } 309 } 310 311 if len(ip.UserIDGroupPairs) > 0 { 312 sort.Sort(ByGroupPair(ip.UserIDGroupPairs)) 313 for _, pair := range ip.UserIDGroupPairs { 314 if pair.GroupID != nil { 315 buf.WriteString(fmt.Sprintf("%s-", *pair.GroupID)) 316 } else { 317 buf.WriteString("-") 318 } 319 if pair.GroupName != nil { 320 buf.WriteString(fmt.Sprintf("%s-", *pair.GroupName)) 321 } else { 322 buf.WriteString("-") 323 } 324 } 325 } 326 327 return fmt.Sprintf("sg-%d", hashcode.String(buf.String())) 328 } 329 330 func expandIPPerm(d *schema.ResourceData, sg *ec2.SecurityGroup) *ec2.IPPermission { 331 var perm ec2.IPPermission 332 333 perm.FromPort = aws.Long(int64(d.Get("from_port").(int))) 334 perm.ToPort = aws.Long(int64(d.Get("to_port").(int))) 335 perm.IPProtocol = aws.String(d.Get("protocol").(string)) 336 337 // build a group map that behaves like a set 338 groups := make(map[string]bool) 339 if raw, ok := d.GetOk("source_security_group_id"); ok { 340 groups[raw.(string)] = true 341 } 342 343 if v, ok := d.GetOk("self"); ok && v.(bool) { 344 if sg.VPCID != nil && *sg.VPCID != "" { 345 groups[*sg.GroupID] = true 346 } else { 347 groups[*sg.GroupName] = true 348 } 349 } 350 351 if len(groups) > 0 { 352 perm.UserIDGroupPairs = make([]*ec2.UserIDGroupPair, len(groups)) 353 // build string list of group name/ids 354 var gl []string 355 for k, _ := range groups { 356 gl = append(gl, k) 357 } 358 359 for i, name := range gl { 360 ownerId, id := "", name 361 if items := strings.Split(id, "/"); len(items) > 1 { 362 ownerId, id = items[0], items[1] 363 } 364 365 perm.UserIDGroupPairs[i] = &ec2.UserIDGroupPair{ 366 GroupID: aws.String(id), 367 UserID: aws.String(ownerId), 368 } 369 370 if sg.VPCID == nil || *sg.VPCID == "" { 371 perm.UserIDGroupPairs[i].GroupID = nil 372 perm.UserIDGroupPairs[i].GroupName = aws.String(id) 373 perm.UserIDGroupPairs[i].UserID = nil 374 } 375 } 376 } 377 378 if raw, ok := d.GetOk("cidr_blocks"); ok { 379 list := raw.([]interface{}) 380 perm.IPRanges = make([]*ec2.IPRange, len(list)) 381 for i, v := range list { 382 perm.IPRanges[i] = &ec2.IPRange{CIDRIP: aws.String(v.(string))} 383 } 384 } 385 386 return &perm 387 }