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