github.com/gabrielperezs/terraform@v0.7.0-rc2.0.20160715084931-f7da2612946f/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 "prefix_list_ids": &schema.Schema{ 63 Type: schema.TypeList, 64 Optional: true, 65 ForceNew: true, 66 Elem: &schema.Schema{Type: schema.TypeString}, 67 }, 68 69 "security_group_id": &schema.Schema{ 70 Type: schema.TypeString, 71 Required: true, 72 ForceNew: true, 73 }, 74 75 "source_security_group_id": &schema.Schema{ 76 Type: schema.TypeString, 77 Optional: true, 78 ForceNew: true, 79 Computed: true, 80 ConflictsWith: []string{"cidr_blocks", "self"}, 81 }, 82 83 "self": &schema.Schema{ 84 Type: schema.TypeBool, 85 Optional: true, 86 Default: false, 87 ForceNew: true, 88 ConflictsWith: []string{"cidr_blocks"}, 89 }, 90 }, 91 } 92 } 93 94 func resourceAwsSecurityGroupRuleCreate(d *schema.ResourceData, meta interface{}) error { 95 conn := meta.(*AWSClient).ec2conn 96 sg_id := d.Get("security_group_id").(string) 97 98 awsMutexKV.Lock(sg_id) 99 defer awsMutexKV.Unlock(sg_id) 100 101 sg, err := findResourceSecurityGroup(conn, sg_id) 102 if err != nil { 103 return err 104 } 105 106 perm, err := expandIPPerm(d, sg) 107 if err != nil { 108 return err 109 } 110 111 ruleType := d.Get("type").(string) 112 isVPC := sg.VpcId != nil && *sg.VpcId != "" 113 114 var autherr error 115 switch ruleType { 116 case "ingress": 117 log.Printf("[DEBUG] Authorizing security group %s %s rule: %s", 118 sg_id, "Ingress", perm) 119 120 req := &ec2.AuthorizeSecurityGroupIngressInput{ 121 GroupId: sg.GroupId, 122 IpPermissions: []*ec2.IpPermission{perm}, 123 } 124 125 if !isVPC { 126 req.GroupId = nil 127 req.GroupName = sg.GroupName 128 } 129 130 _, autherr = conn.AuthorizeSecurityGroupIngress(req) 131 132 case "egress": 133 log.Printf("[DEBUG] Authorizing security group %s %s rule: %#v", 134 sg_id, "Egress", perm) 135 136 req := &ec2.AuthorizeSecurityGroupEgressInput{ 137 GroupId: sg.GroupId, 138 IpPermissions: []*ec2.IpPermission{perm}, 139 } 140 141 _, autherr = conn.AuthorizeSecurityGroupEgress(req) 142 143 default: 144 return fmt.Errorf("Security Group Rule must be type 'ingress' or type 'egress'") 145 } 146 147 if autherr != nil { 148 if awsErr, ok := autherr.(awserr.Error); ok { 149 if awsErr.Code() == "InvalidPermission.Duplicate" { 150 return fmt.Errorf(`[WARN] A duplicate Security Group rule was found on (%s). This may be 151 a side effect of a now-fixed Terraform issue causing two security groups with 152 identical attributes but different source_security_group_ids to overwrite each 153 other in the state. See https://github.com/hashicorp/terraform/pull/2376 for more 154 information and instructions for recovery. Error message: %s`, sg_id, awsErr.Message()) 155 } 156 } 157 158 return fmt.Errorf( 159 "Error authorizing security group rule type %s: %s", 160 ruleType, autherr) 161 } 162 163 id := ipPermissionIDHash(sg_id, ruleType, perm) 164 log.Printf("[DEBUG] Computed group rule ID %s", id) 165 166 retErr := resource.Retry(5*time.Minute, func() *resource.RetryError { 167 sg, err := findResourceSecurityGroup(conn, sg_id) 168 169 if err != nil { 170 log.Printf("[DEBUG] Error finding Secuirty Group (%s) for Rule (%s): %s", sg_id, id, err) 171 return resource.NonRetryableError(err) 172 } 173 174 var rules []*ec2.IpPermission 175 switch ruleType { 176 case "ingress": 177 rules = sg.IpPermissions 178 default: 179 rules = sg.IpPermissionsEgress 180 } 181 182 rule := findRuleMatch(perm, rules, isVPC) 183 184 if rule == nil { 185 log.Printf("[DEBUG] Unable to find matching %s Security Group Rule (%s) for Group %s", 186 ruleType, id, sg_id) 187 return resource.RetryableError(fmt.Errorf("No match found")) 188 } 189 190 log.Printf("[DEBUG] Found rule for Security Group Rule (%s): %s", id, rule) 191 return nil 192 }) 193 194 if retErr != nil { 195 log.Printf("[DEBUG] Error finding matching %s Security Group Rule (%s) for Group %s -- NO STATE WILL BE SAVED", 196 ruleType, id, sg_id) 197 return nil 198 } 199 200 d.SetId(id) 201 return nil 202 } 203 204 func resourceAwsSecurityGroupRuleRead(d *schema.ResourceData, meta interface{}) error { 205 conn := meta.(*AWSClient).ec2conn 206 sg_id := d.Get("security_group_id").(string) 207 sg, err := findResourceSecurityGroup(conn, sg_id) 208 if _, notFound := err.(securityGroupNotFound); notFound { 209 // The security group containing this rule no longer exists. 210 d.SetId("") 211 return nil 212 } 213 if err != nil { 214 return fmt.Errorf("Error finding security group (%s) for rule (%s): %s", sg_id, d.Id(), err) 215 } 216 217 isVPC := sg.VpcId != nil && *sg.VpcId != "" 218 219 var rule *ec2.IpPermission 220 var rules []*ec2.IpPermission 221 ruleType := d.Get("type").(string) 222 switch ruleType { 223 case "ingress": 224 rules = sg.IpPermissions 225 default: 226 rules = sg.IpPermissionsEgress 227 } 228 229 p, err := expandIPPerm(d, sg) 230 if err != nil { 231 return err 232 } 233 234 if len(rules) == 0 { 235 log.Printf("[WARN] No %s rules were found for Security Group (%s) looking for Security Group Rule (%s)", 236 ruleType, *sg.GroupName, d.Id()) 237 d.SetId("") 238 return nil 239 } 240 241 rule = findRuleMatch(p, rules, isVPC) 242 243 if rule == nil { 244 log.Printf("[DEBUG] Unable to find matching %s Security Group Rule (%s) for Group %s", 245 ruleType, d.Id(), sg_id) 246 d.SetId("") 247 return nil 248 } 249 250 log.Printf("[DEBUG] Found rule for Security Group Rule (%s): %s", d.Id(), rule) 251 252 d.Set("type", ruleType) 253 setFromIPPerm(d, sg, p) 254 return nil 255 } 256 257 func resourceAwsSecurityGroupRuleDelete(d *schema.ResourceData, meta interface{}) error { 258 conn := meta.(*AWSClient).ec2conn 259 sg_id := d.Get("security_group_id").(string) 260 261 awsMutexKV.Lock(sg_id) 262 defer awsMutexKV.Unlock(sg_id) 263 264 sg, err := findResourceSecurityGroup(conn, sg_id) 265 if err != nil { 266 return err 267 } 268 269 perm, err := expandIPPerm(d, sg) 270 if err != nil { 271 return err 272 } 273 ruleType := d.Get("type").(string) 274 switch ruleType { 275 case "ingress": 276 log.Printf("[DEBUG] Revoking rule (%s) from security group %s:\n%s", 277 "ingress", sg_id, perm) 278 req := &ec2.RevokeSecurityGroupIngressInput{ 279 GroupId: sg.GroupId, 280 IpPermissions: []*ec2.IpPermission{perm}, 281 } 282 283 _, err = conn.RevokeSecurityGroupIngress(req) 284 285 if err != nil { 286 return fmt.Errorf( 287 "Error revoking security group %s rules: %s", 288 sg_id, err) 289 } 290 case "egress": 291 292 log.Printf("[DEBUG] Revoking security group %#v %s rule: %#v", 293 sg_id, "egress", perm) 294 req := &ec2.RevokeSecurityGroupEgressInput{ 295 GroupId: sg.GroupId, 296 IpPermissions: []*ec2.IpPermission{perm}, 297 } 298 299 _, err = conn.RevokeSecurityGroupEgress(req) 300 301 if err != nil { 302 return fmt.Errorf( 303 "Error revoking security group %s rules: %s", 304 sg_id, err) 305 } 306 } 307 308 d.SetId("") 309 310 return nil 311 } 312 313 func findResourceSecurityGroup(conn *ec2.EC2, id string) (*ec2.SecurityGroup, error) { 314 req := &ec2.DescribeSecurityGroupsInput{ 315 GroupIds: []*string{aws.String(id)}, 316 } 317 resp, err := conn.DescribeSecurityGroups(req) 318 if err, ok := err.(awserr.Error); ok && err.Code() == "InvalidGroup.NotFound" { 319 return nil, securityGroupNotFound{id, nil} 320 } 321 if err != nil { 322 return nil, err 323 } 324 if resp == nil { 325 return nil, securityGroupNotFound{id, nil} 326 } 327 if len(resp.SecurityGroups) != 1 || resp.SecurityGroups[0] == nil { 328 return nil, securityGroupNotFound{id, resp.SecurityGroups} 329 } 330 331 return resp.SecurityGroups[0], nil 332 } 333 334 type securityGroupNotFound struct { 335 id string 336 securityGroups []*ec2.SecurityGroup 337 } 338 339 func (err securityGroupNotFound) Error() string { 340 if err.securityGroups == nil { 341 return fmt.Sprintf("No security group with ID %q", err.id) 342 } 343 return fmt.Sprintf("Expected to find one security group with ID %q, got: %#v", 344 err.id, err.securityGroups) 345 } 346 347 // ByGroupPair implements sort.Interface for []*ec2.UserIDGroupPairs based on 348 // GroupID or GroupName field (only one should be set). 349 type ByGroupPair []*ec2.UserIdGroupPair 350 351 func (b ByGroupPair) Len() int { return len(b) } 352 func (b ByGroupPair) Swap(i, j int) { b[i], b[j] = b[j], b[i] } 353 func (b ByGroupPair) Less(i, j int) bool { 354 if b[i].GroupId != nil && b[j].GroupId != nil { 355 return *b[i].GroupId < *b[j].GroupId 356 } 357 if b[i].GroupName != nil && b[j].GroupName != nil { 358 return *b[i].GroupName < *b[j].GroupName 359 } 360 361 panic("mismatched security group rules, may be a terraform bug") 362 } 363 364 func findRuleMatch(p *ec2.IpPermission, rules []*ec2.IpPermission, isVPC bool) *ec2.IpPermission { 365 var rule *ec2.IpPermission 366 for _, r := range rules { 367 if r.ToPort != nil && *p.ToPort != *r.ToPort { 368 continue 369 } 370 371 if r.FromPort != nil && *p.FromPort != *r.FromPort { 372 continue 373 } 374 375 if r.IpProtocol != nil && *p.IpProtocol != *r.IpProtocol { 376 continue 377 } 378 379 remaining := len(p.IpRanges) 380 for _, ip := range p.IpRanges { 381 for _, rip := range r.IpRanges { 382 if *ip.CidrIp == *rip.CidrIp { 383 remaining-- 384 } 385 } 386 } 387 388 if remaining > 0 { 389 continue 390 } 391 392 remaining = len(p.PrefixListIds) 393 for _, pl := range p.PrefixListIds { 394 for _, rpl := range r.PrefixListIds { 395 if *pl.PrefixListId == *rpl.PrefixListId { 396 remaining-- 397 } 398 } 399 } 400 401 if remaining > 0 { 402 continue 403 } 404 405 remaining = len(p.UserIdGroupPairs) 406 for _, ip := range p.UserIdGroupPairs { 407 for _, rip := range r.UserIdGroupPairs { 408 if isVPC { 409 if *ip.GroupId == *rip.GroupId { 410 remaining-- 411 } 412 } else { 413 if *ip.GroupName == *rip.GroupName { 414 remaining-- 415 } 416 } 417 } 418 } 419 420 if remaining > 0 { 421 continue 422 } 423 424 rule = r 425 } 426 return rule 427 } 428 429 func ipPermissionIDHash(sg_id, ruleType string, ip *ec2.IpPermission) string { 430 var buf bytes.Buffer 431 buf.WriteString(fmt.Sprintf("%s-", sg_id)) 432 if ip.FromPort != nil && *ip.FromPort > 0 { 433 buf.WriteString(fmt.Sprintf("%d-", *ip.FromPort)) 434 } 435 if ip.ToPort != nil && *ip.ToPort > 0 { 436 buf.WriteString(fmt.Sprintf("%d-", *ip.ToPort)) 437 } 438 buf.WriteString(fmt.Sprintf("%s-", *ip.IpProtocol)) 439 buf.WriteString(fmt.Sprintf("%s-", ruleType)) 440 441 // We need to make sure to sort the strings below so that we always 442 // generate the same hash code no matter what is in the set. 443 if len(ip.IpRanges) > 0 { 444 s := make([]string, len(ip.IpRanges)) 445 for i, r := range ip.IpRanges { 446 s[i] = *r.CidrIp 447 } 448 sort.Strings(s) 449 450 for _, v := range s { 451 buf.WriteString(fmt.Sprintf("%s-", v)) 452 } 453 } 454 455 if len(ip.PrefixListIds) > 0 { 456 s := make([]string, len(ip.PrefixListIds)) 457 for i, pl := range ip.PrefixListIds { 458 s[i] = *pl.PrefixListId 459 } 460 sort.Strings(s) 461 462 for _, v := range s { 463 buf.WriteString(fmt.Sprintf("%s-", v)) 464 } 465 } 466 467 if len(ip.UserIdGroupPairs) > 0 { 468 sort.Sort(ByGroupPair(ip.UserIdGroupPairs)) 469 for _, pair := range ip.UserIdGroupPairs { 470 if pair.GroupId != nil { 471 buf.WriteString(fmt.Sprintf("%s-", *pair.GroupId)) 472 } else { 473 buf.WriteString("-") 474 } 475 if pair.GroupName != nil { 476 buf.WriteString(fmt.Sprintf("%s-", *pair.GroupName)) 477 } else { 478 buf.WriteString("-") 479 } 480 } 481 } 482 483 return fmt.Sprintf("sgrule-%d", hashcode.String(buf.String())) 484 } 485 486 func expandIPPerm(d *schema.ResourceData, sg *ec2.SecurityGroup) (*ec2.IpPermission, error) { 487 var perm ec2.IpPermission 488 489 perm.FromPort = aws.Int64(int64(d.Get("from_port").(int))) 490 perm.ToPort = aws.Int64(int64(d.Get("to_port").(int))) 491 protocol := protocolForValue(d.Get("protocol").(string)) 492 perm.IpProtocol = aws.String(protocol) 493 494 // build a group map that behaves like a set 495 groups := make(map[string]bool) 496 if raw, ok := d.GetOk("source_security_group_id"); ok { 497 groups[raw.(string)] = true 498 } 499 500 if v, ok := d.GetOk("self"); ok && v.(bool) { 501 // if sg.GroupId != nil { 502 if sg.VpcId != nil && *sg.VpcId != "" { 503 groups[*sg.GroupId] = true 504 } else { 505 groups[*sg.GroupName] = true 506 } 507 } 508 509 if len(groups) > 0 { 510 perm.UserIdGroupPairs = make([]*ec2.UserIdGroupPair, len(groups)) 511 // build string list of group name/ids 512 var gl []string 513 for k, _ := range groups { 514 gl = append(gl, k) 515 } 516 517 for i, name := range gl { 518 ownerId, id := "", name 519 if items := strings.Split(id, "/"); len(items) > 1 { 520 ownerId, id = items[0], items[1] 521 } 522 523 perm.UserIdGroupPairs[i] = &ec2.UserIdGroupPair{ 524 GroupId: aws.String(id), 525 UserId: aws.String(ownerId), 526 } 527 528 if sg.VpcId == nil || *sg.VpcId == "" { 529 perm.UserIdGroupPairs[i].GroupId = nil 530 perm.UserIdGroupPairs[i].GroupName = aws.String(id) 531 perm.UserIdGroupPairs[i].UserId = nil 532 } 533 } 534 } 535 536 if raw, ok := d.GetOk("cidr_blocks"); ok { 537 list := raw.([]interface{}) 538 perm.IpRanges = make([]*ec2.IpRange, len(list)) 539 for i, v := range list { 540 cidrIP, ok := v.(string) 541 if !ok { 542 return nil, fmt.Errorf("empty element found in cidr_blocks - consider using the compact function") 543 } 544 perm.IpRanges[i] = &ec2.IpRange{CidrIp: aws.String(cidrIP)} 545 } 546 } 547 548 if raw, ok := d.GetOk("prefix_list_ids"); ok { 549 list := raw.([]interface{}) 550 perm.PrefixListIds = make([]*ec2.PrefixListId, len(list)) 551 for i, v := range list { 552 prefixListID, ok := v.(string) 553 if !ok { 554 return nil, fmt.Errorf("empty element found in prefix_list_ids - consider using the compact function") 555 } 556 perm.PrefixListIds[i] = &ec2.PrefixListId{PrefixListId: aws.String(prefixListID)} 557 } 558 } 559 560 return &perm, nil 561 } 562 563 func setFromIPPerm(d *schema.ResourceData, sg *ec2.SecurityGroup, rule *ec2.IpPermission) error { 564 isVPC := sg.VpcId != nil && *sg.VpcId != "" 565 566 d.Set("from_port", rule.FromPort) 567 d.Set("to_port", rule.ToPort) 568 d.Set("protocol", rule.IpProtocol) 569 570 var cb []string 571 for _, c := range rule.IpRanges { 572 cb = append(cb, *c.CidrIp) 573 } 574 575 d.Set("cidr_blocks", cb) 576 577 // 'self' is false by default. Below, we range over the group ids and set true 578 // if the parent sg id is found 579 d.Set("self", false) 580 581 var pl []string 582 for _, p := range rule.PrefixListIds { 583 pl = append(pl, *p.PrefixListId) 584 } 585 d.Set("prefix_list_ids", pl) 586 587 if len(rule.UserIdGroupPairs) > 0 { 588 s := rule.UserIdGroupPairs[0] 589 590 // Check for Pair that is the same as the Security Group, to denote self. 591 // Otherwise, mark the group id in source_security_group_id 592 if isVPC { 593 if *s.GroupId == *sg.GroupId { 594 d.Set("self", true) 595 } 596 d.Set("source_security_group_id", *s.GroupId) 597 } else { 598 if *s.GroupName == *sg.GroupName { 599 d.Set("self", true) 600 } 601 d.Set("source_security_group_id", *s.GroupName) 602 } 603 } 604 605 return nil 606 }