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