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