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 rule_dict = rule['security_group_rule']
115 if rule_dict['security_group_id']:
116 sec_grp = neutron_utils.get_security_group_by_id(
117 self.__neutron, rule_dict['security_group_id'])
119 sec_grp_name = sec_grp['security_group']['name']
121 setting = SecurityGroupRuleSettings(
122 description=rule_dict['description'],
123 direction=rule_dict['direction'], ethertype=rule_dict['ethertype'],
124 port_range_min=rule_dict['port_range_min'],
125 port_range_max=rule_dict['port_range_max'],
126 protocol=rule_dict['protocol'],
127 remote_group_id=rule_dict['remote_group_id'],
128 remote_ip_prefix=rule_dict['remote_ip_prefix'],
129 sec_grp_name=sec_grp_name)
134 Removes and deletes the rules then the security group.
136 for setting, rule in self.__rules.items():
138 neutron_utils.delete_security_group_rule(self.__neutron, rule)
139 except NotFound as e:
140 logger.warning('Rule not found, cannot delete - ' + str(e))
142 self.__rules = dict()
144 if self.__security_group:
146 neutron_utils.delete_security_group(self.__neutron,
147 self.__security_group)
148 except NotFound as e:
150 'Security Group not found, cannot delete - ' + str(e))
152 self.__security_group = None
154 def get_security_group(self):
156 Returns the OpenStack security group object
159 return self.__security_group
163 Returns the associated rules
168 def add_rule(self, rule_setting):
170 Adds a rule to this security group
171 :param rule_setting: the rule configuration
173 rule_setting.sec_grp_name = self.sec_grp_settings.name
174 new_rule = neutron_utils.create_security_group_rule(self.__neutron,
176 self.__rules[rule_setting] = new_rule
177 self.sec_grp_settings.rule_settings.append(rule_setting)
179 def remove_rule(self, rule_id=None, rule_setting=None):
181 Removes a rule to this security group by id, name, or rule_setting
183 :param rule_id: the rule's id
184 :param rule_setting: the rule's setting object
186 rule_to_remove = None
187 if rule_id or rule_setting:
189 rule_to_remove = neutron_utils.get_rule_by_id(
190 self.__neutron, self.__security_group, rule_id)
192 rule_to_remove = self.__rules.get(rule_setting)
195 neutron_utils.delete_security_group_rule(self.__neutron,
197 rule_setting = self.__get_setting_from_rule(rule_to_remove)
199 self.__rules.pop(rule_setting)
201 logger.warning('Rule setting is None, cannot remove rule')
203 def __get_setting_from_rule(self, rule):
205 Returns the associated RuleSetting object for a given rule
206 :param rule: the Rule object
207 :return: the associated RuleSetting object or None
209 for rule_setting in self.sec_grp_settings.rule_settings:
210 if rule_setting.rule_eq(rule):
215 class SecurityGroupSettings:
217 Class representing a keypair configuration
220 def __init__(self, **kwargs):
222 Constructor - all parameters are optional
223 :param name: The keypair name.
224 :param description: The security group's description
225 :param project_name: The name of the project under which the security
226 group will be created
229 self.name = kwargs.get('name')
230 self.description = kwargs.get('description')
231 self.project_name = kwargs.get('project_name')
232 self.rule_settings = list()
234 rule_settings = kwargs.get('rules')
235 if not rule_settings:
236 rule_settings = kwargs.get('rule_settings')
239 for rule_setting in rule_settings:
240 if isinstance(rule_setting, SecurityGroupRuleSettings):
241 self.rule_settings.append(rule_setting)
243 self.rule_settings.append(SecurityGroupRuleSettings(
247 raise Exception('The attribute name is required')
249 for rule_setting in self.rule_settings:
250 if rule_setting.sec_grp_name is not self.name:
252 'Rule settings must correspond with the name of this '
255 def dict_for_neutron(self, keystone):
257 Returns a dictionary object representing this object.
258 This is meant to be converted into JSON designed for use by the Neutron
261 TODO - expand automated testing to exercise all parameters
262 :param keystone: the Keystone client
263 :return: the dictionary object
268 out['name'] = self.name
270 out['description'] = self.description
271 if self.project_name:
272 project = keystone_utils.get_project(keystone, self.project_name)
275 project_id = project.id
277 out['project_id'] = project_id
280 'Could not find project ID for project named - ' +
283 return {'security_group': out}
286 class Direction(enum.Enum):
294 class Protocol(enum.Enum):
304 class Ethertype(enum.Enum):
312 class SecurityGroupRuleSettings:
314 Class representing a keypair configuration
317 def __init__(self, **kwargs):
319 Constructor - all parameters are optional
320 :param sec_grp_name: The security group's name on which to add the
322 :param description: The rule's description
323 :param direction: An enumeration of type
324 create_security_group.RULE_DIRECTION (required)
325 :param remote_group_id: The group ID to associate with this rule
326 (this should be changed to group name once
327 snaps support Groups) (optional)
328 :param protocol: An enumeration of type
329 create_security_group.RULE_PROTOCOL or a string value
330 that will be mapped accordingly (optional)
331 :param ethertype: An enumeration of type
332 create_security_group.RULE_ETHERTYPE (optional)
333 :param port_range_min: The minimum port number in the range that is
334 matched by the security group rule. When the
335 protocol is TCP or UDP, this value must be <=
336 port_range_max. When the protocol is ICMP, this
337 value must be an ICMP type.
338 :param port_range_max: The maximum port number in the range that is
339 matched by the security group rule. When the
340 protocol is TCP or UDP, this value must be <=
341 port_range_max. When the protocol is ICMP, this
342 value must be an ICMP type.
343 :param sec_grp_rule: The OpenStack rule object to a security group rule
345 (note: Cannot be set using the config object nor
346 can I see any real uses for this parameter)
347 :param remote_ip_prefix: The remote IP prefix to associate with this
348 metering rule packet (optional)
350 TODO - Need to support the tenant...
353 self.description = kwargs.get('description')
354 self.sec_grp_name = kwargs.get('sec_grp_name')
355 self.remote_group_id = kwargs.get('remote_group_id')
356 self.direction = None
357 if kwargs.get('direction'):
358 self.direction = map_direction(kwargs['direction'])
361 if kwargs.get('protocol'):
362 self.protocol = map_protocol(kwargs['protocol'])
364 self.protocol = Protocol.null
366 self.ethertype = None
367 if kwargs.get('ethertype'):
368 self.ethertype = map_ethertype(kwargs['ethertype'])
370 self.port_range_min = kwargs.get('port_range_min')
371 self.port_range_max = kwargs.get('port_range_max')
372 self.remote_ip_prefix = kwargs.get('remote_ip_prefix')
374 if not self.direction or not self.sec_grp_name:
375 raise Exception('direction and sec_grp_name are required')
377 def dict_for_neutron(self, neutron):
379 Returns a dictionary object representing this object.
380 This is meant to be converted into JSON designed for use by the Neutron
383 :param neutron: the neutron client for performing lookups
384 :return: the dictionary object
389 out['description'] = self.description
391 out['direction'] = self.direction.name
392 if self.port_range_min:
393 out['port_range_min'] = self.port_range_min
394 if self.port_range_max:
395 out['port_range_max'] = self.port_range_max
397 out['ethertype'] = self.ethertype.name
398 if self.protocol and self.protocol.name != 'null':
399 out['protocol'] = self.protocol.name
400 if self.sec_grp_name:
401 sec_grp = neutron_utils.get_security_group(
402 neutron, self.sec_grp_name)
404 out['security_group_id'] = sec_grp['security_group']['id']
407 'Cannot locate security group with name - ' +
409 if self.remote_group_id:
410 out['remote_group_id'] = self.remote_group_id
411 if self.remote_ip_prefix:
412 out['remote_ip_prefix'] = self.remote_ip_prefix
414 return {'security_group_rule': out}
416 def rule_eq(self, rule):
418 Returns True if this setting created the rule
419 :param rule: the rule to evaluate
422 rule_dict = rule['security_group_rule']
424 if self.description is not None:
425 if rule_dict['description'] is not None and rule_dict[
426 'description'] != '':
428 elif self.description != rule_dict['description']:
429 if rule_dict['description'] != '':
432 if self.direction.name != rule_dict['direction']:
435 if self.ethertype and rule_dict.get('ethertype'):
436 if self.ethertype.name != rule_dict['ethertype']:
439 if self.port_range_min and rule_dict.get('port_range_min'):
440 if self.port_range_min != rule_dict['port_range_min']:
443 if self.port_range_max and rule_dict.get('port_range_max'):
444 if self.port_range_max != rule_dict['port_range_max']:
447 if self.protocol and rule_dict.get('protocol'):
448 if self.protocol.name != rule_dict['protocol']:
451 if self.remote_group_id and rule_dict.get('remote_group_id'):
452 if self.remote_group_id != rule_dict['remote_group_id']:
455 if self.remote_ip_prefix and rule_dict.get('remote_ip_prefix'):
456 if self.remote_ip_prefix != rule_dict['remote_ip_prefix']:
461 def __eq__(self, other):
462 return self.description == other.description and \
463 self.direction == other.direction and \
464 self.port_range_min == other.port_range_min and \
465 self.port_range_max == other.port_range_max and \
466 self.ethertype == other.ethertype and \
467 self.protocol == other.protocol and \
468 self.sec_grp_name == other.sec_grp_name and \
469 self.remote_group_id == other.remote_group_id and \
470 self.remote_ip_prefix == other.remote_ip_prefix
473 return hash((self.sec_grp_name, self.description, self.direction,
474 self.remote_group_id,
475 self.protocol, self.ethertype, self.port_range_min,
476 self.port_range_max, self.remote_ip_prefix))
479 def map_direction(direction):
481 Takes a the direction value maps it to the Direction enum. When None return
483 :param direction: the direction value
484 :return: the Direction enum object
485 :raise: Exception if value is invalid
489 if isinstance(direction, Direction):
492 dir_str = str(direction)
493 if dir_str == 'egress':
494 return Direction.egress
495 elif dir_str == 'ingress':
496 return Direction.ingress
498 raise Exception('Invalid Direction - ' + dir_str)
501 def map_protocol(protocol):
503 Takes a the protocol value maps it to the Protocol enum. When None return
505 :param protocol: the protocol value
506 :return: the Protocol enum object
507 :raise: Exception if value is invalid
511 elif isinstance(protocol, Protocol):
514 proto_str = str(protocol)
515 if proto_str == 'icmp':
517 elif proto_str == 'tcp':
519 elif proto_str == 'udp':
521 elif proto_str == 'null':
524 raise Exception('Invalid Protocol - ' + proto_str)
527 def map_ethertype(ethertype):
529 Takes a the ethertype value maps it to the Ethertype enum. When None return
531 :param ethertype: the ethertype value
532 :return: the Ethertype enum object
533 :raise: Exception if value is invalid
537 elif isinstance(ethertype, Ethertype):
540 eth_str = str(ethertype)
541 if eth_str == 'IPv6':
542 return Ethertype.IPv6
543 elif eth_str == 'IPv4':
544 return Ethertype.IPv4
546 raise Exception('Invalid Ethertype - ' + eth_str)