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