github.com/leeprovoost/terraform@v0.6.10-0.20160119085442-96f3f76118e7/builtin/providers/aws/resource_aws_security_group.go (about) 1 package aws 2 3 import ( 4 "bytes" 5 "fmt" 6 "log" 7 "sort" 8 "time" 9 10 "github.com/aws/aws-sdk-go/aws" 11 "github.com/aws/aws-sdk-go/aws/awserr" 12 "github.com/aws/aws-sdk-go/service/ec2" 13 "github.com/hashicorp/terraform/helper/hashcode" 14 "github.com/hashicorp/terraform/helper/resource" 15 "github.com/hashicorp/terraform/helper/schema" 16 ) 17 18 func resourceAwsSecurityGroup() *schema.Resource { 19 return &schema.Resource{ 20 Create: resourceAwsSecurityGroupCreate, 21 Read: resourceAwsSecurityGroupRead, 22 Update: resourceAwsSecurityGroupUpdate, 23 Delete: resourceAwsSecurityGroupDelete, 24 25 Schema: map[string]*schema.Schema{ 26 "name": &schema.Schema{ 27 Type: schema.TypeString, 28 Optional: true, 29 Computed: true, 30 ForceNew: true, 31 ConflictsWith: []string{"name_prefix"}, 32 ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { 33 value := v.(string) 34 if len(value) > 255 { 35 errors = append(errors, fmt.Errorf( 36 "%q cannot be longer than 255 characters", k)) 37 } 38 return 39 }, 40 }, 41 42 "name_prefix": &schema.Schema{ 43 Type: schema.TypeString, 44 Optional: true, 45 ForceNew: true, 46 ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { 47 value := v.(string) 48 if len(value) > 100 { 49 errors = append(errors, fmt.Errorf( 50 "%q cannot be longer than 100 characters, name is limited to 255", k)) 51 } 52 return 53 }, 54 }, 55 56 "description": &schema.Schema{ 57 Type: schema.TypeString, 58 Optional: true, 59 ForceNew: true, 60 Default: "Managed by Terraform", 61 ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { 62 value := v.(string) 63 if len(value) > 255 { 64 errors = append(errors, fmt.Errorf( 65 "%q cannot be longer than 255 characters", k)) 66 } 67 return 68 }, 69 }, 70 71 "vpc_id": &schema.Schema{ 72 Type: schema.TypeString, 73 Optional: true, 74 ForceNew: true, 75 Computed: true, 76 }, 77 78 "ingress": &schema.Schema{ 79 Type: schema.TypeSet, 80 Optional: true, 81 Computed: true, 82 Elem: &schema.Resource{ 83 Schema: map[string]*schema.Schema{ 84 "from_port": &schema.Schema{ 85 Type: schema.TypeInt, 86 Required: true, 87 }, 88 89 "to_port": &schema.Schema{ 90 Type: schema.TypeInt, 91 Required: true, 92 }, 93 94 "protocol": &schema.Schema{ 95 Type: schema.TypeString, 96 Required: true, 97 }, 98 99 "cidr_blocks": &schema.Schema{ 100 Type: schema.TypeList, 101 Optional: true, 102 Elem: &schema.Schema{Type: schema.TypeString}, 103 }, 104 105 "security_groups": &schema.Schema{ 106 Type: schema.TypeSet, 107 Optional: true, 108 Elem: &schema.Schema{Type: schema.TypeString}, 109 Set: func(v interface{}) int { 110 return hashcode.String(v.(string)) 111 }, 112 }, 113 114 "self": &schema.Schema{ 115 Type: schema.TypeBool, 116 Optional: true, 117 Default: false, 118 }, 119 }, 120 }, 121 Set: resourceAwsSecurityGroupRuleHash, 122 }, 123 124 "egress": &schema.Schema{ 125 Type: schema.TypeSet, 126 Optional: true, 127 Computed: true, 128 Elem: &schema.Resource{ 129 Schema: map[string]*schema.Schema{ 130 "from_port": &schema.Schema{ 131 Type: schema.TypeInt, 132 Required: true, 133 }, 134 135 "to_port": &schema.Schema{ 136 Type: schema.TypeInt, 137 Required: true, 138 }, 139 140 "protocol": &schema.Schema{ 141 Type: schema.TypeString, 142 Required: true, 143 }, 144 145 "cidr_blocks": &schema.Schema{ 146 Type: schema.TypeList, 147 Optional: true, 148 Elem: &schema.Schema{Type: schema.TypeString}, 149 }, 150 151 "security_groups": &schema.Schema{ 152 Type: schema.TypeSet, 153 Optional: true, 154 Elem: &schema.Schema{Type: schema.TypeString}, 155 Set: func(v interface{}) int { 156 return hashcode.String(v.(string)) 157 }, 158 }, 159 160 "self": &schema.Schema{ 161 Type: schema.TypeBool, 162 Optional: true, 163 Default: false, 164 }, 165 }, 166 }, 167 Set: resourceAwsSecurityGroupRuleHash, 168 }, 169 170 "owner_id": &schema.Schema{ 171 Type: schema.TypeString, 172 Computed: true, 173 }, 174 175 "tags": tagsSchema(), 176 }, 177 } 178 } 179 180 func resourceAwsSecurityGroupCreate(d *schema.ResourceData, meta interface{}) error { 181 conn := meta.(*AWSClient).ec2conn 182 183 securityGroupOpts := &ec2.CreateSecurityGroupInput{} 184 185 if v, ok := d.GetOk("vpc_id"); ok { 186 securityGroupOpts.VpcId = aws.String(v.(string)) 187 } 188 189 if v := d.Get("description"); v != nil { 190 securityGroupOpts.Description = aws.String(v.(string)) 191 } 192 193 var groupName string 194 if v, ok := d.GetOk("name"); ok { 195 groupName = v.(string) 196 } else if v, ok := d.GetOk("name_prefix"); ok { 197 groupName = resource.PrefixedUniqueId(v.(string)) 198 } else { 199 groupName = resource.UniqueId() 200 } 201 securityGroupOpts.GroupName = aws.String(groupName) 202 203 var err error 204 log.Printf( 205 "[DEBUG] Security Group create configuration: %#v", securityGroupOpts) 206 createResp, err := conn.CreateSecurityGroup(securityGroupOpts) 207 if err != nil { 208 return fmt.Errorf("Error creating Security Group: %s", err) 209 } 210 211 d.SetId(*createResp.GroupId) 212 213 log.Printf("[INFO] Security Group ID: %s", d.Id()) 214 215 // Wait for the security group to truly exist 216 log.Printf( 217 "[DEBUG] Waiting for Security Group (%s) to exist", 218 d.Id()) 219 stateConf := &resource.StateChangeConf{ 220 Pending: []string{""}, 221 Target: "exists", 222 Refresh: SGStateRefreshFunc(conn, d.Id()), 223 Timeout: 1 * time.Minute, 224 } 225 226 resp, err := stateConf.WaitForState() 227 if err != nil { 228 return fmt.Errorf( 229 "Error waiting for Security Group (%s) to become available: %s", 230 d.Id(), err) 231 } 232 233 // AWS defaults all Security Groups to have an ALLOW ALL egress rule. Here we 234 // revoke that rule, so users don't unknowningly have/use it. 235 group := resp.(*ec2.SecurityGroup) 236 if group.VpcId != nil && *group.VpcId != "" { 237 log.Printf("[DEBUG] Revoking default egress rule for Security Group for %s", d.Id()) 238 239 req := &ec2.RevokeSecurityGroupEgressInput{ 240 GroupId: createResp.GroupId, 241 IpPermissions: []*ec2.IpPermission{ 242 &ec2.IpPermission{ 243 FromPort: aws.Int64(int64(0)), 244 ToPort: aws.Int64(int64(0)), 245 IpRanges: []*ec2.IpRange{ 246 &ec2.IpRange{ 247 CidrIp: aws.String("0.0.0.0/0"), 248 }, 249 }, 250 IpProtocol: aws.String("-1"), 251 }, 252 }, 253 } 254 255 if _, err = conn.RevokeSecurityGroupEgress(req); err != nil { 256 return fmt.Errorf( 257 "Error revoking default egress rule for Security Group (%s): %s", 258 d.Id(), err) 259 } 260 261 } 262 263 return resourceAwsSecurityGroupUpdate(d, meta) 264 } 265 266 func resourceAwsSecurityGroupRead(d *schema.ResourceData, meta interface{}) error { 267 conn := meta.(*AWSClient).ec2conn 268 269 sgRaw, _, err := SGStateRefreshFunc(conn, d.Id())() 270 if err != nil { 271 return err 272 } 273 if sgRaw == nil { 274 d.SetId("") 275 return nil 276 } 277 278 sg := sgRaw.(*ec2.SecurityGroup) 279 280 ingressRules := resourceAwsSecurityGroupIPPermGather(d, sg.IpPermissions) 281 egressRules := resourceAwsSecurityGroupIPPermGather(d, sg.IpPermissionsEgress) 282 283 d.Set("description", sg.Description) 284 d.Set("name", sg.GroupName) 285 d.Set("vpc_id", sg.VpcId) 286 d.Set("owner_id", sg.OwnerId) 287 d.Set("ingress", ingressRules) 288 d.Set("egress", egressRules) 289 d.Set("tags", tagsToMap(sg.Tags)) 290 return nil 291 } 292 293 func resourceAwsSecurityGroupUpdate(d *schema.ResourceData, meta interface{}) error { 294 conn := meta.(*AWSClient).ec2conn 295 296 sgRaw, _, err := SGStateRefreshFunc(conn, d.Id())() 297 if err != nil { 298 return err 299 } 300 if sgRaw == nil { 301 d.SetId("") 302 return nil 303 } 304 305 group := sgRaw.(*ec2.SecurityGroup) 306 307 err = resourceAwsSecurityGroupUpdateRules(d, "ingress", meta, group) 308 if err != nil { 309 return err 310 } 311 312 if d.Get("vpc_id") != nil { 313 err = resourceAwsSecurityGroupUpdateRules(d, "egress", meta, group) 314 if err != nil { 315 return err 316 } 317 } 318 319 if err := setTags(conn, d); err != nil { 320 return err 321 } 322 323 d.SetPartial("tags") 324 325 return resourceAwsSecurityGroupRead(d, meta) 326 } 327 328 func resourceAwsSecurityGroupDelete(d *schema.ResourceData, meta interface{}) error { 329 conn := meta.(*AWSClient).ec2conn 330 331 log.Printf("[DEBUG] Security Group destroy: %v", d.Id()) 332 333 return resource.Retry(5*time.Minute, func() error { 334 _, err := conn.DeleteSecurityGroup(&ec2.DeleteSecurityGroupInput{ 335 GroupId: aws.String(d.Id()), 336 }) 337 if err != nil { 338 ec2err, ok := err.(awserr.Error) 339 if !ok { 340 return err 341 } 342 343 switch ec2err.Code() { 344 case "InvalidGroup.NotFound": 345 return nil 346 case "DependencyViolation": 347 // If it is a dependency violation, we want to retry 348 return err 349 default: 350 // Any other error, we want to quit the retry loop immediately 351 return resource.RetryError{Err: err} 352 } 353 } 354 355 return nil 356 }) 357 } 358 359 func resourceAwsSecurityGroupRuleHash(v interface{}) int { 360 var buf bytes.Buffer 361 m := v.(map[string]interface{}) 362 buf.WriteString(fmt.Sprintf("%d-", m["from_port"].(int))) 363 buf.WriteString(fmt.Sprintf("%d-", m["to_port"].(int))) 364 buf.WriteString(fmt.Sprintf("%s-", m["protocol"].(string))) 365 buf.WriteString(fmt.Sprintf("%t-", m["self"].(bool))) 366 367 // We need to make sure to sort the strings below so that we always 368 // generate the same hash code no matter what is in the set. 369 if v, ok := m["cidr_blocks"]; ok { 370 vs := v.([]interface{}) 371 s := make([]string, len(vs)) 372 for i, raw := range vs { 373 s[i] = raw.(string) 374 } 375 sort.Strings(s) 376 377 for _, v := range s { 378 buf.WriteString(fmt.Sprintf("%s-", v)) 379 } 380 } 381 if v, ok := m["security_groups"]; ok { 382 vs := v.(*schema.Set).List() 383 s := make([]string, len(vs)) 384 for i, raw := range vs { 385 s[i] = raw.(string) 386 } 387 sort.Strings(s) 388 389 for _, v := range s { 390 buf.WriteString(fmt.Sprintf("%s-", v)) 391 } 392 } 393 394 return hashcode.String(buf.String()) 395 } 396 397 func resourceAwsSecurityGroupIPPermGather(d *schema.ResourceData, permissions []*ec2.IpPermission) []map[string]interface{} { 398 ruleMap := make(map[string]map[string]interface{}) 399 for _, perm := range permissions { 400 var fromPort, toPort int64 401 if v := perm.FromPort; v != nil { 402 fromPort = *v 403 } 404 if v := perm.ToPort; v != nil { 405 toPort = *v 406 } 407 408 k := fmt.Sprintf("%s-%d-%d", *perm.IpProtocol, fromPort, toPort) 409 m, ok := ruleMap[k] 410 if !ok { 411 m = make(map[string]interface{}) 412 ruleMap[k] = m 413 } 414 415 m["from_port"] = fromPort 416 m["to_port"] = toPort 417 m["protocol"] = *perm.IpProtocol 418 419 if len(perm.IpRanges) > 0 { 420 raw, ok := m["cidr_blocks"] 421 if !ok { 422 raw = make([]string, 0, len(perm.IpRanges)) 423 } 424 list := raw.([]string) 425 426 for _, ip := range perm.IpRanges { 427 list = append(list, *ip.CidrIp) 428 } 429 430 m["cidr_blocks"] = list 431 } 432 433 var groups []string 434 if len(perm.UserIdGroupPairs) > 0 { 435 groups = flattenSecurityGroups(perm.UserIdGroupPairs) 436 } 437 for i, id := range groups { 438 if id == d.Id() { 439 groups[i], groups = groups[len(groups)-1], groups[:len(groups)-1] 440 m["self"] = true 441 } 442 } 443 444 if len(groups) > 0 { 445 raw, ok := m["security_groups"] 446 if !ok { 447 raw = make([]string, 0, len(groups)) 448 } 449 list := raw.([]string) 450 451 list = append(list, groups...) 452 m["security_groups"] = list 453 } 454 } 455 rules := make([]map[string]interface{}, 0, len(ruleMap)) 456 for _, m := range ruleMap { 457 rules = append(rules, m) 458 } 459 return rules 460 } 461 462 func resourceAwsSecurityGroupUpdateRules( 463 d *schema.ResourceData, ruleset string, 464 meta interface{}, group *ec2.SecurityGroup) error { 465 466 if d.HasChange(ruleset) { 467 o, n := d.GetChange(ruleset) 468 if o == nil { 469 o = new(schema.Set) 470 } 471 if n == nil { 472 n = new(schema.Set) 473 } 474 475 os := o.(*schema.Set) 476 ns := n.(*schema.Set) 477 478 remove, err := expandIPPerms(group, os.Difference(ns).List()) 479 if err != nil { 480 return err 481 } 482 add, err := expandIPPerms(group, ns.Difference(os).List()) 483 if err != nil { 484 return err 485 } 486 487 // TODO: We need to handle partial state better in the in-between 488 // in this update. 489 490 // TODO: It'd be nicer to authorize before removing, but then we have 491 // to deal with complicated unrolling to get individual CIDR blocks 492 // to avoid authorizing already authorized sources. Removing before 493 // adding is easier here, and Terraform should be fast enough to 494 // not have service issues. 495 496 if len(remove) > 0 || len(add) > 0 { 497 conn := meta.(*AWSClient).ec2conn 498 499 var err error 500 if len(remove) > 0 { 501 log.Printf("[DEBUG] Revoking security group %#v %s rule: %#v", 502 group, ruleset, remove) 503 504 if ruleset == "egress" { 505 req := &ec2.RevokeSecurityGroupEgressInput{ 506 GroupId: group.GroupId, 507 IpPermissions: remove, 508 } 509 _, err = conn.RevokeSecurityGroupEgress(req) 510 } else { 511 req := &ec2.RevokeSecurityGroupIngressInput{ 512 GroupId: group.GroupId, 513 IpPermissions: remove, 514 } 515 _, err = conn.RevokeSecurityGroupIngress(req) 516 } 517 518 if err != nil { 519 return fmt.Errorf( 520 "Error authorizing security group %s rules: %s", 521 ruleset, err) 522 } 523 } 524 525 if len(add) > 0 { 526 log.Printf("[DEBUG] Authorizing security group %#v %s rule: %#v", 527 group, ruleset, add) 528 // Authorize the new rules 529 if ruleset == "egress" { 530 req := &ec2.AuthorizeSecurityGroupEgressInput{ 531 GroupId: group.GroupId, 532 IpPermissions: add, 533 } 534 _, err = conn.AuthorizeSecurityGroupEgress(req) 535 } else { 536 req := &ec2.AuthorizeSecurityGroupIngressInput{ 537 GroupId: group.GroupId, 538 IpPermissions: add, 539 } 540 if group.VpcId == nil || *group.VpcId == "" { 541 req.GroupId = nil 542 req.GroupName = group.GroupName 543 } 544 545 _, err = conn.AuthorizeSecurityGroupIngress(req) 546 } 547 548 if err != nil { 549 return fmt.Errorf( 550 "Error authorizing security group %s rules: %s", 551 ruleset, err) 552 } 553 } 554 } 555 } 556 return nil 557 } 558 559 // SGStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch 560 // a security group. 561 func SGStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc { 562 return func() (interface{}, string, error) { 563 req := &ec2.DescribeSecurityGroupsInput{ 564 GroupIds: []*string{aws.String(id)}, 565 } 566 resp, err := conn.DescribeSecurityGroups(req) 567 if err != nil { 568 if ec2err, ok := err.(awserr.Error); ok { 569 if ec2err.Code() == "InvalidSecurityGroupID.NotFound" || 570 ec2err.Code() == "InvalidGroup.NotFound" { 571 resp = nil 572 err = nil 573 } 574 } 575 576 if err != nil { 577 log.Printf("Error on SGStateRefresh: %s", err) 578 return nil, "", err 579 } 580 } 581 582 if resp == nil { 583 return nil, "", nil 584 } 585 586 group := resp.SecurityGroups[0] 587 return group, "exists", nil 588 } 589 }