github.com/acm1/terraform@v0.6.2-0.20150729164239-1f314444f45c/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 d.SetId("") 156 } 157 158 var rule *ec2.IPPermission 159 ruleType := d.Get("type").(string) 160 var rl []*ec2.IPPermission 161 switch ruleType { 162 case "ingress": 163 rl = sg.IPPermissions 164 default: 165 rl = sg.IPPermissionsEgress 166 } 167 168 for _, r := range rl { 169 if d.Id() == ipPermissionIDHash(ruleType, r) { 170 rule = r 171 } 172 } 173 174 if rule == nil { 175 log.Printf("[DEBUG] Unable to find matching %s Security Group Rule for Group %s", 176 ruleType, sg_id) 177 d.SetId("") 178 return nil 179 } 180 181 d.Set("from_port", rule.FromPort) 182 d.Set("to_port", rule.ToPort) 183 d.Set("protocol", rule.IPProtocol) 184 d.Set("type", ruleType) 185 186 var cb []string 187 for _, c := range rule.IPRanges { 188 cb = append(cb, *c.CIDRIP) 189 } 190 191 d.Set("cidr_blocks", cb) 192 193 if len(rule.UserIDGroupPairs) > 0 { 194 s := rule.UserIDGroupPairs[0] 195 d.Set("source_security_group_id", *s.GroupID) 196 } 197 198 return nil 199 } 200 201 func resourceAwsSecurityGroupRuleDelete(d *schema.ResourceData, meta interface{}) error { 202 conn := meta.(*AWSClient).ec2conn 203 sg_id := d.Get("security_group_id").(string) 204 sg, err := findResourceSecurityGroup(conn, sg_id) 205 206 if err != nil { 207 return err 208 } 209 210 perm := expandIPPerm(d, sg) 211 ruleType := d.Get("type").(string) 212 switch ruleType { 213 case "ingress": 214 log.Printf("[DEBUG] Revoking rule (%s) from security group %s:\n%s", 215 "ingress", sg_id, perm) 216 req := &ec2.RevokeSecurityGroupIngressInput{ 217 GroupID: sg.GroupID, 218 IPPermissions: []*ec2.IPPermission{perm}, 219 } 220 221 _, err = conn.RevokeSecurityGroupIngress(req) 222 223 if err != nil { 224 return fmt.Errorf( 225 "Error revoking security group %s rules: %s", 226 sg_id, err) 227 } 228 case "egress": 229 230 log.Printf("[DEBUG] Revoking security group %#v %s rule: %#v", 231 sg_id, "egress", perm) 232 req := &ec2.RevokeSecurityGroupEgressInput{ 233 GroupID: sg.GroupID, 234 IPPermissions: []*ec2.IPPermission{perm}, 235 } 236 237 _, err = conn.RevokeSecurityGroupEgress(req) 238 239 if err != nil { 240 return fmt.Errorf( 241 "Error revoking security group %s rules: %s", 242 sg_id, err) 243 } 244 } 245 246 d.SetId("") 247 248 return nil 249 } 250 251 func findResourceSecurityGroup(conn *ec2.EC2, id string) (*ec2.SecurityGroup, error) { 252 req := &ec2.DescribeSecurityGroupsInput{ 253 GroupIDs: []*string{aws.String(id)}, 254 } 255 resp, err := conn.DescribeSecurityGroups(req) 256 if err != nil { 257 return nil, err 258 } 259 if resp == nil || len(resp.SecurityGroups) != 1 || resp.SecurityGroups[0] == nil { 260 return nil, fmt.Errorf( 261 "Expected to find one security group with ID %q, got: %#v", 262 id, resp.SecurityGroups) 263 } 264 265 return resp.SecurityGroups[0], nil 266 } 267 268 // ByGroupPair implements sort.Interface for []*ec2.UserIDGroupPairs based on 269 // GroupID or GroupName field (only one should be set). 270 type ByGroupPair []*ec2.UserIDGroupPair 271 272 func (b ByGroupPair) Len() int { return len(b) } 273 func (b ByGroupPair) Swap(i, j int) { b[i], b[j] = b[j], b[i] } 274 func (b ByGroupPair) Less(i, j int) bool { 275 if b[i].GroupID != nil && b[j].GroupID != nil { 276 return *b[i].GroupID < *b[j].GroupID 277 } 278 if b[i].GroupName != nil && b[j].GroupName != nil { 279 return *b[i].GroupName < *b[j].GroupName 280 } 281 282 panic("mismatched security group rules, may be a terraform bug") 283 } 284 285 func ipPermissionIDHash(ruleType string, ip *ec2.IPPermission) string { 286 var buf bytes.Buffer 287 if ip.FromPort != nil && *ip.FromPort > 0 { 288 buf.WriteString(fmt.Sprintf("%d-", *ip.FromPort)) 289 } 290 if ip.ToPort != nil && *ip.ToPort > 0 { 291 buf.WriteString(fmt.Sprintf("%d-", *ip.ToPort)) 292 } 293 buf.WriteString(fmt.Sprintf("%s-", *ip.IPProtocol)) 294 buf.WriteString(fmt.Sprintf("%s-", ruleType)) 295 296 // We need to make sure to sort the strings below so that we always 297 // generate the same hash code no matter what is in the set. 298 if len(ip.IPRanges) > 0 { 299 s := make([]string, len(ip.IPRanges)) 300 for i, r := range ip.IPRanges { 301 s[i] = *r.CIDRIP 302 } 303 sort.Strings(s) 304 305 for _, v := range s { 306 buf.WriteString(fmt.Sprintf("%s-", v)) 307 } 308 } 309 310 if len(ip.UserIDGroupPairs) > 0 { 311 sort.Sort(ByGroupPair(ip.UserIDGroupPairs)) 312 for _, pair := range ip.UserIDGroupPairs { 313 if pair.GroupID != nil { 314 buf.WriteString(fmt.Sprintf("%s-", *pair.GroupID)) 315 } else { 316 buf.WriteString("-") 317 } 318 if pair.GroupName != nil { 319 buf.WriteString(fmt.Sprintf("%s-", *pair.GroupName)) 320 } else { 321 buf.WriteString("-") 322 } 323 } 324 } 325 326 return fmt.Sprintf("sg-%d", hashcode.String(buf.String())) 327 } 328 329 func expandIPPerm(d *schema.ResourceData, sg *ec2.SecurityGroup) *ec2.IPPermission { 330 var perm ec2.IPPermission 331 332 perm.FromPort = aws.Int64(int64(d.Get("from_port").(int))) 333 perm.ToPort = aws.Int64(int64(d.Get("to_port").(int))) 334 perm.IPProtocol = aws.String(d.Get("protocol").(string)) 335 336 // build a group map that behaves like a set 337 groups := make(map[string]bool) 338 if raw, ok := d.GetOk("source_security_group_id"); ok { 339 groups[raw.(string)] = true 340 } 341 342 if v, ok := d.GetOk("self"); ok && v.(bool) { 343 if sg.VPCID != nil && *sg.VPCID != "" { 344 groups[*sg.GroupID] = true 345 } else { 346 groups[*sg.GroupName] = true 347 } 348 } 349 350 if len(groups) > 0 { 351 perm.UserIDGroupPairs = make([]*ec2.UserIDGroupPair, len(groups)) 352 // build string list of group name/ids 353 var gl []string 354 for k, _ := range groups { 355 gl = append(gl, k) 356 } 357 358 for i, name := range gl { 359 ownerId, id := "", name 360 if items := strings.Split(id, "/"); len(items) > 1 { 361 ownerId, id = items[0], items[1] 362 } 363 364 perm.UserIDGroupPairs[i] = &ec2.UserIDGroupPair{ 365 GroupID: aws.String(id), 366 UserID: aws.String(ownerId), 367 } 368 369 if sg.VPCID == nil || *sg.VPCID == "" { 370 perm.UserIDGroupPairs[i].GroupID = nil 371 perm.UserIDGroupPairs[i].GroupName = aws.String(id) 372 perm.UserIDGroupPairs[i].UserID = nil 373 } 374 } 375 } 376 377 if raw, ok := d.GetOk("cidr_blocks"); ok { 378 list := raw.([]interface{}) 379 perm.IPRanges = make([]*ec2.IPRange, len(list)) 380 for i, v := range list { 381 perm.IPRanges[i] = &ec2.IPRange{CIDRIP: aws.String(v.(string))} 382 } 383 } 384 385 return &perm 386 }