github.com/bendemaree/terraform@v0.5.4-0.20150613200311-f50d97d6eee6/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 Schema: map[string]*schema.Schema{ 25 "type": &schema.Schema{ 26 Type: schema.TypeString, 27 Required: true, 28 ForceNew: true, 29 Description: "Type of rule, ingress (inbound) or egress (outbound).", 30 }, 31 32 "from_port": &schema.Schema{ 33 Type: schema.TypeInt, 34 Required: true, 35 ForceNew: true, 36 }, 37 38 "to_port": &schema.Schema{ 39 Type: schema.TypeInt, 40 Required: true, 41 ForceNew: true, 42 }, 43 44 "protocol": &schema.Schema{ 45 Type: schema.TypeString, 46 Required: true, 47 ForceNew: true, 48 }, 49 50 "cidr_blocks": &schema.Schema{ 51 Type: schema.TypeList, 52 Optional: true, 53 ForceNew: true, 54 Elem: &schema.Schema{Type: schema.TypeString}, 55 }, 56 57 "security_group_id": &schema.Schema{ 58 Type: schema.TypeString, 59 Required: true, 60 ForceNew: true, 61 }, 62 63 "source_security_group_id": &schema.Schema{ 64 Type: schema.TypeString, 65 Optional: true, 66 ForceNew: true, 67 Computed: true, 68 }, 69 70 "self": &schema.Schema{ 71 Type: schema.TypeBool, 72 Optional: true, 73 Default: false, 74 ForceNew: true, 75 }, 76 }, 77 } 78 } 79 80 func resourceAwsSecurityGroupRuleCreate(d *schema.ResourceData, meta interface{}) error { 81 conn := meta.(*AWSClient).ec2conn 82 sg_id := d.Get("security_group_id").(string) 83 sg, err := findResourceSecurityGroup(conn, sg_id) 84 85 if err != nil { 86 return fmt.Errorf("sorry") 87 } 88 89 perm := expandIPPerm(d, sg) 90 91 ruleType := d.Get("type").(string) 92 93 switch ruleType { 94 case "ingress": 95 log.Printf("[DEBUG] Authorizing security group %s %s rule: %s", 96 sg_id, "Ingress", awsutil.StringValue(perm)) 97 98 req := &ec2.AuthorizeSecurityGroupIngressInput{ 99 GroupID: sg.GroupID, 100 IPPermissions: []*ec2.IPPermission{perm}, 101 } 102 103 if sg.VPCID == nil || *sg.VPCID == "" { 104 req.GroupID = nil 105 req.GroupName = sg.GroupName 106 } 107 108 _, err := conn.AuthorizeSecurityGroupIngress(req) 109 110 if err != nil { 111 return fmt.Errorf( 112 "Error authorizing security group %s rules: %s", 113 "rules", err) 114 } 115 116 case "egress": 117 log.Printf("[DEBUG] Authorizing security group %s %s rule: %#v", 118 sg_id, "Egress", perm) 119 120 req := &ec2.AuthorizeSecurityGroupEgressInput{ 121 GroupID: sg.GroupID, 122 IPPermissions: []*ec2.IPPermission{perm}, 123 } 124 125 _, err = conn.AuthorizeSecurityGroupEgress(req) 126 127 if err != nil { 128 return fmt.Errorf( 129 "Error authorizing security group %s rules: %s", 130 "rules", err) 131 } 132 133 default: 134 return fmt.Errorf("Security Group Rule must be type 'ingress' or type 'egress'") 135 } 136 137 d.SetId(ipPermissionIDHash(ruleType, perm)) 138 139 return resourceAwsSecurityGroupRuleRead(d, meta) 140 } 141 142 func resourceAwsSecurityGroupRuleRead(d *schema.ResourceData, meta interface{}) error { 143 conn := meta.(*AWSClient).ec2conn 144 sg_id := d.Get("security_group_id").(string) 145 sg, err := findResourceSecurityGroup(conn, sg_id) 146 if err != nil { 147 d.SetId("") 148 } 149 150 var rule *ec2.IPPermission 151 ruleType := d.Get("type").(string) 152 var rl []*ec2.IPPermission 153 switch ruleType { 154 case "ingress": 155 rl = sg.IPPermissions 156 default: 157 rl = sg.IPPermissionsEgress 158 } 159 160 for _, r := range rl { 161 if d.Id() == ipPermissionIDHash(ruleType, r) { 162 rule = r 163 } 164 } 165 166 if rule == nil { 167 log.Printf("[DEBUG] Unable to find matching %s Security Group Rule for Group %s", 168 ruleType, sg_id) 169 d.SetId("") 170 return nil 171 } 172 173 d.Set("from_port", rule.FromPort) 174 d.Set("to_port", rule.ToPort) 175 d.Set("protocol", rule.IPProtocol) 176 d.Set("type", ruleType) 177 178 var cb []string 179 for _, c := range rule.IPRanges { 180 cb = append(cb, *c.CIDRIP) 181 } 182 183 d.Set("cidr_blocks", cb) 184 185 if len(rule.UserIDGroupPairs) > 0 { 186 s := rule.UserIDGroupPairs[0] 187 d.Set("source_security_group_id", *s.GroupID) 188 } 189 190 return nil 191 } 192 193 func resourceAwsSecurityGroupRuleDelete(d *schema.ResourceData, meta interface{}) error { 194 conn := meta.(*AWSClient).ec2conn 195 sg_id := d.Get("security_group_id").(string) 196 sg, err := findResourceSecurityGroup(conn, sg_id) 197 198 if err != nil { 199 return fmt.Errorf("sorry") 200 } 201 202 perm := expandIPPerm(d, sg) 203 ruleType := d.Get("type").(string) 204 switch ruleType { 205 case "ingress": 206 log.Printf("[DEBUG] Revoking rule (%s) from security group %s:\n%s", 207 "ingress", sg_id, awsutil.StringValue(perm)) 208 req := &ec2.RevokeSecurityGroupIngressInput{ 209 GroupID: sg.GroupID, 210 IPPermissions: []*ec2.IPPermission{perm}, 211 } 212 213 _, err = conn.RevokeSecurityGroupIngress(req) 214 215 if err != nil { 216 return fmt.Errorf( 217 "Error revoking security group %s rules: %s", 218 sg_id, err) 219 } 220 case "egress": 221 222 log.Printf("[DEBUG] Revoking security group %#v %s rule: %#v", 223 sg_id, "egress", perm) 224 req := &ec2.RevokeSecurityGroupEgressInput{ 225 GroupID: sg.GroupID, 226 IPPermissions: []*ec2.IPPermission{perm}, 227 } 228 229 _, err = conn.RevokeSecurityGroupEgress(req) 230 231 if err != nil { 232 return fmt.Errorf( 233 "Error revoking security group %s rules: %s", 234 sg_id, err) 235 } 236 } 237 238 d.SetId("") 239 240 return nil 241 } 242 243 func findResourceSecurityGroup(conn *ec2.EC2, id string) (*ec2.SecurityGroup, error) { 244 req := &ec2.DescribeSecurityGroupsInput{ 245 GroupIDs: []*string{aws.String(id)}, 246 } 247 resp, err := conn.DescribeSecurityGroups(req) 248 if err != nil { 249 if ec2err, ok := err.(awserr.Error); ok { 250 if ec2err.Code() == "InvalidSecurityGroupID.NotFound" || 251 ec2err.Code() == "InvalidGroup.NotFound" { 252 resp = nil 253 err = nil 254 } 255 } 256 257 if err != nil { 258 log.Printf("Error on findResourceSecurityGroup: %s", err) 259 return nil, err 260 } 261 } 262 263 return resp.SecurityGroups[0], nil 264 } 265 266 func ipPermissionIDHash(ruleType string, ip *ec2.IPPermission) string { 267 var buf bytes.Buffer 268 // for egress rules, an TCP rule of -1 is automatically added, in which case 269 // the to and from ports will be nil. We don't record this rule locally. 270 if ip.IPProtocol != nil && *ip.IPProtocol != "-1" { 271 buf.WriteString(fmt.Sprintf("%d-", *ip.FromPort)) 272 buf.WriteString(fmt.Sprintf("%d-", *ip.ToPort)) 273 buf.WriteString(fmt.Sprintf("%s-", *ip.IPProtocol)) 274 } 275 buf.WriteString(fmt.Sprintf("%s-", ruleType)) 276 277 // We need to make sure to sort the strings below so that we always 278 // generate the same hash code no matter what is in the set. 279 if len(ip.IPRanges) > 0 { 280 s := make([]string, len(ip.IPRanges)) 281 for i, r := range ip.IPRanges { 282 s[i] = *r.CIDRIP 283 } 284 sort.Strings(s) 285 286 for _, v := range s { 287 buf.WriteString(fmt.Sprintf("%s-", v)) 288 } 289 } 290 291 return fmt.Sprintf("sg-%d", hashcode.String(buf.String())) 292 } 293 294 func expandIPPerm(d *schema.ResourceData, sg *ec2.SecurityGroup) *ec2.IPPermission { 295 var perm ec2.IPPermission 296 297 perm.FromPort = aws.Long(int64(d.Get("from_port").(int))) 298 perm.ToPort = aws.Long(int64(d.Get("to_port").(int))) 299 perm.IPProtocol = aws.String(d.Get("protocol").(string)) 300 301 // build a group map that behaves like a set 302 groups := make(map[string]bool) 303 if raw, ok := d.GetOk("source_security_group_id"); ok { 304 groups[raw.(string)] = true 305 } 306 307 if v, ok := d.GetOk("self"); ok && v.(bool) { 308 if sg.VPCID != nil && *sg.VPCID != "" { 309 groups[*sg.GroupID] = true 310 } else { 311 groups[*sg.GroupName] = true 312 } 313 } 314 315 if len(groups) > 0 { 316 perm.UserIDGroupPairs = make([]*ec2.UserIDGroupPair, len(groups)) 317 // build string list of group name/ids 318 var gl []string 319 for k, _ := range groups { 320 gl = append(gl, k) 321 } 322 323 for i, name := range gl { 324 ownerId, id := "", name 325 if items := strings.Split(id, "/"); len(items) > 1 { 326 ownerId, id = items[0], items[1] 327 } 328 329 perm.UserIDGroupPairs[i] = &ec2.UserIDGroupPair{ 330 GroupID: aws.String(id), 331 UserID: aws.String(ownerId), 332 } 333 334 if sg.VPCID == nil || *sg.VPCID == "" { 335 perm.UserIDGroupPairs[i].GroupID = nil 336 perm.UserIDGroupPairs[i].GroupName = aws.String(id) 337 perm.UserIDGroupPairs[i].UserID = nil 338 } 339 } 340 } 341 342 if raw, ok := d.GetOk("cidr_blocks"); ok { 343 list := raw.([]interface{}) 344 perm.IPRanges = make([]*ec2.IPRange, len(list)) 345 for i, v := range list { 346 perm.IPRanges[i] = &ec2.IPRange{CIDRIP: aws.String(v.(string))} 347 } 348 } 349 350 return &perm 351 }