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