github.com/andresvia/terraform@v0.6.15-0.20160412045437-d51c75946785/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 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/schema" 15 ) 16 17 func resourceAwsSecurityGroupRule() *schema.Resource { 18 return &schema.Resource{ 19 Create: resourceAwsSecurityGroupRuleCreate, 20 Read: resourceAwsSecurityGroupRuleRead, 21 Delete: resourceAwsSecurityGroupRuleDelete, 22 23 SchemaVersion: 2, 24 MigrateState: resourceAwsSecurityGroupRuleMigrateState, 25 26 Schema: map[string]*schema.Schema{ 27 "type": &schema.Schema{ 28 Type: schema.TypeString, 29 Required: true, 30 ForceNew: true, 31 Description: "Type of rule, ingress (inbound) or egress (outbound).", 32 }, 33 34 "from_port": &schema.Schema{ 35 Type: schema.TypeInt, 36 Required: true, 37 ForceNew: true, 38 }, 39 40 "to_port": &schema.Schema{ 41 Type: schema.TypeInt, 42 Required: true, 43 ForceNew: true, 44 }, 45 46 "protocol": &schema.Schema{ 47 Type: schema.TypeString, 48 Required: true, 49 ForceNew: true, 50 StateFunc: protocolStateFunc, 51 }, 52 53 "cidr_blocks": &schema.Schema{ 54 Type: schema.TypeList, 55 Optional: true, 56 ForceNew: true, 57 Elem: &schema.Schema{Type: schema.TypeString}, 58 }, 59 60 "security_group_id": &schema.Schema{ 61 Type: schema.TypeString, 62 Required: true, 63 ForceNew: true, 64 }, 65 66 "source_security_group_id": &schema.Schema{ 67 Type: schema.TypeString, 68 Optional: true, 69 ForceNew: true, 70 Computed: true, 71 ConflictsWith: []string{"cidr_blocks", "self"}, 72 }, 73 74 "self": &schema.Schema{ 75 Type: schema.TypeBool, 76 Optional: true, 77 Default: false, 78 ForceNew: true, 79 ConflictsWith: []string{"cidr_blocks"}, 80 }, 81 }, 82 } 83 } 84 85 func resourceAwsSecurityGroupRuleCreate(d *schema.ResourceData, meta interface{}) error { 86 conn := meta.(*AWSClient).ec2conn 87 sg_id := d.Get("security_group_id").(string) 88 89 awsMutexKV.Lock(sg_id) 90 defer awsMutexKV.Unlock(sg_id) 91 92 sg, err := findResourceSecurityGroup(conn, sg_id) 93 if err != nil { 94 return err 95 } 96 97 perm, err := expandIPPerm(d, sg) 98 if err != nil { 99 return err 100 } 101 102 ruleType := d.Get("type").(string) 103 104 var autherr error 105 switch ruleType { 106 case "ingress": 107 log.Printf("[DEBUG] Authorizing security group %s %s rule: %s", 108 sg_id, "Ingress", perm) 109 110 req := &ec2.AuthorizeSecurityGroupIngressInput{ 111 GroupId: sg.GroupId, 112 IpPermissions: []*ec2.IpPermission{perm}, 113 } 114 115 if sg.VpcId == nil || *sg.VpcId == "" { 116 req.GroupId = nil 117 req.GroupName = sg.GroupName 118 } 119 120 _, autherr = conn.AuthorizeSecurityGroupIngress(req) 121 122 case "egress": 123 log.Printf("[DEBUG] Authorizing security group %s %s rule: %#v", 124 sg_id, "Egress", perm) 125 126 req := &ec2.AuthorizeSecurityGroupEgressInput{ 127 GroupId: sg.GroupId, 128 IpPermissions: []*ec2.IpPermission{perm}, 129 } 130 131 _, autherr = conn.AuthorizeSecurityGroupEgress(req) 132 133 default: 134 return fmt.Errorf("Security Group Rule must be type 'ingress' or type 'egress'") 135 } 136 137 if autherr != nil { 138 if awsErr, ok := autherr.(awserr.Error); ok { 139 if awsErr.Code() == "InvalidPermission.Duplicate" { 140 return fmt.Errorf(`[WARN] A duplicate Security Group rule was found. This may be 141 a side effect of a now-fixed Terraform issue causing two security groups with 142 identical attributes but different source_security_group_ids to overwrite each 143 other in the state. See https://github.com/hashicorp/terraform/pull/2376 for more 144 information and instructions for recovery. Error message: %s`, awsErr.Message()) 145 } 146 } 147 148 return fmt.Errorf( 149 "Error authorizing security group rule type %s: %s", 150 ruleType, autherr) 151 } 152 153 id := ipPermissionIDHash(sg_id, ruleType, perm) 154 d.SetId(id) 155 log.Printf("[DEBUG] Security group rule ID set to %s", id) 156 157 return resourceAwsSecurityGroupRuleRead(d, meta) 158 } 159 160 func resourceAwsSecurityGroupRuleRead(d *schema.ResourceData, meta interface{}) error { 161 conn := meta.(*AWSClient).ec2conn 162 sg_id := d.Get("security_group_id").(string) 163 sg, err := findResourceSecurityGroup(conn, sg_id) 164 if err != nil { 165 log.Printf("[DEBUG] Error finding Secuirty Group (%s) for Rule (%s): %s", sg_id, d.Id(), err) 166 d.SetId("") 167 return nil 168 } 169 170 isVPC := sg.VpcId != nil && *sg.VpcId != "" 171 172 var rule *ec2.IpPermission 173 var rules []*ec2.IpPermission 174 ruleType := d.Get("type").(string) 175 switch ruleType { 176 case "ingress": 177 rules = sg.IpPermissions 178 default: 179 rules = sg.IpPermissionsEgress 180 } 181 182 p, err := expandIPPerm(d, sg) 183 if err != nil { 184 return err 185 } 186 187 if len(rules) == 0 { 188 log.Printf("[WARN] No %s rules were found for Security Group (%s) looking for Security Group Rule (%s)", 189 ruleType, *sg.GroupName, d.Id()) 190 d.SetId("") 191 return nil 192 } 193 194 for _, r := range rules { 195 if r.ToPort != nil && *p.ToPort != *r.ToPort { 196 continue 197 } 198 199 if r.FromPort != nil && *p.FromPort != *r.FromPort { 200 continue 201 } 202 203 if r.IpProtocol != nil && *p.IpProtocol != *r.IpProtocol { 204 continue 205 } 206 207 remaining := len(p.IpRanges) 208 for _, ip := range p.IpRanges { 209 for _, rip := range r.IpRanges { 210 if *ip.CidrIp == *rip.CidrIp { 211 remaining-- 212 } 213 } 214 } 215 216 if remaining > 0 { 217 continue 218 } 219 220 remaining = len(p.UserIdGroupPairs) 221 for _, ip := range p.UserIdGroupPairs { 222 for _, rip := range r.UserIdGroupPairs { 223 if isVPC { 224 if *ip.GroupId == *rip.GroupId { 225 remaining-- 226 } 227 } else { 228 if *ip.GroupName == *rip.GroupName { 229 remaining-- 230 } 231 } 232 } 233 } 234 235 if remaining > 0 { 236 continue 237 } 238 239 log.Printf("[DEBUG] Found rule for Security Group Rule (%s): %s", d.Id(), r) 240 rule = r 241 } 242 243 if rule == nil { 244 log.Printf("[DEBUG] Unable to find matching %s Security Group Rule (%s) for Group %s", 245 ruleType, d.Id(), sg_id) 246 d.SetId("") 247 return nil 248 } 249 250 d.Set("from_port", rule.FromPort) 251 d.Set("to_port", rule.ToPort) 252 d.Set("protocol", rule.IpProtocol) 253 d.Set("type", ruleType) 254 255 var cb []string 256 for _, c := range p.IpRanges { 257 cb = append(cb, *c.CidrIp) 258 } 259 260 d.Set("cidr_blocks", cb) 261 262 if len(p.UserIdGroupPairs) > 0 { 263 s := p.UserIdGroupPairs[0] 264 if isVPC { 265 d.Set("source_security_group_id", *s.GroupId) 266 } else { 267 d.Set("source_security_group_id", *s.GroupName) 268 } 269 } 270 271 return nil 272 } 273 274 func resourceAwsSecurityGroupRuleDelete(d *schema.ResourceData, meta interface{}) error { 275 conn := meta.(*AWSClient).ec2conn 276 sg_id := d.Get("security_group_id").(string) 277 278 awsMutexKV.Lock(sg_id) 279 defer awsMutexKV.Unlock(sg_id) 280 281 sg, err := findResourceSecurityGroup(conn, sg_id) 282 if err != nil { 283 return err 284 } 285 286 perm, err := expandIPPerm(d, sg) 287 if err != nil { 288 return err 289 } 290 ruleType := d.Get("type").(string) 291 switch ruleType { 292 case "ingress": 293 log.Printf("[DEBUG] Revoking rule (%s) from security group %s:\n%s", 294 "ingress", sg_id, perm) 295 req := &ec2.RevokeSecurityGroupIngressInput{ 296 GroupId: sg.GroupId, 297 IpPermissions: []*ec2.IpPermission{perm}, 298 } 299 300 _, err = conn.RevokeSecurityGroupIngress(req) 301 302 if err != nil { 303 return fmt.Errorf( 304 "Error revoking security group %s rules: %s", 305 sg_id, err) 306 } 307 case "egress": 308 309 log.Printf("[DEBUG] Revoking security group %#v %s rule: %#v", 310 sg_id, "egress", perm) 311 req := &ec2.RevokeSecurityGroupEgressInput{ 312 GroupId: sg.GroupId, 313 IpPermissions: []*ec2.IpPermission{perm}, 314 } 315 316 _, err = conn.RevokeSecurityGroupEgress(req) 317 318 if err != nil { 319 return fmt.Errorf( 320 "Error revoking security group %s rules: %s", 321 sg_id, err) 322 } 323 } 324 325 d.SetId("") 326 327 return nil 328 } 329 330 func findResourceSecurityGroup(conn *ec2.EC2, id string) (*ec2.SecurityGroup, error) { 331 req := &ec2.DescribeSecurityGroupsInput{ 332 GroupIds: []*string{aws.String(id)}, 333 } 334 resp, err := conn.DescribeSecurityGroups(req) 335 if err != nil { 336 return nil, err 337 } 338 339 if resp == nil || len(resp.SecurityGroups) != 1 || resp.SecurityGroups[0] == nil { 340 return nil, fmt.Errorf( 341 "Expected to find one security group with ID %q, got: %#v", 342 id, resp.SecurityGroups) 343 } 344 345 return resp.SecurityGroups[0], nil 346 } 347 348 // ByGroupPair implements sort.Interface for []*ec2.UserIDGroupPairs based on 349 // GroupID or GroupName field (only one should be set). 350 type ByGroupPair []*ec2.UserIdGroupPair 351 352 func (b ByGroupPair) Len() int { return len(b) } 353 func (b ByGroupPair) Swap(i, j int) { b[i], b[j] = b[j], b[i] } 354 func (b ByGroupPair) Less(i, j int) bool { 355 if b[i].GroupId != nil && b[j].GroupId != nil { 356 return *b[i].GroupId < *b[j].GroupId 357 } 358 if b[i].GroupName != nil && b[j].GroupName != nil { 359 return *b[i].GroupName < *b[j].GroupName 360 } 361 362 panic("mismatched security group rules, may be a terraform bug") 363 } 364 365 func ipPermissionIDHash(sg_id, ruleType string, ip *ec2.IpPermission) string { 366 var buf bytes.Buffer 367 buf.WriteString(fmt.Sprintf("%s-", sg_id)) 368 if ip.FromPort != nil && *ip.FromPort > 0 { 369 buf.WriteString(fmt.Sprintf("%d-", *ip.FromPort)) 370 } 371 if ip.ToPort != nil && *ip.ToPort > 0 { 372 buf.WriteString(fmt.Sprintf("%d-", *ip.ToPort)) 373 } 374 buf.WriteString(fmt.Sprintf("%s-", *ip.IpProtocol)) 375 buf.WriteString(fmt.Sprintf("%s-", ruleType)) 376 377 // We need to make sure to sort the strings below so that we always 378 // generate the same hash code no matter what is in the set. 379 if len(ip.IpRanges) > 0 { 380 s := make([]string, len(ip.IpRanges)) 381 for i, r := range ip.IpRanges { 382 s[i] = *r.CidrIp 383 } 384 sort.Strings(s) 385 386 for _, v := range s { 387 buf.WriteString(fmt.Sprintf("%s-", v)) 388 } 389 } 390 391 if len(ip.UserIdGroupPairs) > 0 { 392 sort.Sort(ByGroupPair(ip.UserIdGroupPairs)) 393 for _, pair := range ip.UserIdGroupPairs { 394 if pair.GroupId != nil { 395 buf.WriteString(fmt.Sprintf("%s-", *pair.GroupId)) 396 } else { 397 buf.WriteString("-") 398 } 399 if pair.GroupName != nil { 400 buf.WriteString(fmt.Sprintf("%s-", *pair.GroupName)) 401 } else { 402 buf.WriteString("-") 403 } 404 } 405 } 406 407 return fmt.Sprintf("sgrule-%d", hashcode.String(buf.String())) 408 } 409 410 func expandIPPerm(d *schema.ResourceData, sg *ec2.SecurityGroup) (*ec2.IpPermission, error) { 411 var perm ec2.IpPermission 412 413 perm.FromPort = aws.Int64(int64(d.Get("from_port").(int))) 414 perm.ToPort = aws.Int64(int64(d.Get("to_port").(int))) 415 protocol := protocolForValue(d.Get("protocol").(string)) 416 perm.IpProtocol = aws.String(protocol) 417 418 // build a group map that behaves like a set 419 groups := make(map[string]bool) 420 if raw, ok := d.GetOk("source_security_group_id"); ok { 421 groups[raw.(string)] = true 422 } 423 424 if v, ok := d.GetOk("self"); ok && v.(bool) { 425 // if sg.GroupId != nil { 426 if sg.VpcId != nil && *sg.VpcId != "" { 427 groups[*sg.GroupId] = true 428 } else { 429 groups[*sg.GroupName] = true 430 } 431 } 432 433 if len(groups) > 0 { 434 perm.UserIdGroupPairs = make([]*ec2.UserIdGroupPair, len(groups)) 435 // build string list of group name/ids 436 var gl []string 437 for k, _ := range groups { 438 gl = append(gl, k) 439 } 440 441 for i, name := range gl { 442 ownerId, id := "", name 443 if items := strings.Split(id, "/"); len(items) > 1 { 444 ownerId, id = items[0], items[1] 445 } 446 447 perm.UserIdGroupPairs[i] = &ec2.UserIdGroupPair{ 448 GroupId: aws.String(id), 449 UserId: aws.String(ownerId), 450 } 451 452 if sg.VpcId == nil || *sg.VpcId == "" { 453 perm.UserIdGroupPairs[i].GroupId = nil 454 perm.UserIdGroupPairs[i].GroupName = aws.String(id) 455 perm.UserIdGroupPairs[i].UserId = nil 456 } 457 } 458 } 459 460 if raw, ok := d.GetOk("cidr_blocks"); ok { 461 list := raw.([]interface{}) 462 perm.IpRanges = make([]*ec2.IpRange, len(list)) 463 for i, v := range list { 464 cidrIP, ok := v.(string) 465 if !ok { 466 return nil, fmt.Errorf("empty element found in cidr_blocks - consider using the compact function") 467 } 468 perm.IpRanges[i] = &ec2.IpRange{CidrIp: aws.String(cidrIP)} 469 } 470 } 471 472 return &perm, nil 473 }