github.com/hobbeswalsh/terraform@v0.3.7-0.20150619183303-ad17cf55a0fa/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/aws/awsutil" 13 "github.com/aws/aws-sdk-go/service/ec2" 14 "github.com/hashicorp/terraform/helper/hashcode" 15 "github.com/hashicorp/terraform/helper/schema" 16 ) 17 18 func resourceAwsSecurityGroupRule() *schema.Resource { 19 return &schema.Resource{ 20 Create: resourceAwsSecurityGroupRuleCreate, 21 Read: resourceAwsSecurityGroupRuleRead, 22 Delete: resourceAwsSecurityGroupRuleDelete, 23 24 SchemaVersion: 1, 25 MigrateState: resourceAwsSecurityGroupRuleMigrateState, 26 27 Schema: map[string]*schema.Schema{ 28 "type": &schema.Schema{ 29 Type: schema.TypeString, 30 Required: true, 31 ForceNew: true, 32 Description: "Type of rule, ingress (inbound) or egress (outbound).", 33 }, 34 35 "from_port": &schema.Schema{ 36 Type: schema.TypeInt, 37 Required: true, 38 ForceNew: true, 39 }, 40 41 "to_port": &schema.Schema{ 42 Type: schema.TypeInt, 43 Required: true, 44 ForceNew: true, 45 }, 46 47 "protocol": &schema.Schema{ 48 Type: schema.TypeString, 49 Required: true, 50 ForceNew: true, 51 }, 52 53 "cidr_blocks": &schema.Schema{ 54 Type: schema.TypeList, 55 Optional: true, 56 ForceNew: true, 57 Elem: &schema.Schema{Type: schema.TypeString}, 58 }, 59 60 "security_group_id": &schema.Schema{ 61 Type: schema.TypeString, 62 Required: true, 63 ForceNew: true, 64 }, 65 66 "source_security_group_id": &schema.Schema{ 67 Type: schema.TypeString, 68 Optional: true, 69 ForceNew: true, 70 Computed: true, 71 ConflictsWith: []string{"cidr_blocks"}, 72 }, 73 74 "self": &schema.Schema{ 75 Type: schema.TypeBool, 76 Optional: true, 77 Default: false, 78 ForceNew: true, 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 fmt.Errorf("sorry") 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", awsutil.StringValue(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(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 d.SetId("") 157 } 158 159 var rule *ec2.IPPermission 160 ruleType := d.Get("type").(string) 161 var rl []*ec2.IPPermission 162 switch ruleType { 163 case "ingress": 164 rl = sg.IPPermissions 165 default: 166 rl = sg.IPPermissionsEgress 167 } 168 169 for _, r := range rl { 170 if d.Id() == ipPermissionIDHash(ruleType, r) { 171 rule = r 172 } 173 } 174 175 if rule == nil { 176 log.Printf("[DEBUG] Unable to find matching %s Security Group Rule for Group %s", 177 ruleType, sg_id) 178 d.SetId("") 179 return nil 180 } 181 182 d.Set("from_port", rule.FromPort) 183 d.Set("to_port", rule.ToPort) 184 d.Set("protocol", rule.IPProtocol) 185 d.Set("type", ruleType) 186 187 var cb []string 188 for _, c := range rule.IPRanges { 189 cb = append(cb, *c.CIDRIP) 190 } 191 192 d.Set("cidr_blocks", cb) 193 194 if len(rule.UserIDGroupPairs) > 0 { 195 s := rule.UserIDGroupPairs[0] 196 d.Set("source_security_group_id", *s.GroupID) 197 } 198 199 return nil 200 } 201 202 func resourceAwsSecurityGroupRuleDelete(d *schema.ResourceData, meta interface{}) error { 203 conn := meta.(*AWSClient).ec2conn 204 sg_id := d.Get("security_group_id").(string) 205 sg, err := findResourceSecurityGroup(conn, sg_id) 206 207 if err != nil { 208 return fmt.Errorf("sorry") 209 } 210 211 perm := expandIPPerm(d, sg) 212 ruleType := d.Get("type").(string) 213 switch ruleType { 214 case "ingress": 215 log.Printf("[DEBUG] Revoking rule (%s) from security group %s:\n%s", 216 "ingress", sg_id, awsutil.StringValue(perm)) 217 req := &ec2.RevokeSecurityGroupIngressInput{ 218 GroupID: sg.GroupID, 219 IPPermissions: []*ec2.IPPermission{perm}, 220 } 221 222 _, err = conn.RevokeSecurityGroupIngress(req) 223 224 if err != nil { 225 return fmt.Errorf( 226 "Error revoking security group %s rules: %s", 227 sg_id, err) 228 } 229 case "egress": 230 231 log.Printf("[DEBUG] Revoking security group %#v %s rule: %#v", 232 sg_id, "egress", perm) 233 req := &ec2.RevokeSecurityGroupEgressInput{ 234 GroupID: sg.GroupID, 235 IPPermissions: []*ec2.IPPermission{perm}, 236 } 237 238 _, err = conn.RevokeSecurityGroupEgress(req) 239 240 if err != nil { 241 return fmt.Errorf( 242 "Error revoking security group %s rules: %s", 243 sg_id, err) 244 } 245 } 246 247 d.SetId("") 248 249 return nil 250 } 251 252 func findResourceSecurityGroup(conn *ec2.EC2, id string) (*ec2.SecurityGroup, error) { 253 req := &ec2.DescribeSecurityGroupsInput{ 254 GroupIDs: []*string{aws.String(id)}, 255 } 256 resp, err := conn.DescribeSecurityGroups(req) 257 if err != nil { 258 if ec2err, ok := err.(awserr.Error); ok { 259 if ec2err.Code() == "InvalidSecurityGroupID.NotFound" || 260 ec2err.Code() == "InvalidGroup.NotFound" { 261 resp = nil 262 err = nil 263 } 264 } 265 266 if err != nil { 267 log.Printf("Error on findResourceSecurityGroup: %s", err) 268 return nil, err 269 } 270 } 271 272 return resp.SecurityGroups[0], nil 273 } 274 275 // ByGroupPair implements sort.Interface for []*ec2.UserIDGroupPairs based on 276 // GroupID or GroupName field (only one should be set). 277 type ByGroupPair []*ec2.UserIDGroupPair 278 279 func (b ByGroupPair) Len() int { return len(b) } 280 func (b ByGroupPair) Swap(i, j int) { b[i], b[j] = b[j], b[i] } 281 func (b ByGroupPair) Less(i, j int) bool { 282 if b[i].GroupID != nil && b[j].GroupID != nil { 283 return *b[i].GroupID < *b[j].GroupID 284 } 285 if b[i].GroupName != nil && b[j].GroupName != nil { 286 return *b[i].GroupName < *b[j].GroupName 287 } 288 289 panic("mismatched security group rules, may be a terraform bug") 290 } 291 292 func ipPermissionIDHash(ruleType string, ip *ec2.IPPermission) string { 293 var buf bytes.Buffer 294 if ip.FromPort != nil && *ip.FromPort > 0 { 295 buf.WriteString(fmt.Sprintf("%d-", *ip.FromPort)) 296 } 297 if ip.ToPort != nil && *ip.ToPort > 0 { 298 buf.WriteString(fmt.Sprintf("%d-", *ip.ToPort)) 299 } 300 buf.WriteString(fmt.Sprintf("%s-", *ip.IPProtocol)) 301 buf.WriteString(fmt.Sprintf("%s-", ruleType)) 302 303 // We need to make sure to sort the strings below so that we always 304 // generate the same hash code no matter what is in the set. 305 if len(ip.IPRanges) > 0 { 306 s := make([]string, len(ip.IPRanges)) 307 for i, r := range ip.IPRanges { 308 s[i] = *r.CIDRIP 309 } 310 sort.Strings(s) 311 312 for _, v := range s { 313 buf.WriteString(fmt.Sprintf("%s-", v)) 314 } 315 } 316 317 if len(ip.UserIDGroupPairs) > 0 { 318 sort.Sort(ByGroupPair(ip.UserIDGroupPairs)) 319 for _, pair := range ip.UserIDGroupPairs { 320 if pair.GroupID != nil { 321 buf.WriteString(fmt.Sprintf("%s-", *pair.GroupID)) 322 } else { 323 buf.WriteString("-") 324 } 325 if pair.GroupName != nil { 326 buf.WriteString(fmt.Sprintf("%s-", *pair.GroupName)) 327 } else { 328 buf.WriteString("-") 329 } 330 } 331 } 332 333 return fmt.Sprintf("sg-%d", hashcode.String(buf.String())) 334 } 335 336 func expandIPPerm(d *schema.ResourceData, sg *ec2.SecurityGroup) *ec2.IPPermission { 337 var perm ec2.IPPermission 338 339 perm.FromPort = aws.Long(int64(d.Get("from_port").(int))) 340 perm.ToPort = aws.Long(int64(d.Get("to_port").(int))) 341 perm.IPProtocol = aws.String(d.Get("protocol").(string)) 342 343 // build a group map that behaves like a set 344 groups := make(map[string]bool) 345 if raw, ok := d.GetOk("source_security_group_id"); ok { 346 groups[raw.(string)] = true 347 } 348 349 if v, ok := d.GetOk("self"); ok && v.(bool) { 350 if sg.VPCID != nil && *sg.VPCID != "" { 351 groups[*sg.GroupID] = true 352 } else { 353 groups[*sg.GroupName] = true 354 } 355 } 356 357 if len(groups) > 0 { 358 perm.UserIDGroupPairs = make([]*ec2.UserIDGroupPair, len(groups)) 359 // build string list of group name/ids 360 var gl []string 361 for k, _ := range groups { 362 gl = append(gl, k) 363 } 364 365 for i, name := range gl { 366 ownerId, id := "", name 367 if items := strings.Split(id, "/"); len(items) > 1 { 368 ownerId, id = items[0], items[1] 369 } 370 371 perm.UserIDGroupPairs[i] = &ec2.UserIDGroupPair{ 372 GroupID: aws.String(id), 373 UserID: aws.String(ownerId), 374 } 375 376 if sg.VPCID == nil || *sg.VPCID == "" { 377 perm.UserIDGroupPairs[i].GroupID = nil 378 perm.UserIDGroupPairs[i].GroupName = aws.String(id) 379 perm.UserIDGroupPairs[i].UserID = nil 380 } 381 } 382 } 383 384 if raw, ok := d.GetOk("cidr_blocks"); ok { 385 list := raw.([]interface{}) 386 perm.IPRanges = make([]*ec2.IPRange, len(list)) 387 for i, v := range list { 388 perm.IPRanges[i] = &ec2.IPRange{CIDRIP: aws.String(v.(string))} 389 } 390 } 391 392 return &perm 393 }