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