1# Copyright (c) 2006-2011 Mitch Garnaat http://garnaat.org/ 2# Copyright (c) 2011, Eucalyptus Systems, Inc. 3# 4# Permission is hereby granted, free of charge, to any person obtaining a 5# copy of this software and associated documentation files (the 6# "Software"), to deal in the Software without restriction, including 7# without limitation the rights to use, copy, modify, merge, publish, dis- 8# tribute, sublicense, and/or sell copies of the Software, and to permit 9# persons to whom the Software is furnished to do so, subject to the fol- 10# lowing conditions: 11# 12# The above copyright notice and this permission notice shall be included 13# in all copies or substantial portions of the Software. 14# 15# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 16# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- 17# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 18# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 19# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 21# IN THE SOFTWARE. 22 23""" 24Represents an EC2 Security Group 25""" 26from boto.ec2.ec2object import TaggedEC2Object 27from boto.exception import BotoClientError 28 29 30class SecurityGroup(TaggedEC2Object): 31 32 def __init__(self, connection=None, owner_id=None, 33 name=None, description=None, id=None): 34 super(SecurityGroup, self).__init__(connection) 35 self.id = id 36 self.owner_id = owner_id 37 self.name = name 38 self.description = description 39 self.vpc_id = None 40 self.rules = IPPermissionsList() 41 self.rules_egress = IPPermissionsList() 42 43 def __repr__(self): 44 return 'SecurityGroup:%s' % self.name 45 46 def startElement(self, name, attrs, connection): 47 retval = super(SecurityGroup, self).startElement(name, attrs, connection) 48 if retval is not None: 49 return retval 50 if name == 'ipPermissions': 51 return self.rules 52 elif name == 'ipPermissionsEgress': 53 return self.rules_egress 54 else: 55 return None 56 57 def endElement(self, name, value, connection): 58 if name == 'ownerId': 59 self.owner_id = value 60 elif name == 'groupId': 61 self.id = value 62 elif name == 'groupName': 63 self.name = value 64 elif name == 'vpcId': 65 self.vpc_id = value 66 elif name == 'groupDescription': 67 self.description = value 68 elif name == 'ipRanges': 69 pass 70 elif name == 'return': 71 if value == 'false': 72 self.status = False 73 elif value == 'true': 74 self.status = True 75 else: 76 raise Exception( 77 'Unexpected value of status %s for group %s' % ( 78 value, 79 self.name 80 ) 81 ) 82 else: 83 setattr(self, name, value) 84 85 def delete(self, dry_run=False): 86 if self.vpc_id: 87 return self.connection.delete_security_group( 88 group_id=self.id, 89 dry_run=dry_run 90 ) 91 else: 92 return self.connection.delete_security_group( 93 self.name, 94 dry_run=dry_run 95 ) 96 97 def add_rule(self, ip_protocol, from_port, to_port, 98 src_group_name, src_group_owner_id, cidr_ip, 99 src_group_group_id, dry_run=False): 100 """ 101 Add a rule to the SecurityGroup object. Note that this method 102 only changes the local version of the object. No information 103 is sent to EC2. 104 """ 105 rule = IPPermissions(self) 106 rule.ip_protocol = ip_protocol 107 rule.from_port = from_port 108 rule.to_port = to_port 109 self.rules.append(rule) 110 rule.add_grant( 111 src_group_name, 112 src_group_owner_id, 113 cidr_ip, 114 src_group_group_id, 115 dry_run=dry_run 116 ) 117 118 def remove_rule(self, ip_protocol, from_port, to_port, 119 src_group_name, src_group_owner_id, cidr_ip, 120 src_group_group_id, dry_run=False): 121 """ 122 Remove a rule to the SecurityGroup object. Note that this method 123 only changes the local version of the object. No information 124 is sent to EC2. 125 """ 126 if not self.rules: 127 raise ValueError("The security group has no rules") 128 129 target_rule = None 130 for rule in self.rules: 131 if rule.ip_protocol == ip_protocol: 132 if rule.from_port == from_port: 133 if rule.to_port == to_port: 134 target_rule = rule 135 target_grant = None 136 for grant in rule.grants: 137 if grant.name == src_group_name or grant.group_id == src_group_group_id: 138 if grant.owner_id == src_group_owner_id: 139 if grant.cidr_ip == cidr_ip: 140 target_grant = grant 141 if target_grant: 142 rule.grants.remove(target_grant) 143 if len(rule.grants) == 0: 144 self.rules.remove(target_rule) 145 146 def authorize(self, ip_protocol=None, from_port=None, to_port=None, 147 cidr_ip=None, src_group=None, dry_run=False): 148 """ 149 Add a new rule to this security group. 150 You need to pass in either src_group_name 151 OR ip_protocol, from_port, to_port, 152 and cidr_ip. In other words, either you are authorizing another 153 group or you are authorizing some ip-based rule. 154 155 :type ip_protocol: string 156 :param ip_protocol: Either tcp | udp | icmp 157 158 :type from_port: int 159 :param from_port: The beginning port number you are enabling 160 161 :type to_port: int 162 :param to_port: The ending port number you are enabling 163 164 :type cidr_ip: string or list of strings 165 :param cidr_ip: The CIDR block you are providing access to. 166 See http://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing 167 168 :type src_group: :class:`boto.ec2.securitygroup.SecurityGroup` or 169 :class:`boto.ec2.securitygroup.GroupOrCIDR` 170 :param src_group: The Security Group you are granting access to. 171 172 :rtype: bool 173 :return: True if successful. 174 """ 175 group_name = None 176 if not self.vpc_id: 177 group_name = self.name 178 group_id = None 179 if self.vpc_id: 180 group_id = self.id 181 src_group_name = None 182 src_group_owner_id = None 183 src_group_group_id = None 184 if src_group: 185 cidr_ip = None 186 src_group_owner_id = src_group.owner_id 187 if not self.vpc_id: 188 src_group_name = src_group.name 189 else: 190 if hasattr(src_group, 'group_id'): 191 src_group_group_id = src_group.group_id 192 else: 193 src_group_group_id = src_group.id 194 status = self.connection.authorize_security_group(group_name, 195 src_group_name, 196 src_group_owner_id, 197 ip_protocol, 198 from_port, 199 to_port, 200 cidr_ip, 201 group_id, 202 src_group_group_id, 203 dry_run=dry_run) 204 if status: 205 if not isinstance(cidr_ip, list): 206 cidr_ip = [cidr_ip] 207 for single_cidr_ip in cidr_ip: 208 self.add_rule(ip_protocol, from_port, to_port, src_group_name, 209 src_group_owner_id, single_cidr_ip, 210 src_group_group_id, dry_run=dry_run) 211 return status 212 213 def revoke(self, ip_protocol=None, from_port=None, to_port=None, 214 cidr_ip=None, src_group=None, dry_run=False): 215 group_name = None 216 if not self.vpc_id: 217 group_name = self.name 218 group_id = None 219 if self.vpc_id: 220 group_id = self.id 221 src_group_name = None 222 src_group_owner_id = None 223 src_group_group_id = None 224 if src_group: 225 cidr_ip = None 226 src_group_owner_id = src_group.owner_id 227 if not self.vpc_id: 228 src_group_name = src_group.name 229 else: 230 if hasattr(src_group, 'group_id'): 231 src_group_group_id = src_group.group_id 232 else: 233 src_group_group_id = src_group.id 234 status = self.connection.revoke_security_group(group_name, 235 src_group_name, 236 src_group_owner_id, 237 ip_protocol, 238 from_port, 239 to_port, 240 cidr_ip, 241 group_id, 242 src_group_group_id, 243 dry_run=dry_run) 244 if status: 245 self.remove_rule(ip_protocol, from_port, to_port, src_group_name, 246 src_group_owner_id, cidr_ip, src_group_group_id, 247 dry_run=dry_run) 248 return status 249 250 def copy_to_region(self, region, name=None, dry_run=False): 251 """ 252 Create a copy of this security group in another region. 253 Note that the new security group will be a separate entity 254 and will not stay in sync automatically after the copy 255 operation. 256 257 :type region: :class:`boto.ec2.regioninfo.RegionInfo` 258 :param region: The region to which this security group will be copied. 259 260 :type name: string 261 :param name: The name of the copy. If not supplied, the copy 262 will have the same name as this security group. 263 264 :rtype: :class:`boto.ec2.securitygroup.SecurityGroup` 265 :return: The new security group. 266 """ 267 if region.name == self.region: 268 raise BotoClientError('Unable to copy to the same Region') 269 conn_params = self.connection.get_params() 270 rconn = region.connect(**conn_params) 271 sg = rconn.create_security_group( 272 name or self.name, 273 self.description, 274 dry_run=dry_run 275 ) 276 source_groups = [] 277 for rule in self.rules: 278 for grant in rule.grants: 279 grant_nom = grant.name or grant.group_id 280 if grant_nom: 281 if grant_nom not in source_groups: 282 source_groups.append(grant_nom) 283 sg.authorize(None, None, None, None, grant, 284 dry_run=dry_run) 285 else: 286 sg.authorize(rule.ip_protocol, rule.from_port, rule.to_port, 287 grant.cidr_ip, dry_run=dry_run) 288 return sg 289 290 def instances(self, dry_run=False): 291 """ 292 Find all of the current instances that are running within this 293 security group. 294 295 :rtype: list of :class:`boto.ec2.instance.Instance` 296 :return: A list of Instance objects 297 """ 298 rs = [] 299 if self.vpc_id: 300 rs.extend(self.connection.get_all_reservations( 301 filters={'instance.group-id': self.id}, 302 dry_run=dry_run 303 )) 304 else: 305 rs.extend(self.connection.get_all_reservations( 306 filters={'group-id': self.id}, 307 dry_run=dry_run 308 )) 309 instances = [i for r in rs for i in r.instances] 310 return instances 311 312 313class IPPermissionsList(list): 314 315 def startElement(self, name, attrs, connection): 316 if name == 'item': 317 self.append(IPPermissions(self)) 318 return self[-1] 319 return None 320 321 def endElement(self, name, value, connection): 322 pass 323 324 325class IPPermissions(object): 326 327 def __init__(self, parent=None): 328 self.parent = parent 329 self.ip_protocol = None 330 self.from_port = None 331 self.to_port = None 332 self.grants = [] 333 334 def __repr__(self): 335 return 'IPPermissions:%s(%s-%s)' % (self.ip_protocol, 336 self.from_port, self.to_port) 337 338 def startElement(self, name, attrs, connection): 339 if name == 'item': 340 self.grants.append(GroupOrCIDR(self)) 341 return self.grants[-1] 342 return None 343 344 def endElement(self, name, value, connection): 345 if name == 'ipProtocol': 346 self.ip_protocol = value 347 elif name == 'fromPort': 348 self.from_port = value 349 elif name == 'toPort': 350 self.to_port = value 351 else: 352 setattr(self, name, value) 353 354 def add_grant(self, name=None, owner_id=None, cidr_ip=None, group_id=None, 355 dry_run=False): 356 grant = GroupOrCIDR(self) 357 grant.owner_id = owner_id 358 grant.group_id = group_id 359 grant.name = name 360 grant.cidr_ip = cidr_ip 361 self.grants.append(grant) 362 return grant 363 364 365class GroupOrCIDR(object): 366 367 def __init__(self, parent=None): 368 self.owner_id = None 369 self.group_id = None 370 self.name = None 371 self.cidr_ip = None 372 373 def __repr__(self): 374 if self.cidr_ip: 375 return '%s' % self.cidr_ip 376 else: 377 return '%s-%s' % (self.name or self.group_id, self.owner_id) 378 379 def startElement(self, name, attrs, connection): 380 return None 381 382 def endElement(self, name, value, connection): 383 if name == 'userId': 384 self.owner_id = value 385 elif name == 'groupId': 386 self.group_id = value 387 elif name == 'groupName': 388 self.name = value 389 if name == 'cidrIp': 390 self.cidr_ip = value 391 else: 392 setattr(self, name, value) 393