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