github.com/jdextraze/terraform@v0.6.17-0.20160511153921-e33847c8a8af/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 "time" 10 11 "github.com/aws/aws-sdk-go/aws" 12 "github.com/aws/aws-sdk-go/aws/awserr" 13 "github.com/aws/aws-sdk-go/service/ec2" 14 "github.com/hashicorp/terraform/helper/hashcode" 15 "github.com/hashicorp/terraform/helper/resource" 16 "github.com/hashicorp/terraform/helper/schema" 17 ) 18 19 func resourceAwsSecurityGroupRule() *schema.Resource { 20 return &schema.Resource{ 21 Create: resourceAwsSecurityGroupRuleCreate, 22 Read: resourceAwsSecurityGroupRuleRead, 23 Delete: resourceAwsSecurityGroupRuleDelete, 24 25 SchemaVersion: 2, 26 MigrateState: resourceAwsSecurityGroupRuleMigrateState, 27 28 Schema: map[string]*schema.Schema{ 29 "type": &schema.Schema{ 30 Type: schema.TypeString, 31 Required: true, 32 ForceNew: true, 33 Description: "Type of rule, ingress (inbound) or egress (outbound).", 34 }, 35 36 "from_port": &schema.Schema{ 37 Type: schema.TypeInt, 38 Required: true, 39 ForceNew: true, 40 }, 41 42 "to_port": &schema.Schema{ 43 Type: schema.TypeInt, 44 Required: true, 45 ForceNew: true, 46 }, 47 48 "protocol": &schema.Schema{ 49 Type: schema.TypeString, 50 Required: true, 51 ForceNew: true, 52 StateFunc: protocolStateFunc, 53 }, 54 55 "cidr_blocks": &schema.Schema{ 56 Type: schema.TypeList, 57 Optional: true, 58 ForceNew: true, 59 Elem: &schema.Schema{Type: schema.TypeString}, 60 }, 61 62 "security_group_id": &schema.Schema{ 63 Type: schema.TypeString, 64 Required: true, 65 ForceNew: true, 66 }, 67 68 "source_security_group_id": &schema.Schema{ 69 Type: schema.TypeString, 70 Optional: true, 71 ForceNew: true, 72 Computed: true, 73 ConflictsWith: []string{"cidr_blocks", "self"}, 74 }, 75 76 "self": &schema.Schema{ 77 Type: schema.TypeBool, 78 Optional: true, 79 Default: false, 80 ForceNew: true, 81 ConflictsWith: []string{"cidr_blocks"}, 82 }, 83 }, 84 } 85 } 86 87 func resourceAwsSecurityGroupRuleCreate(d *schema.ResourceData, meta interface{}) error { 88 conn := meta.(*AWSClient).ec2conn 89 sg_id := d.Get("security_group_id").(string) 90 91 awsMutexKV.Lock(sg_id) 92 defer awsMutexKV.Unlock(sg_id) 93 94 sg, err := findResourceSecurityGroup(conn, sg_id) 95 if err != nil { 96 return err 97 } 98 99 perm, err := expandIPPerm(d, sg) 100 if err != nil { 101 return err 102 } 103 104 ruleType := d.Get("type").(string) 105 isVPC := sg.VpcId != nil && *sg.VpcId != "" 106 107 var autherr error 108 switch ruleType { 109 case "ingress": 110 log.Printf("[DEBUG] Authorizing security group %s %s rule: %s", 111 sg_id, "Ingress", perm) 112 113 req := &ec2.AuthorizeSecurityGroupIngressInput{ 114 GroupId: sg.GroupId, 115 IpPermissions: []*ec2.IpPermission{perm}, 116 } 117 118 if !isVPC { 119 req.GroupId = nil 120 req.GroupName = sg.GroupName 121 } 122 123 _, autherr = conn.AuthorizeSecurityGroupIngress(req) 124 125 case "egress": 126 log.Printf("[DEBUG] Authorizing security group %s %s rule: %#v", 127 sg_id, "Egress", perm) 128 129 req := &ec2.AuthorizeSecurityGroupEgressInput{ 130 GroupId: sg.GroupId, 131 IpPermissions: []*ec2.IpPermission{perm}, 132 } 133 134 _, autherr = conn.AuthorizeSecurityGroupEgress(req) 135 136 default: 137 return fmt.Errorf("Security Group Rule must be type 'ingress' or type 'egress'") 138 } 139 140 if autherr != nil { 141 if awsErr, ok := autherr.(awserr.Error); ok { 142 if awsErr.Code() == "InvalidPermission.Duplicate" { 143 return fmt.Errorf(`[WARN] A duplicate Security Group rule was found on (%s). This may be 144 a side effect of a now-fixed Terraform issue causing two security groups with 145 identical attributes but different source_security_group_ids to overwrite each 146 other in the state. See https://github.com/hashicorp/terraform/pull/2376 for more 147 information and instructions for recovery. Error message: %s`, sg_id, awsErr.Message()) 148 } 149 } 150 151 return fmt.Errorf( 152 "Error authorizing security group rule type %s: %s", 153 ruleType, autherr) 154 } 155 156 id := ipPermissionIDHash(sg_id, ruleType, perm) 157 log.Printf("[DEBUG] Computed group rule ID %s", id) 158 159 retErr := resource.Retry(5*time.Minute, func() *resource.RetryError { 160 sg, err := findResourceSecurityGroup(conn, sg_id) 161 162 if err != nil { 163 log.Printf("[DEBUG] Error finding Secuirty Group (%s) for Rule (%s): %s", sg_id, id, err) 164 return resource.NonRetryableError(err) 165 } 166 167 var rules []*ec2.IpPermission 168 switch ruleType { 169 case "ingress": 170 rules = sg.IpPermissions 171 default: 172 rules = sg.IpPermissionsEgress 173 } 174 175 rule := findRuleMatch(perm, rules, isVPC) 176 177 if rule == nil { 178 log.Printf("[DEBUG] Unable to find matching %s Security Group Rule (%s) for Group %s", 179 ruleType, id, sg_id) 180 return resource.RetryableError(fmt.Errorf("No match found")) 181 } 182 183 log.Printf("[DEBUG] Found rule for Security Group Rule (%s): %s", id, rule) 184 return nil 185 }) 186 187 if retErr != nil { 188 log.Printf("[DEBUG] Error finding matching %s Security Group Rule (%s) for Group %s -- NO STATE WILL BE SAVED", 189 ruleType, id, sg_id) 190 return nil 191 } 192 193 d.SetId(id) 194 return nil 195 } 196 197 func resourceAwsSecurityGroupRuleRead(d *schema.ResourceData, meta interface{}) error { 198 conn := meta.(*AWSClient).ec2conn 199 sg_id := d.Get("security_group_id").(string) 200 sg, err := findResourceSecurityGroup(conn, sg_id) 201 if err != nil { 202 log.Printf("[DEBUG] Error finding Secuirty Group (%s) for Rule (%s): %s", sg_id, d.Id(), err) 203 d.SetId("") 204 return nil 205 } 206 207 isVPC := sg.VpcId != nil && *sg.VpcId != "" 208 209 var rule *ec2.IpPermission 210 var rules []*ec2.IpPermission 211 ruleType := d.Get("type").(string) 212 switch ruleType { 213 case "ingress": 214 rules = sg.IpPermissions 215 default: 216 rules = sg.IpPermissionsEgress 217 } 218 219 p, err := expandIPPerm(d, sg) 220 if err != nil { 221 return err 222 } 223 224 if len(rules) == 0 { 225 log.Printf("[WARN] No %s rules were found for Security Group (%s) looking for Security Group Rule (%s)", 226 ruleType, *sg.GroupName, d.Id()) 227 d.SetId("") 228 return nil 229 } 230 231 rule = findRuleMatch(p, rules, isVPC) 232 233 if rule == nil { 234 log.Printf("[DEBUG] Unable to find matching %s Security Group Rule (%s) for Group %s", 235 ruleType, d.Id(), sg_id) 236 d.SetId("") 237 return nil 238 } 239 240 log.Printf("[DEBUG] Found rule for Security Group Rule (%s): %s", d.Id(), rule) 241 242 d.Set("from_port", rule.FromPort) 243 d.Set("to_port", rule.ToPort) 244 d.Set("protocol", rule.IpProtocol) 245 d.Set("type", ruleType) 246 247 var cb []string 248 for _, c := range p.IpRanges { 249 cb = append(cb, *c.CidrIp) 250 } 251 252 d.Set("cidr_blocks", cb) 253 254 if len(p.UserIdGroupPairs) > 0 { 255 s := p.UserIdGroupPairs[0] 256 if isVPC { 257 d.Set("source_security_group_id", *s.GroupId) 258 } else { 259 d.Set("source_security_group_id", *s.GroupName) 260 } 261 } 262 263 return nil 264 } 265 266 func resourceAwsSecurityGroupRuleDelete(d *schema.ResourceData, meta interface{}) error { 267 conn := meta.(*AWSClient).ec2conn 268 sg_id := d.Get("security_group_id").(string) 269 270 awsMutexKV.Lock(sg_id) 271 defer awsMutexKV.Unlock(sg_id) 272 273 sg, err := findResourceSecurityGroup(conn, sg_id) 274 if err != nil { 275 return err 276 } 277 278 perm, err := expandIPPerm(d, sg) 279 if err != nil { 280 return err 281 } 282 ruleType := d.Get("type").(string) 283 switch ruleType { 284 case "ingress": 285 log.Printf("[DEBUG] Revoking rule (%s) from security group %s:\n%s", 286 "ingress", sg_id, perm) 287 req := &ec2.RevokeSecurityGroupIngressInput{ 288 GroupId: sg.GroupId, 289 IpPermissions: []*ec2.IpPermission{perm}, 290 } 291 292 _, err = conn.RevokeSecurityGroupIngress(req) 293 294 if err != nil { 295 return fmt.Errorf( 296 "Error revoking security group %s rules: %s", 297 sg_id, err) 298 } 299 case "egress": 300 301 log.Printf("[DEBUG] Revoking security group %#v %s rule: %#v", 302 sg_id, "egress", perm) 303 req := &ec2.RevokeSecurityGroupEgressInput{ 304 GroupId: sg.GroupId, 305 IpPermissions: []*ec2.IpPermission{perm}, 306 } 307 308 _, err = conn.RevokeSecurityGroupEgress(req) 309 310 if err != nil { 311 return fmt.Errorf( 312 "Error revoking security group %s rules: %s", 313 sg_id, err) 314 } 315 } 316 317 d.SetId("") 318 319 return nil 320 } 321 322 func findResourceSecurityGroup(conn *ec2.EC2, id string) (*ec2.SecurityGroup, error) { 323 req := &ec2.DescribeSecurityGroupsInput{ 324 GroupIds: []*string{aws.String(id)}, 325 } 326 resp, err := conn.DescribeSecurityGroups(req) 327 if err != nil { 328 return nil, err 329 } 330 331 if resp == nil || len(resp.SecurityGroups) != 1 || resp.SecurityGroups[0] == nil { 332 return nil, fmt.Errorf( 333 "Expected to find one security group with ID %q, got: %#v", 334 id, resp.SecurityGroups) 335 } 336 337 return resp.SecurityGroups[0], nil 338 } 339 340 // ByGroupPair implements sort.Interface for []*ec2.UserIDGroupPairs based on 341 // GroupID or GroupName field (only one should be set). 342 type ByGroupPair []*ec2.UserIdGroupPair 343 344 func (b ByGroupPair) Len() int { return len(b) } 345 func (b ByGroupPair) Swap(i, j int) { b[i], b[j] = b[j], b[i] } 346 func (b ByGroupPair) Less(i, j int) bool { 347 if b[i].GroupId != nil && b[j].GroupId != nil { 348 return *b[i].GroupId < *b[j].GroupId 349 } 350 if b[i].GroupName != nil && b[j].GroupName != nil { 351 return *b[i].GroupName < *b[j].GroupName 352 } 353 354 panic("mismatched security group rules, may be a terraform bug") 355 } 356 357 func findRuleMatch(p *ec2.IpPermission, rules []*ec2.IpPermission, isVPC bool) *ec2.IpPermission { 358 var rule *ec2.IpPermission 359 for _, r := range rules { 360 if r.ToPort != nil && *p.ToPort != *r.ToPort { 361 continue 362 } 363 364 if r.FromPort != nil && *p.FromPort != *r.FromPort { 365 continue 366 } 367 368 if r.IpProtocol != nil && *p.IpProtocol != *r.IpProtocol { 369 continue 370 } 371 372 remaining := len(p.IpRanges) 373 for _, ip := range p.IpRanges { 374 for _, rip := range r.IpRanges { 375 if *ip.CidrIp == *rip.CidrIp { 376 remaining-- 377 } 378 } 379 } 380 381 if remaining > 0 { 382 continue 383 } 384 385 remaining = len(p.UserIdGroupPairs) 386 for _, ip := range p.UserIdGroupPairs { 387 for _, rip := range r.UserIdGroupPairs { 388 if isVPC { 389 if *ip.GroupId == *rip.GroupId { 390 remaining-- 391 } 392 } else { 393 if *ip.GroupName == *rip.GroupName { 394 remaining-- 395 } 396 } 397 } 398 } 399 400 if remaining > 0 { 401 continue 402 } 403 404 rule = r 405 } 406 return rule 407 } 408 409 func ipPermissionIDHash(sg_id, ruleType string, ip *ec2.IpPermission) string { 410 var buf bytes.Buffer 411 buf.WriteString(fmt.Sprintf("%s-", sg_id)) 412 if ip.FromPort != nil && *ip.FromPort > 0 { 413 buf.WriteString(fmt.Sprintf("%d-", *ip.FromPort)) 414 } 415 if ip.ToPort != nil && *ip.ToPort > 0 { 416 buf.WriteString(fmt.Sprintf("%d-", *ip.ToPort)) 417 } 418 buf.WriteString(fmt.Sprintf("%s-", *ip.IpProtocol)) 419 buf.WriteString(fmt.Sprintf("%s-", ruleType)) 420 421 // We need to make sure to sort the strings below so that we always 422 // generate the same hash code no matter what is in the set. 423 if len(ip.IpRanges) > 0 { 424 s := make([]string, len(ip.IpRanges)) 425 for i, r := range ip.IpRanges { 426 s[i] = *r.CidrIp 427 } 428 sort.Strings(s) 429 430 for _, v := range s { 431 buf.WriteString(fmt.Sprintf("%s-", v)) 432 } 433 } 434 435 if len(ip.UserIdGroupPairs) > 0 { 436 sort.Sort(ByGroupPair(ip.UserIdGroupPairs)) 437 for _, pair := range ip.UserIdGroupPairs { 438 if pair.GroupId != nil { 439 buf.WriteString(fmt.Sprintf("%s-", *pair.GroupId)) 440 } else { 441 buf.WriteString("-") 442 } 443 if pair.GroupName != nil { 444 buf.WriteString(fmt.Sprintf("%s-", *pair.GroupName)) 445 } else { 446 buf.WriteString("-") 447 } 448 } 449 } 450 451 return fmt.Sprintf("sgrule-%d", hashcode.String(buf.String())) 452 } 453 454 func expandIPPerm(d *schema.ResourceData, sg *ec2.SecurityGroup) (*ec2.IpPermission, error) { 455 var perm ec2.IpPermission 456 457 perm.FromPort = aws.Int64(int64(d.Get("from_port").(int))) 458 perm.ToPort = aws.Int64(int64(d.Get("to_port").(int))) 459 protocol := protocolForValue(d.Get("protocol").(string)) 460 perm.IpProtocol = aws.String(protocol) 461 462 // build a group map that behaves like a set 463 groups := make(map[string]bool) 464 if raw, ok := d.GetOk("source_security_group_id"); ok { 465 groups[raw.(string)] = true 466 } 467 468 if v, ok := d.GetOk("self"); ok && v.(bool) { 469 // if sg.GroupId != nil { 470 if sg.VpcId != nil && *sg.VpcId != "" { 471 groups[*sg.GroupId] = true 472 } else { 473 groups[*sg.GroupName] = true 474 } 475 } 476 477 if len(groups) > 0 { 478 perm.UserIdGroupPairs = make([]*ec2.UserIdGroupPair, len(groups)) 479 // build string list of group name/ids 480 var gl []string 481 for k, _ := range groups { 482 gl = append(gl, k) 483 } 484 485 for i, name := range gl { 486 ownerId, id := "", name 487 if items := strings.Split(id, "/"); len(items) > 1 { 488 ownerId, id = items[0], items[1] 489 } 490 491 perm.UserIdGroupPairs[i] = &ec2.UserIdGroupPair{ 492 GroupId: aws.String(id), 493 UserId: aws.String(ownerId), 494 } 495 496 if sg.VpcId == nil || *sg.VpcId == "" { 497 perm.UserIdGroupPairs[i].GroupId = nil 498 perm.UserIdGroupPairs[i].GroupName = aws.String(id) 499 perm.UserIdGroupPairs[i].UserId = nil 500 } 501 } 502 } 503 504 if raw, ok := d.GetOk("cidr_blocks"); ok { 505 list := raw.([]interface{}) 506 perm.IpRanges = make([]*ec2.IpRange, len(list)) 507 for i, v := range list { 508 cidrIP, ok := v.(string) 509 if !ok { 510 return nil, fmt.Errorf("empty element found in cidr_blocks - consider using the compact function") 511 } 512 perm.IpRanges[i] = &ec2.IpRange{CidrIp: aws.String(cidrIP)} 513 } 514 } 515 516 return &perm, nil 517 }