1 # Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
2 # and others. All rights reserved.
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at:
8 # http://www.apache.org/licenses/LICENSE-2.0
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
18 from neutronclient.common.exceptions import NotFound
19 from snaps.openstack.utils import keystone_utils
20 from snaps.openstack.utils import neutron_utils
22 __author__ = 'spisarski'
24 logger = logging.getLogger('OpenStackSecurityGroup')
27 class OpenStackSecurityGroup:
29 Class responsible for creating Security Groups
32 def __init__(self, os_creds, sec_grp_settings):
34 Constructor - all parameters are required
35 :param os_creds: The credentials to connect with OpenStack
36 :param sec_grp_settings: The settings used to create a security group
38 self.__os_creds = os_creds
39 self.sec_grp_settings = sec_grp_settings
41 self.__keystone = None
43 # Attributes instantiated on create()
44 self.__security_group = None
46 # dict where the rule settings object is the key
49 def create(self, cleanup=False):
51 Responsible for creating the security group.
52 :param cleanup: Denotes whether or not this is being called for cleanup
53 :return: the OpenStack security group object
55 self.__neutron = neutron_utils.neutron_client(self.__os_creds)
56 self.__keystone = keystone_utils.keystone_client(self.__os_creds)
59 'Creating security group %s...' % self.sec_grp_settings.name)
61 self.__security_group = neutron_utils.get_security_group(
62 self.__neutron, self.sec_grp_settings.name)
63 if not self.__security_group and not cleanup:
64 # Create the security group
65 self.__security_group = neutron_utils.create_security_group(
66 self.__neutron, self.__keystone,
67 self.sec_grp_settings)
69 # Get the rules added for free
70 auto_rules = neutron_utils.get_rules_by_security_group(
71 self.__neutron, self.__security_group)
74 for auto_rule in auto_rules:
75 auto_rule_setting = self.__generate_rule_setting(auto_rule)
76 self.__rules[auto_rule_setting] = auto_rule
79 # Create the custom rules
80 for sec_grp_rule_setting in self.sec_grp_settings.rule_settings:
81 custom_rule = neutron_utils.create_security_group_rule(
82 self.__neutron, sec_grp_rule_setting)
83 self.__rules[sec_grp_rule_setting] = custom_rule
85 # Refresh security group object to reflect the new rules added
86 self.__security_group = neutron_utils.get_security_group(
87 self.__neutron, self.sec_grp_settings.name)
90 existing_rules = neutron_utils.get_rules_by_security_group(
91 self.__neutron, self.__security_group)
93 for existing_rule in existing_rules:
95 rule_setting = self.__get_setting_from_rule(existing_rule)
99 rule_setting = self.__generate_rule_setting(existing_rule)
102 self.__rules[rule_setting] = existing_rule
104 return self.__security_group
106 def __generate_rule_setting(self, rule):
108 Creates a SecurityGroupRuleSettings object for a given rule
109 :param rule: the rule from which to create the
110 SecurityGroupRuleSettings object
111 :return: the newly instantiated SecurityGroupRuleSettings object
113 sec_grp = neutron_utils.get_security_group_by_id(
114 self.__neutron, rule.security_group_id)
116 setting = SecurityGroupRuleSettings(
117 description=rule.description,
118 direction=rule.direction,
119 ethertype=rule.ethertype,
120 port_range_min=rule.port_range_min,
121 port_range_max=rule.port_range_max,
122 protocol=rule.protocol,
123 remote_group_id=rule.remote_group_id,
124 remote_ip_prefix=rule.remote_ip_prefix,
125 sec_grp_name=sec_grp.name)
130 Removes and deletes the rules then the security group.
132 for setting, rule in self.__rules.items():
134 neutron_utils.delete_security_group_rule(self.__neutron, rule)
135 except NotFound as e:
136 logger.warning('Rule not found, cannot delete - ' + str(e))
138 self.__rules = dict()
140 if self.__security_group:
142 neutron_utils.delete_security_group(self.__neutron,
143 self.__security_group)
144 except NotFound as e:
146 'Security Group not found, cannot delete - ' + str(e))
148 self.__security_group = None
150 def get_security_group(self):
152 Returns the OpenStack security group object
155 return self.__security_group
159 Returns the associated rules
164 def add_rule(self, rule_setting):
166 Adds a rule to this security group
167 :param rule_setting: the rule configuration
169 rule_setting.sec_grp_name = self.sec_grp_settings.name
170 new_rule = neutron_utils.create_security_group_rule(self.__neutron,
172 self.__rules[rule_setting] = new_rule
173 self.sec_grp_settings.rule_settings.append(rule_setting)
175 def remove_rule(self, rule_id=None, rule_setting=None):
177 Removes a rule to this security group by id, name, or rule_setting
179 :param rule_id: the rule's id
180 :param rule_setting: the rule's setting object
182 rule_to_remove = None
183 if rule_id or rule_setting:
185 rule_to_remove = neutron_utils.get_rule_by_id(
186 self.__neutron, self.__security_group, rule_id)
188 rule_to_remove = self.__rules.get(rule_setting)
191 neutron_utils.delete_security_group_rule(self.__neutron,
193 rule_setting = self.__get_setting_from_rule(rule_to_remove)
195 self.__rules.pop(rule_setting)
197 logger.warning('Rule setting is None, cannot remove rule')
199 def __get_setting_from_rule(self, rule):
201 Returns the associated RuleSetting object for a given rule
202 :param rule: the Rule object
203 :return: the associated RuleSetting object or None
205 for rule_setting in self.sec_grp_settings.rule_settings:
206 if rule_setting.rule_eq(rule):
211 class SecurityGroupSettings:
213 Class representing a keypair configuration
216 def __init__(self, **kwargs):
218 Constructor - all parameters are optional
219 :param name: The keypair name.
220 :param description: The security group's description
221 :param project_name: The name of the project under which the security
222 group will be created
225 self.name = kwargs.get('name')
226 self.description = kwargs.get('description')
227 self.project_name = kwargs.get('project_name')
228 self.rule_settings = list()
230 rule_settings = kwargs.get('rules')
231 if not rule_settings:
232 rule_settings = kwargs.get('rule_settings')
235 for rule_setting in rule_settings:
236 if isinstance(rule_setting, SecurityGroupRuleSettings):
237 self.rule_settings.append(rule_setting)
239 self.rule_settings.append(SecurityGroupRuleSettings(
243 raise SecurityGroupSettingsError('The attribute name is required')
245 for rule_setting in self.rule_settings:
246 if rule_setting.sec_grp_name is not self.name:
247 raise SecurityGroupSettingsError(
248 'Rule settings must correspond with the name of this '
251 def dict_for_neutron(self, keystone):
253 Returns a dictionary object representing this object.
254 This is meant to be converted into JSON designed for use by the Neutron
257 TODO - expand automated testing to exercise all parameters
258 :param keystone: the Keystone client
259 :return: the dictionary object
264 out['name'] = self.name
266 out['description'] = self.description
267 if self.project_name:
268 project = keystone_utils.get_project(
269 keystone=keystone, project_name=self.project_name)
272 project_id = project.id
274 out['tenant_id'] = project_id
276 raise SecurityGroupSettingsError(
277 'Could not find project ID for project named - ' +
280 return {'security_group': out}
283 class Direction(enum.Enum):
291 class Protocol(enum.Enum):
301 class Ethertype(enum.Enum):
309 class SecurityGroupSettingsError(Exception):
311 Exception to be thrown when security group settings attributes are
316 class SecurityGroupRuleSettings:
318 Class representing a keypair configuration
321 def __init__(self, **kwargs):
323 Constructor - all parameters are optional
324 :param sec_grp_name: The security group's name on which to add the
326 :param description: The rule's description
327 :param direction: An enumeration of type
328 create_security_group.RULE_DIRECTION (required)
329 :param remote_group_id: The group ID to associate with this rule
330 (this should be changed to group name once
331 snaps support Groups) (optional)
332 :param protocol: An enumeration of type
333 create_security_group.RULE_PROTOCOL or a string value
334 that will be mapped accordingly (optional)
335 :param ethertype: An enumeration of type
336 create_security_group.RULE_ETHERTYPE (optional)
337 :param port_range_min: The minimum port number in the range that is
338 matched by the security group rule. When the
339 protocol is TCP or UDP, this value must be <=
340 port_range_max. When the protocol is ICMP, this
341 value must be an ICMP type.
342 :param port_range_max: The maximum port number in the range that is
343 matched by the security group rule. When the
344 protocol is TCP or UDP, this value must be <=
345 port_range_max. When the protocol is ICMP, this
346 value must be an ICMP type.
347 :param sec_grp_rule: The OpenStack rule object to a security group rule
349 (note: Cannot be set using the config object nor
350 can I see any real uses for this parameter)
351 :param remote_ip_prefix: The remote IP prefix to associate with this
352 metering rule packet (optional)
354 TODO - Need to support the tenant...
357 self.description = kwargs.get('description')
358 self.sec_grp_name = kwargs.get('sec_grp_name')
359 self.remote_group_id = kwargs.get('remote_group_id')
360 self.direction = None
361 if kwargs.get('direction'):
362 self.direction = map_direction(kwargs['direction'])
365 if kwargs.get('protocol'):
366 self.protocol = map_protocol(kwargs['protocol'])
368 self.protocol = Protocol.null
370 self.ethertype = None
371 if kwargs.get('ethertype'):
372 self.ethertype = map_ethertype(kwargs['ethertype'])
374 self.port_range_min = kwargs.get('port_range_min')
375 self.port_range_max = kwargs.get('port_range_max')
376 self.remote_ip_prefix = kwargs.get('remote_ip_prefix')
378 if not self.direction or not self.sec_grp_name:
379 raise SecurityGroupRuleSettingsError(
380 'direction and sec_grp_name are required')
382 def dict_for_neutron(self, neutron):
384 Returns a dictionary object representing this object.
385 This is meant to be converted into JSON designed for use by the Neutron
388 :param neutron: the neutron client for performing lookups
389 :return: the dictionary object
394 out['description'] = self.description
396 out['direction'] = self.direction.name
397 if self.port_range_min:
398 out['port_range_min'] = self.port_range_min
399 if self.port_range_max:
400 out['port_range_max'] = self.port_range_max
402 out['ethertype'] = self.ethertype.name
403 if self.protocol and self.protocol.name != 'null':
404 out['protocol'] = self.protocol.name
405 if self.sec_grp_name:
406 sec_grp = neutron_utils.get_security_group(
407 neutron, self.sec_grp_name)
409 out['security_group_id'] = sec_grp.id
411 raise SecurityGroupRuleSettingsError(
412 'Cannot locate security group with name - ' +
414 if self.remote_group_id:
415 out['remote_group_id'] = self.remote_group_id
416 if self.remote_ip_prefix:
417 out['remote_ip_prefix'] = self.remote_ip_prefix
419 return {'security_group_rule': out}
421 def rule_eq(self, rule):
423 Returns True if this setting created the rule
424 :param rule: the rule to evaluate
427 if self.description is not None:
428 if (rule.description is not None and
429 rule.description != ''):
431 elif self.description != rule.description:
432 if rule.description != '':
435 if self.direction.name != rule.direction:
438 if self.ethertype and rule.ethertype:
439 if self.ethertype.name != rule.ethertype:
442 if self.port_range_min and rule.port_range_min:
443 if self.port_range_min != rule.port_range_min:
446 if self.port_range_max and rule.port_range_max:
447 if self.port_range_max != rule.port_range_max:
450 if self.protocol and rule.protocol:
451 if self.protocol.name != rule.protocol:
454 if self.remote_group_id and rule.remote_group_id:
455 if self.remote_group_id != rule.remote_group_id:
458 if self.remote_ip_prefix and rule.remote_ip_prefix:
459 if self.remote_ip_prefix != rule.remote_ip_prefix:
464 def __eq__(self, other):
466 self.description == other.description and
467 self.direction == other.direction and
468 self.port_range_min == other.port_range_min and
469 self.port_range_max == other.port_range_max and
470 self.ethertype == other.ethertype and
471 self.protocol == other.protocol and
472 self.sec_grp_name == other.sec_grp_name and
473 self.remote_group_id == other.remote_group_id and
474 self.remote_ip_prefix == other.remote_ip_prefix)
477 return hash((self.sec_grp_name, self.description, self.direction,
478 self.remote_group_id,
479 self.protocol, self.ethertype, self.port_range_min,
480 self.port_range_max, self.remote_ip_prefix))
483 def map_direction(direction):
485 Takes a the direction value maps it to the Direction enum. When None return
487 :param direction: the direction value
488 :return: the Direction enum object
489 :raise: Exception if value is invalid
493 if isinstance(direction, Direction):
496 dir_str = str(direction)
497 if dir_str == 'egress':
498 return Direction.egress
499 elif dir_str == 'ingress':
500 return Direction.ingress
502 raise SecurityGroupRuleSettingsError(
503 'Invalid Direction - ' + dir_str)
506 def map_protocol(protocol):
508 Takes a the protocol value maps it to the Protocol enum. When None return
510 :param protocol: the protocol value
511 :return: the Protocol enum object
512 :raise: Exception if value is invalid
516 elif isinstance(protocol, Protocol):
519 proto_str = str(protocol)
520 if proto_str == 'icmp':
522 elif proto_str == 'tcp':
524 elif proto_str == 'udp':
526 elif proto_str == 'null':
529 raise SecurityGroupRuleSettingsError(
530 'Invalid Protocol - ' + proto_str)
533 def map_ethertype(ethertype):
535 Takes a the ethertype value maps it to the Ethertype enum. When None return
537 :param ethertype: the ethertype value
538 :return: the Ethertype enum object
539 :raise: Exception if value is invalid
543 elif isinstance(ethertype, Ethertype):
546 eth_str = str(ethertype)
547 if eth_str == 'IPv6':
548 return Ethertype.IPv6
549 elif eth_str == 'IPv4':
550 return Ethertype.IPv4
552 raise SecurityGroupRuleSettingsError(
553 'Invalid Ethertype - ' + eth_str)
556 class SecurityGroupRuleSettingsError(Exception):
558 Exception to be thrown when security group rule settings attributes are