github.com/leeprovoost/terraform@v0.6.10-0.20160119085442-96f3f76118e7/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 d.SetId(ipPermissionIDHash(sg_id, ruleType, perm)) 153 154 return resourceAwsSecurityGroupRuleRead(d, meta) 155 } 156 157 func resourceAwsSecurityGroupRuleRead(d *schema.ResourceData, meta interface{}) error { 158 conn := meta.(*AWSClient).ec2conn 159 sg_id := d.Get("security_group_id").(string) 160 sg, err := findResourceSecurityGroup(conn, sg_id) 161 if err != nil { 162 log.Printf("[DEBUG] Error finding Secuirty Group (%s) for Rule (%s): %s", sg_id, d.Id(), err) 163 d.SetId("") 164 return nil 165 } 166 167 var rule *ec2.IpPermission 168 var rules []*ec2.IpPermission 169 ruleType := d.Get("type").(string) 170 switch ruleType { 171 case "ingress": 172 rules = sg.IpPermissions 173 default: 174 rules = sg.IpPermissionsEgress 175 } 176 177 p, err := expandIPPerm(d, sg) 178 if err != nil { 179 return err 180 } 181 182 if len(rules) == 0 { 183 log.Printf("[WARN] No %s rules were found for Security Group (%s) looking for Security Group Rule (%s)", 184 ruleType, *sg.GroupName, d.Id()) 185 d.SetId("") 186 return nil 187 } 188 189 for _, r := range rules { 190 if r.ToPort != nil && *p.ToPort != *r.ToPort { 191 continue 192 } 193 194 if r.FromPort != nil && *p.FromPort != *r.FromPort { 195 continue 196 } 197 198 if r.IpProtocol != nil && *p.IpProtocol != *r.IpProtocol { 199 continue 200 } 201 202 remaining := len(p.IpRanges) 203 for _, ip := range p.IpRanges { 204 for _, rip := range r.IpRanges { 205 if *ip.CidrIp == *rip.CidrIp { 206 remaining-- 207 } 208 } 209 } 210 211 if remaining > 0 { 212 continue 213 } 214 215 remaining = len(p.UserIdGroupPairs) 216 for _, ip := range p.UserIdGroupPairs { 217 for _, rip := range r.UserIdGroupPairs { 218 if *ip.GroupId == *rip.GroupId { 219 remaining-- 220 } 221 } 222 } 223 224 if remaining > 0 { 225 continue 226 } 227 228 log.Printf("[DEBUG] Found rule for Security Group Rule (%s): %s", d.Id(), r) 229 rule = r 230 } 231 232 if rule == nil { 233 log.Printf("[DEBUG] Unable to find matching %s Security Group Rule (%s) for Group %s", 234 ruleType, d.Id(), sg_id) 235 d.SetId("") 236 return nil 237 } 238 239 d.Set("from_port", rule.FromPort) 240 d.Set("to_port", rule.ToPort) 241 d.Set("protocol", rule.IpProtocol) 242 d.Set("type", ruleType) 243 244 var cb []string 245 for _, c := range p.IpRanges { 246 cb = append(cb, *c.CidrIp) 247 } 248 249 d.Set("cidr_blocks", cb) 250 251 if len(p.UserIdGroupPairs) > 0 { 252 s := p.UserIdGroupPairs[0] 253 d.Set("source_security_group_id", *s.GroupId) 254 } 255 256 return nil 257 } 258 259 func resourceAwsSecurityGroupRuleDelete(d *schema.ResourceData, meta interface{}) error { 260 conn := meta.(*AWSClient).ec2conn 261 sg_id := d.Get("security_group_id").(string) 262 263 awsMutexKV.Lock(sg_id) 264 defer awsMutexKV.Unlock(sg_id) 265 266 sg, err := findResourceSecurityGroup(conn, sg_id) 267 if err != nil { 268 return err 269 } 270 271 perm, err := expandIPPerm(d, sg) 272 if err != nil { 273 return err 274 } 275 ruleType := d.Get("type").(string) 276 switch ruleType { 277 case "ingress": 278 log.Printf("[DEBUG] Revoking rule (%s) from security group %s:\n%s", 279 "ingress", sg_id, perm) 280 req := &ec2.RevokeSecurityGroupIngressInput{ 281 GroupId: sg.GroupId, 282 IpPermissions: []*ec2.IpPermission{perm}, 283 } 284 285 _, err = conn.RevokeSecurityGroupIngress(req) 286 287 if err != nil { 288 return fmt.Errorf( 289 "Error revoking security group %s rules: %s", 290 sg_id, err) 291 } 292 case "egress": 293 294 log.Printf("[DEBUG] Revoking security group %#v %s rule: %#v", 295 sg_id, "egress", perm) 296 req := &ec2.RevokeSecurityGroupEgressInput{ 297 GroupId: sg.GroupId, 298 IpPermissions: []*ec2.IpPermission{perm}, 299 } 300 301 _, err = conn.RevokeSecurityGroupEgress(req) 302 303 if err != nil { 304 return fmt.Errorf( 305 "Error revoking security group %s rules: %s", 306 sg_id, err) 307 } 308 } 309 310 d.SetId("") 311 312 return nil 313 } 314 315 func findResourceSecurityGroup(conn *ec2.EC2, id string) (*ec2.SecurityGroup, error) { 316 req := &ec2.DescribeSecurityGroupsInput{ 317 GroupIds: []*string{aws.String(id)}, 318 } 319 resp, err := conn.DescribeSecurityGroups(req) 320 if err != nil { 321 return nil, err 322 } 323 324 if resp == nil || len(resp.SecurityGroups) != 1 || resp.SecurityGroups[0] == nil { 325 return nil, fmt.Errorf( 326 "Expected to find one security group with ID %q, got: %#v", 327 id, resp.SecurityGroups) 328 } 329 330 return resp.SecurityGroups[0], nil 331 } 332 333 // ByGroupPair implements sort.Interface for []*ec2.UserIDGroupPairs based on 334 // GroupID or GroupName field (only one should be set). 335 type ByGroupPair []*ec2.UserIdGroupPair 336 337 func (b ByGroupPair) Len() int { return len(b) } 338 func (b ByGroupPair) Swap(i, j int) { b[i], b[j] = b[j], b[i] } 339 func (b ByGroupPair) Less(i, j int) bool { 340 if b[i].GroupId != nil && b[j].GroupId != nil { 341 return *b[i].GroupId < *b[j].GroupId 342 } 343 if b[i].GroupName != nil && b[j].GroupName != nil { 344 return *b[i].GroupName < *b[j].GroupName 345 } 346 347 panic("mismatched security group rules, may be a terraform bug") 348 } 349 350 func ipPermissionIDHash(sg_id, ruleType string, ip *ec2.IpPermission) string { 351 var buf bytes.Buffer 352 buf.WriteString(fmt.Sprintf("%s-", sg_id)) 353 if ip.FromPort != nil && *ip.FromPort > 0 { 354 buf.WriteString(fmt.Sprintf("%d-", *ip.FromPort)) 355 } 356 if ip.ToPort != nil && *ip.ToPort > 0 { 357 buf.WriteString(fmt.Sprintf("%d-", *ip.ToPort)) 358 } 359 buf.WriteString(fmt.Sprintf("%s-", *ip.IpProtocol)) 360 buf.WriteString(fmt.Sprintf("%s-", ruleType)) 361 362 // We need to make sure to sort the strings below so that we always 363 // generate the same hash code no matter what is in the set. 364 if len(ip.IpRanges) > 0 { 365 s := make([]string, len(ip.IpRanges)) 366 for i, r := range ip.IpRanges { 367 s[i] = *r.CidrIp 368 } 369 sort.Strings(s) 370 371 for _, v := range s { 372 buf.WriteString(fmt.Sprintf("%s-", v)) 373 } 374 } 375 376 if len(ip.UserIdGroupPairs) > 0 { 377 sort.Sort(ByGroupPair(ip.UserIdGroupPairs)) 378 for _, pair := range ip.UserIdGroupPairs { 379 if pair.GroupId != nil { 380 buf.WriteString(fmt.Sprintf("%s-", *pair.GroupId)) 381 } else { 382 buf.WriteString("-") 383 } 384 if pair.GroupName != nil { 385 buf.WriteString(fmt.Sprintf("%s-", *pair.GroupName)) 386 } else { 387 buf.WriteString("-") 388 } 389 } 390 } 391 392 return fmt.Sprintf("sgrule-%d", hashcode.String(buf.String())) 393 } 394 395 func expandIPPerm(d *schema.ResourceData, sg *ec2.SecurityGroup) (*ec2.IpPermission, error) { 396 var perm ec2.IpPermission 397 398 perm.FromPort = aws.Int64(int64(d.Get("from_port").(int))) 399 perm.ToPort = aws.Int64(int64(d.Get("to_port").(int))) 400 perm.IpProtocol = aws.String(d.Get("protocol").(string)) 401 402 // build a group map that behaves like a set 403 groups := make(map[string]bool) 404 if raw, ok := d.GetOk("source_security_group_id"); ok { 405 groups[raw.(string)] = true 406 } 407 408 if v, ok := d.GetOk("self"); ok && v.(bool) { 409 if sg.VpcId != nil && *sg.VpcId != "" { 410 groups[*sg.GroupId] = true 411 } else { 412 groups[*sg.GroupName] = true 413 } 414 } 415 416 if len(groups) > 0 { 417 perm.UserIdGroupPairs = make([]*ec2.UserIdGroupPair, len(groups)) 418 // build string list of group name/ids 419 var gl []string 420 for k, _ := range groups { 421 gl = append(gl, k) 422 } 423 424 for i, name := range gl { 425 ownerId, id := "", name 426 if items := strings.Split(id, "/"); len(items) > 1 { 427 ownerId, id = items[0], items[1] 428 } 429 430 perm.UserIdGroupPairs[i] = &ec2.UserIdGroupPair{ 431 GroupId: aws.String(id), 432 UserId: aws.String(ownerId), 433 } 434 435 if sg.VpcId == nil || *sg.VpcId == "" { 436 perm.UserIdGroupPairs[i].GroupId = nil 437 perm.UserIdGroupPairs[i].GroupName = aws.String(id) 438 perm.UserIdGroupPairs[i].UserId = nil 439 } 440 } 441 } 442 443 if raw, ok := d.GetOk("cidr_blocks"); ok { 444 list := raw.([]interface{}) 445 perm.IpRanges = make([]*ec2.IpRange, len(list)) 446 for i, v := range list { 447 cidrIP, ok := v.(string) 448 if !ok { 449 return nil, fmt.Errorf("empty element found in cidr_blocks - consider using the compact function") 450 } 451 perm.IpRanges[i] = &ec2.IpRange{CidrIp: aws.String(cidrIP)} 452 } 453 } 454 455 return &perm, nil 456 }