summaryrefslogtreecommitdiffstats
path: root/snaps/openstack/create_security_group.py
diff options
context:
space:
mode:
Diffstat (limited to 'snaps/openstack/create_security_group.py')
-rw-r--r--snaps/openstack/create_security_group.py521
1 files changed, 521 insertions, 0 deletions
diff --git a/snaps/openstack/create_security_group.py b/snaps/openstack/create_security_group.py
new file mode 100644
index 0000000..fc1ee98
--- /dev/null
+++ b/snaps/openstack/create_security_group.py
@@ -0,0 +1,521 @@
+# Copyright (c) 2016 Cable Television Laboratories, Inc. ("CableLabs")
+# and others. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+import logging
+
+import enum
+from neutronclient.common.exceptions import NotFound
+from snaps.openstack.utils import neutron_utils
+from snaps.openstack.utils import keystone_utils
+
+__author__ = 'spisarski'
+
+logger = logging.getLogger('OpenStackSecurityGroup')
+
+
+class OpenStackSecurityGroup:
+ """
+ Class responsible for creating Security Groups
+ """
+
+ def __init__(self, os_creds, sec_grp_settings):
+ """
+ Constructor - all parameters are required
+ :param os_creds: The credentials to connect with OpenStack
+ :param sec_grp_settings: The settings used to create a security group
+ """
+ self.__os_creds = os_creds
+ self.sec_grp_settings = sec_grp_settings
+ self.__neutron = neutron_utils.neutron_client(os_creds)
+ self.__keystone = keystone_utils.keystone_client(os_creds)
+
+ # Attributes instantiated on create()
+ self.__security_group = None
+
+ # dict where the rule settings object is the key
+ self.__rules = dict()
+
+ def create(self, cleanup=False):
+ """
+ Responsible for creating the security group.
+ :param cleanup: Denotes whether or not this is being called for cleanup or not
+ :return: the OpenStack security group object
+ """
+ logger.info('Creating security group %s...' % self.sec_grp_settings.name)
+
+ self.__security_group = neutron_utils.get_security_group(self.__neutron, self.sec_grp_settings.name)
+ if not self.__security_group and not cleanup:
+ # Create the security group
+ self.__security_group = neutron_utils.create_security_group(self.__neutron, self.__keystone,
+ self.sec_grp_settings)
+
+ # Get the rules added for free
+ auto_rules = neutron_utils.get_rules_by_security_group(self.__neutron, self.__security_group)
+
+ ctr = 0
+ for auto_rule in auto_rules:
+ auto_rule_setting = self.__generate_rule_setting(auto_rule)
+ self.__rules[auto_rule_setting] = auto_rule
+ ctr += 1
+
+ # Create the custom rules
+ for sec_grp_rule_setting in self.sec_grp_settings.rule_settings:
+ custom_rule = neutron_utils.create_security_group_rule(self.__neutron, sec_grp_rule_setting)
+ self.__rules[sec_grp_rule_setting] = custom_rule
+
+ # Refresh security group object to reflect the new rules added to it
+ self.__security_group = neutron_utils.get_security_group(self.__neutron, self.sec_grp_settings.name)
+ else:
+ # Populate rules
+ existing_rules = neutron_utils.get_rules_by_security_group(self.__neutron, self.__security_group)
+
+ for existing_rule in existing_rules:
+ # For Custom Rules
+ rule_setting = self.__get_setting_from_rule(existing_rule)
+ ctr = 0
+ if not rule_setting:
+ # For Free Rules
+ rule_setting = self.__generate_rule_setting(existing_rule)
+ ctr += 1
+
+ self.__rules[rule_setting] = existing_rule
+
+ return self.__security_group
+
+ def __generate_rule_setting(self, rule):
+ """
+ Creates a SecurityGroupRuleSettings object for a given rule
+ :param rule: the rule from which to create the SecurityGroupRuleSettings object
+ :return: the newly instantiated SecurityGroupRuleSettings object
+ """
+ rule_dict = rule['security_group_rule']
+ sec_grp_name = None
+ if rule_dict['security_group_id']:
+ sec_grp = neutron_utils.get_security_group_by_id(self.__neutron, rule_dict['security_group_id'])
+ if sec_grp:
+ sec_grp_name = sec_grp['security_group']['name']
+
+ setting = SecurityGroupRuleSettings(description=rule_dict['description'],
+ direction=rule_dict['direction'], ethertype=rule_dict['ethertype'],
+ port_range_min=rule_dict['port_range_min'],
+ port_range_max=rule_dict['port_range_max'], protocol=rule_dict['protocol'],
+ remote_group_id=rule_dict['remote_group_id'],
+ remote_ip_prefix=rule_dict['remote_ip_prefix'], sec_grp_name=sec_grp_name)
+ return setting
+
+ def clean(self):
+ """
+ Removes and deletes the rules then the security group.
+ """
+ for setting, rule in self.__rules.iteritems():
+ try:
+ neutron_utils.delete_security_group_rule(self.__neutron, rule)
+ except NotFound as e:
+ logger.warn('Rule not found, cannot delete - ' + e.message)
+ pass
+ self.__rules = dict()
+
+ if self.__security_group:
+ try:
+ neutron_utils.delete_security_group(self.__neutron, self.__security_group)
+ except NotFound as e:
+ logger.warn('Security Group not found, cannot delete - ' + e.message)
+
+ self.__security_group = None
+
+ def get_security_group(self):
+ """
+ Returns the OpenStack security group object
+ :return:
+ """
+ return self.__security_group
+
+ def get_rules(self):
+ """
+ Returns the associated rules
+ :return:
+ """
+ return self.__rules
+
+ def add_rule(self, rule_setting):
+ """
+ Adds a rule to this security group
+ :param rule_setting: the rule configuration
+ """
+ rule_setting.sec_grp_name = self.sec_grp_settings.name
+ new_rule = neutron_utils.create_security_group_rule(self.__neutron, rule_setting)
+ self.__rules[rule_setting] = new_rule
+ self.sec_grp_settings.rule_settings.append(rule_setting)
+
+ def remove_rule(self, rule_id=None, rule_setting=None):
+ """
+ Removes a rule to this security group by id, name, or rule_setting object
+ :param rule_id: the rule's id
+ :param rule_setting: the rule's setting object
+ """
+ rule_to_remove = None
+ if rule_id or rule_setting:
+ if rule_id:
+ rule_to_remove = neutron_utils.get_rule_by_id(self.__neutron, self.__security_group, rule_id)
+ elif rule_setting:
+ rule_to_remove = self.__rules.get(rule_setting)
+
+ if rule_to_remove:
+ neutron_utils.delete_security_group_rule(self.__neutron, rule_to_remove)
+ rule_setting = self.__get_setting_from_rule(rule_to_remove)
+ if rule_setting:
+ self.__rules.pop(rule_setting)
+ else:
+ logger.warn('Rule setting is None, cannot remove rule')
+
+ def __get_setting_from_rule(self, rule):
+ """
+ Returns the associated RuleSetting object for a given rule
+ :param rule: the Rule object
+ :return: the associated RuleSetting object or None
+ """
+ for rule_setting in self.sec_grp_settings.rule_settings:
+ if rule_setting.rule_eq(rule):
+ return rule_setting
+ return None
+
+
+class SecurityGroupSettings:
+ """
+ Class representing a keypair configuration
+ """
+
+ def __init__(self, config=None, name=None, description=None, project_name=None,
+ rule_settings=list()):
+ """
+ Constructor - all parameters are optional
+ :param config: Should be a dict object containing the configuration settings using the attribute names below
+ as each member's the key and overrides any of the other parameters.
+ :param name: The keypair name.
+ :param description: The security group's description
+ :param project_name: The name of the project under which the security group will be created
+ :return:
+ """
+ if config:
+ self.name = config.get('name')
+ self.description = config.get('description')
+ self.project_name = config.get('project_name')
+ self.rule_settings = list()
+ if config.get('rules') and type(config['rules']) is list:
+ for config_rule in config['rules']:
+ self.rule_settings.append(SecurityGroupRuleSettings(config=config_rule))
+ else:
+ self.name = name
+ self.description = description
+ self.project_name = project_name
+ self.rule_settings = rule_settings
+
+ if not self.name:
+ raise Exception('The attribute name is required')
+
+ for rule_setting in self.rule_settings:
+ if rule_setting.sec_grp_name is not self.name:
+ raise Exception('Rule settings must correspond with the name of this security group')
+
+ def dict_for_neutron(self, keystone):
+ """
+ Returns a dictionary object representing this object.
+ This is meant to be converted into JSON designed for use by the Neutron API
+
+ TODO - expand automated testing to exercise all parameters
+ :param keystone: the Keystone client
+ :return: the dictionary object
+ """
+ out = dict()
+
+ if self.name:
+ out['name'] = self.name
+ if self.description:
+ out['description'] = self.description
+ if self.project_name:
+ project = keystone_utils.get_project(keystone, self.project_name)
+ project_id = None
+ if project:
+ project_id = project.id
+ if project_id:
+ out['project_id'] = project_id
+ else:
+ raise Exception('Could not find project ID for project named - ' + self.project_name)
+
+ return {'security_group': out}
+
+
+class Direction(enum.Enum):
+ """
+ A rule's direction
+ """
+ ingress = 'ingress'
+ egress = 'egress'
+
+
+class Protocol(enum.Enum):
+ """
+ A rule's protocol
+ """
+ icmp = 'icmp'
+ tcp = 'tcp'
+ udp = 'udp'
+ null = 'null'
+
+
+class Ethertype(enum.Enum):
+ """
+ A rule's ethertype
+ """
+ IPv4 = 4
+ IPv6 = 6
+
+
+class SecurityGroupRuleSettings:
+ """
+ Class representing a keypair configuration
+ """
+
+ def __init__(self, config=None, sec_grp_name=None, description=None, direction=None,
+ remote_group_id=None, protocol=None, ethertype=None, port_range_min=None, port_range_max=None,
+ sec_grp_rule=None, remote_ip_prefix=None):
+ """
+ Constructor - all parameters are optional
+ :param config: Should be a dict object containing the configuration settings using the attribute names below
+ as each member's the key and overrides any of the other parameters.
+ :param sec_grp_name: The security group's name on which to add the rule. (required)
+ :param description: The rule's description
+ :param direction: An enumeration of type create_security_group.RULE_DIRECTION (required)
+ :param remote_group_id: The group ID to associate with this rule (this should be changed to group name
+ once snaps support Groups) (optional)
+ :param protocol: An enumeration of type create_security_group.RULE_PROTOCOL or a string value that will be
+ mapped accordingly (optional)
+ :param ethertype: An enumeration of type create_security_group.RULE_ETHERTYPE (optional)
+ :param port_range_min: The minimum port number in the range that is matched by the security group rule. When
+ the protocol is TCP or UDP, this value must be <= port_range_max. When the protocol is
+ ICMP, this value must be an ICMP type.
+ :param port_range_max: The maximum port number in the range that is matched by the security group rule. When
+ the protocol is TCP or UDP, this value must be <= port_range_max. When the protocol is
+ ICMP, this value must be an ICMP type.
+ :param sec_grp_rule: The OpenStack rule object to a security group rule object to associate
+ (note: Cannot be set using the config object nor can I see any real uses for this
+ parameter)
+ :param remote_ip_prefix: The remote IP prefix to associate with this metering rule packet (optional)
+
+ TODO - Need to support the tenant...
+ """
+
+ if config:
+ self.description = config.get('description')
+ self.sec_grp_name = config.get('sec_grp_name')
+ self.remote_group_id = config.get('remote_group_id')
+ self.direction = None
+ if config.get('direction'):
+ self.direction = map_direction(config['direction'])
+
+ self.protocol = None
+ if config.get('protocol'):
+ self.protocol = map_protocol(config['protocol'])
+ else:
+ self.protocol = Protocol.null
+
+ self.ethertype = None
+ if config.get('ethertype'):
+ self.ethertype = map_ethertype(config['ethertype'])
+
+ self.port_range_min = config.get('port_range_min')
+ self.port_range_max = config.get('port_range_max')
+ self.remote_ip_prefix = config.get('remote_ip_prefix')
+ else:
+ self.description = description
+ self.sec_grp_name = sec_grp_name
+ self.remote_group_id = remote_group_id
+ self.direction = map_direction(direction)
+ self.protocol = map_protocol(protocol)
+ self.ethertype = map_ethertype(ethertype)
+ self.port_range_min = port_range_min
+ self.port_range_max = port_range_max
+ self.sec_grp_rule = sec_grp_rule
+ self.remote_ip_prefix = remote_ip_prefix
+
+ if not self.direction or not self.sec_grp_name:
+ raise Exception('direction and sec_grp_name are required')
+
+ def dict_for_neutron(self, neutron):
+ """
+ Returns a dictionary object representing this object.
+ This is meant to be converted into JSON designed for use by the Neutron API
+
+ :param neutron: the neutron client for performing lookups
+ :return: the dictionary object
+ """
+ out = dict()
+
+ if self.description:
+ out['description'] = self.description
+ if self.direction:
+ out['direction'] = self.direction.name
+ if self.port_range_min:
+ out['port_range_min'] = self.port_range_min
+ if self.port_range_max:
+ out['port_range_max'] = self.port_range_max
+ if self.ethertype:
+ out['ethertype'] = self.ethertype.name
+ if self.protocol:
+ out['protocol'] = self.protocol.name
+ if self.sec_grp_name:
+ sec_grp = neutron_utils.get_security_group(neutron, self.sec_grp_name)
+ if sec_grp:
+ out['security_group_id'] = sec_grp['security_group']['id']
+ else:
+ raise Exception('Cannot locate security group with name - ' + self.sec_grp_name)
+ if self.remote_group_id:
+ out['remote_group_id'] = self.remote_group_id
+ if self.sec_grp_rule:
+ out['security_group_rule'] = self.sec_grp_rule
+ if self.remote_ip_prefix:
+ out['remote_ip_prefix'] = self.remote_ip_prefix
+
+ return {'security_group_rule': out}
+
+ def rule_eq(self, rule):
+ """
+ Returns True if this setting created the rule
+ :param rule: the rule to evaluate
+ :return: T/F
+ """
+ rule_dict = rule['security_group_rule']
+
+ if self.description is not None:
+ if rule_dict['description'] is not None and rule_dict['description'] != '':
+ return False
+ elif self.description != rule_dict['description']:
+ if rule_dict['description'] != '':
+ return False
+
+ if self.direction.name != rule_dict['direction']:
+ return False
+
+ if self.ethertype and rule_dict.get('ethertype'):
+ if self.ethertype.name != rule_dict['ethertype']:
+ return False
+
+ if self.port_range_min and rule_dict.get('port_range_min'):
+ if self.port_range_min != rule_dict['port_range_min']:
+ return False
+
+ if self.port_range_max and rule_dict.get('port_range_max'):
+ if self.port_range_max != rule_dict['port_range_max']:
+ return False
+
+ if self.protocol and rule_dict.get('protocol'):
+ if self.protocol.name != rule_dict['protocol']:
+ return False
+
+ if self.remote_group_id and rule_dict.get('remote_group_id'):
+ if self.remote_group_id != rule_dict['remote_group_id']:
+ return False
+
+ if self.remote_ip_prefix and rule_dict.get('remote_ip_prefix'):
+ if self.remote_ip_prefix != rule_dict['remote_ip_prefix']:
+ return False
+
+ return True
+
+ def __eq__(self, other):
+ return self.description == other.description and \
+ self.direction == other.direction and \
+ self.port_range_min == other.port_range_min and \
+ self.port_range_max == other.port_range_max and \
+ self.ethertype == other.ethertype and \
+ self.protocol == other.protocol and \
+ self.sec_grp_name == other.sec_grp_name and \
+ self.remote_group_id == other.remote_group_id and \
+ self.sec_grp_rule == other.sec_grp_rule and \
+ self.remote_ip_prefix == other.remote_ip_prefix
+
+ def __hash__(self):
+ return hash((self.sec_grp_name, self.description, self.direction, self.remote_group_id,
+ self.protocol, self.ethertype, self.port_range_min, self.port_range_max, self.sec_grp_rule,
+ self.remote_ip_prefix))
+
+
+def map_direction(direction):
+ """
+ Takes a the direction value maps it to the Direction enum. When None return None
+ :param direction: the direction value
+ :return: the Direction enum object
+ :raise: Exception if value is invalid
+ """
+ if not direction:
+ return None
+ if type(direction) is Direction:
+ return direction
+ elif isinstance(direction, basestring):
+ if direction == 'egress':
+ return Direction.egress
+ elif direction == 'ingress':
+ return Direction.ingress
+ else:
+ raise Exception('Invalid Direction - ' + direction)
+ else:
+ raise Exception('Invalid Direction object - ' + str(direction))
+
+
+def map_protocol(protocol):
+ """
+ Takes a the protocol value maps it to the Protocol enum. When None return None
+ :param protocol: the protocol value
+ :return: the Protocol enum object
+ :raise: Exception if value is invalid
+ """
+ if not protocol:
+ return None
+ elif type(protocol) is Protocol:
+ return protocol
+ elif isinstance(protocol, basestring):
+ if protocol == 'icmp':
+ return Protocol.icmp
+ elif protocol == 'tcp':
+ return Protocol.tcp
+ elif protocol == 'udp':
+ return Protocol.udp
+ elif protocol == 'null':
+ return Protocol.null
+ else:
+ raise Exception('Invalid Protocol - ' + protocol)
+ else:
+ raise Exception('Invalid Protocol object - ' + str(protocol))
+
+
+def map_ethertype(ethertype):
+ """
+ Takes a the ethertype value maps it to the Ethertype enum. When None return None
+ :param ethertype: the ethertype value
+ :return: the Ethertype enum object
+ :raise: Exception if value is invalid
+ """
+ if not ethertype:
+ return None
+ elif type(ethertype) is Ethertype:
+ return ethertype
+ elif isinstance(ethertype, basestring):
+ if ethertype == 'IPv6':
+ return Ethertype.IPv6
+ elif ethertype == 'IPv4':
+ return Ethertype.IPv4
+ else:
+ raise Exception('Invalid Ethertype - ' + ethertype)
+ else:
+ raise Exception('Invalid Ethertype object - ' + str(ethertype))