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