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