def enum_group_members(self, groups, group_types): """ Check group membership. Groups reference only the type of element and the names of those members. If the element is being created, skip the existence check. If the member is not being created, attempt to fetch the member and save to cache. Return the cache. Check cache.missing before continuing to ensure all required elements are found. :param list groups: list of groups extracted from elements :param list group_types: all supported group types by name :rtype: Cache """ to_be_created = { } # dict of element by type: set([names]) to be created. # If groups reference elements that are to be created, skip validating. # Otherwise check for existence for element in self.elements: for typeof, values in element.items(): if typeof not in group_types: to_be_created.setdefault(typeof, set()).add(values.get('name')) cache = Cache() for group in groups: for _, values in group.items(): members = {} if values.get( 'members') is None else values['members'] for typeof, member in members.items(): for name in member: if name not in to_be_created.get(typeof, set()): cache._add_entry(typeof, name) return cache
def exec_module(self, **kwargs): state = kwargs.pop('state', 'present') for name, value in kwargs.items(): setattr(self, name, value) changed = False try: if state == 'present': self.cache = Cache() self.check_elements() # Any missing dependencies.. if self.cache.missing: self.fail(msg='Missing dependent elements that are not created within this ' 'playbook: %s' % self.cache.missing) deferrals, elements = self.resolve_references(self.elements) if self.cache.missing: self.fail(msg='Missing elements that have a dependency, cannot continue: %s' % self.cache.missing) if self.check_mode: return self.results for element in elements: if self.create_or_update_element(element): changed = True if deferrals: _, ospf_elements = self.resolve_references(deferrals) for element in ospf_elements: if self.create_or_update_element(element): changed = True else: # No need to validate elements beyond type and name for element in self.elements: for typeof, values in element.items(): klazz = lookup_class(typeof) name = values.get('name') if name: try: klazz(name).delete() result = {'name': name, 'type': klazz.typeof, 'action': 'deleted'} changed = True except SMCException as e: result = {'name': name, 'type': klazz.typeof, 'action': 'failed to delete ' 'with reason: %s' % str(e)} finally: self.results['state'].append(result) except SMCException as err: self.fail(msg=str(err), exception=traceback.format_exc()) self.results['changed'] = changed return self.results
def exec_module(self, **kwargs): state = kwargs.pop('state', 'present') for name, value in kwargs.items(): setattr(self, name, value) changed = False engine = self.fetch_element(Engine) if not engine: self.fail(msg='Engine specified cannot be found: %s' % self.name) self.cache = Cache() try: if state == 'present': self.validate_elements(engine) if self.cache.missing: self.fail(msg='Missing element dependencies, cannot continue: %s' % self.cache.missing) if self.check_mode: return self.results if self.update_routing(engine): changed = True if self.update_antispoofing(engine): changed = True else: # No need to validate an elements type, instead just check for the # type and name of the element to remove from the interface routing if self.delete_routing(engine): changed = True except SMCException as e: self.fail(msg=str(e)) self.results['changed'] = changed return self.results
def exec_module(self, **kwargs): state = kwargs.pop('state', 'present') for name, value in kwargs.items(): setattr(self, name, value) changed = False try: # Now that we have valid option settings, we can hit the db if self.policy: policy = FirewallPolicy.get(self.policy) else: policy = FirewallSubPolicy.get(self.sub_policy) if state == 'present': for rule in self.rules: try: validate_rule_syntax(rule) except Exception as e: self.fail(msg=str(e)) self.cache = Cache() for rule in self.rules: # Resolve elements if they exist, calls to SMC could happen here if 'sources' in rule: self.field_resolver(rule.get('sources'), rule_targets) if 'destinations' in rule: self.field_resolver(rule.get('destinations'), rule_targets) if 'services' in rule: self.field_resolver(rule.get('services'), service_targets) if 'vpn_policy' in rule: self.cache._add_entry('vpn', rule.get('vpn_policy')) if 'sub_policy' in rule: self.cache._add_entry('sub_ipv4_fw_policy', rule.get('sub_policy')) if 'authentication_options' in rule: auth = rule['authentication_options'] if auth.get('require_auth'): for method in auth.get('methods'): self.cache._add_entry('authentication_service', method) for accounts in ('users', 'groups'): self.cache._add_user_entries(accounts, auth.get(accounts, [])) if self.cache.missing: self.fail(msg='Missing required elements that are referenced in this ' 'configuration: %s' % self.cache.missing) if self.check_mode: return self.results for rule in self.rules: rule_dict = {} if 'log_options' in rule: log_options = LogOptions() _log = rule['log_options'] for name, value in log_options.items(): if name not in _log: log_options.pop(name) log_options.update(rule.get('log_options', {})) rule_dict.update(log_options=log_options) if 'connection_tracking' in rule: connection_tracking = ConnectionTracking() _ct = rule['connection_tracking'] for name, value in connection_tracking.items(): if name not in _ct: connection_tracking.pop(name) connection_tracking.update(rule.get('connection_tracking',{})) rule_dict.update(connection_tracking=connection_tracking) action = Action() # If no action, set to default based on version if 'action' not in rule: action.action = 'allow' if not is_sixdotsix_compat() else ['allow'] else: action.action = rule.get('action') if 'inspection_options' in rule: _inspection = rule['inspection_options'] for option in inspection_options: if option in _inspection: action[option] = _inspection.get(option) if 'authentication_options' in rule: _auth_options = rule['authentication_options'] auth_options = AuthenticationOptions() if _auth_options.get('require_auth'): auth_options.update(methods=[ self.get_value('authentication_service', m).href for m in _auth_options.get('methods', [])], require_auth=True) auth_options.update(users=[entry.href for entry in self.cache.get_type('user_element')]) rule_dict.update(authentication_options=auth_options) rule_dict.update(action=action) for field in ('sources', 'destinations', 'services'): rule_dict[field] = self.get_values(rule.get(field, None)) rule_dict.update( vpn_policy=self.get_value('vpn', rule.get('vpn_policy')), sub_policy=self.get_value('sub_ipv4_fw_policy', rule.get('sub_policy')), mobile_vpn=rule.get('mobile_vpn', False)) if 'comment' in rule: rule_dict.update(comment=rule.get('comment')) rule_dict.update( name=rule.get('name'), is_disabled=rule.get('is_disabled', False)) if 'tag' not in rule: # If no tag is present, this is a create rule_dict.update( before=rule.get('add_before'), after=rule.get('add_after')) rule = policy.fw_ipv4_access_rules.create(**rule_dict) changed = True self.results['state'].append({ 'rule': rule.name, 'type': rule.typeof, 'action': 'created'}) else: # Modify as rule has 'tag' defined. Fetch the rule first # by it's tag reference, skip if tag not found target_rule = self.rule_by_tag(policy, rule.get('tag')) if not target_rule: continue changes = compare_rules(target_rule, rule_dict) # Changes have already been merged if any if rule.get('add_after', None): rule_at_pos = self.rule_by_tag(policy, rule.get('add_after')) if rule_at_pos: target_rule.move_rule_after(rule_at_pos) changes.append('add_after') elif rule.get('add_before', None): rule_at_pos = self.rule_by_tag(policy, rule.get('add_before')) if rule_at_pos: target_rule.move_rule_before(rule_at_pos) changes.append('add_before') elif changes: target_rule.save() if changes: changed = True self.results['state'].append({ 'rule': target_rule.name, 'type': target_rule.typeof, 'action': 'modified', 'changes': changes}) elif state == 'absent': for rule in self.rules: if 'tag' in rule: target_rule = self.rule_by_tag(policy, rule.get('tag')) if target_rule: target_rule.delete() changed = True self.results['state'].append({ 'rule': target_rule.name, 'type': target_rule.typeof, 'action': 'deleted'}) except SMCException as err: self.fail(msg=str(err), exception=traceback.format_exc()) self.results['changed'] = changed return self.results
class FirewallRule(ForcepointModuleBase): def __init__(self): self.module_args = dict( policy=dict(type='str'), sub_policy=dict(type='str'), rules=dict(type='list', default=[]), state=dict(default='present', type='str', choices=['present', 'absent']) ) self.policy = None self.sub_policy = None self.rules = None mutually_exclusive = [ ['policy', 'sub_policy'], ] required_one_of = [ [ 'policy', 'sub_policy' ] ] self.results = dict( changed=False, state=[] ) super(FirewallRule, self).__init__(self.module_args, mutually_exclusive=mutually_exclusive, required_one_of=required_one_of, supports_check_mode=True) def exec_module(self, **kwargs): state = kwargs.pop('state', 'present') for name, value in kwargs.items(): setattr(self, name, value) changed = False try: # Now that we have valid option settings, we can hit the db if self.policy: policy = FirewallPolicy.get(self.policy) else: policy = FirewallSubPolicy.get(self.sub_policy) if state == 'present': for rule in self.rules: try: validate_rule_syntax(rule) except Exception as e: self.fail(msg=str(e)) self.cache = Cache() for rule in self.rules: # Resolve elements if they exist, calls to SMC could happen here if 'sources' in rule: self.field_resolver(rule.get('sources'), rule_targets) if 'destinations' in rule: self.field_resolver(rule.get('destinations'), rule_targets) if 'services' in rule: self.field_resolver(rule.get('services'), service_targets) if 'vpn_policy' in rule: self.cache._add_entry('vpn', rule.get('vpn_policy')) if 'sub_policy' in rule: self.cache._add_entry('sub_ipv4_fw_policy', rule.get('sub_policy')) if 'authentication_options' in rule: auth = rule['authentication_options'] if auth.get('require_auth'): for method in auth.get('methods'): self.cache._add_entry('authentication_service', method) for accounts in ('users', 'groups'): self.cache._add_user_entries(accounts, auth.get(accounts, [])) if self.cache.missing: self.fail(msg='Missing required elements that are referenced in this ' 'configuration: %s' % self.cache.missing) if self.check_mode: return self.results for rule in self.rules: rule_dict = {} if 'log_options' in rule: log_options = LogOptions() _log = rule['log_options'] for name, value in log_options.items(): if name not in _log: log_options.pop(name) log_options.update(rule.get('log_options', {})) rule_dict.update(log_options=log_options) if 'connection_tracking' in rule: connection_tracking = ConnectionTracking() _ct = rule['connection_tracking'] for name, value in connection_tracking.items(): if name not in _ct: connection_tracking.pop(name) connection_tracking.update(rule.get('connection_tracking',{})) rule_dict.update(connection_tracking=connection_tracking) action = Action() # If no action, set to default based on version if 'action' not in rule: action.action = 'allow' if not is_sixdotsix_compat() else ['allow'] else: action.action = rule.get('action') if 'inspection_options' in rule: _inspection = rule['inspection_options'] for option in inspection_options: if option in _inspection: action[option] = _inspection.get(option) if 'authentication_options' in rule: _auth_options = rule['authentication_options'] auth_options = AuthenticationOptions() if _auth_options.get('require_auth'): auth_options.update(methods=[ self.get_value('authentication_service', m).href for m in _auth_options.get('methods', [])], require_auth=True) auth_options.update(users=[entry.href for entry in self.cache.get_type('user_element')]) rule_dict.update(authentication_options=auth_options) rule_dict.update(action=action) for field in ('sources', 'destinations', 'services'): rule_dict[field] = self.get_values(rule.get(field, None)) rule_dict.update( vpn_policy=self.get_value('vpn', rule.get('vpn_policy')), sub_policy=self.get_value('sub_ipv4_fw_policy', rule.get('sub_policy')), mobile_vpn=rule.get('mobile_vpn', False)) if 'comment' in rule: rule_dict.update(comment=rule.get('comment')) rule_dict.update( name=rule.get('name'), is_disabled=rule.get('is_disabled', False)) if 'tag' not in rule: # If no tag is present, this is a create rule_dict.update( before=rule.get('add_before'), after=rule.get('add_after')) rule = policy.fw_ipv4_access_rules.create(**rule_dict) changed = True self.results['state'].append({ 'rule': rule.name, 'type': rule.typeof, 'action': 'created'}) else: # Modify as rule has 'tag' defined. Fetch the rule first # by it's tag reference, skip if tag not found target_rule = self.rule_by_tag(policy, rule.get('tag')) if not target_rule: continue changes = compare_rules(target_rule, rule_dict) # Changes have already been merged if any if rule.get('add_after', None): rule_at_pos = self.rule_by_tag(policy, rule.get('add_after')) if rule_at_pos: target_rule.move_rule_after(rule_at_pos) changes.append('add_after') elif rule.get('add_before', None): rule_at_pos = self.rule_by_tag(policy, rule.get('add_before')) if rule_at_pos: target_rule.move_rule_before(rule_at_pos) changes.append('add_before') elif changes: target_rule.save() if changes: changed = True self.results['state'].append({ 'rule': target_rule.name, 'type': target_rule.typeof, 'action': 'modified', 'changes': changes}) elif state == 'absent': for rule in self.rules: if 'tag' in rule: target_rule = self.rule_by_tag(policy, rule.get('tag')) if target_rule: target_rule.delete() changed = True self.results['state'].append({ 'rule': target_rule.name, 'type': target_rule.typeof, 'action': 'deleted'}) except SMCException as err: self.fail(msg=str(err), exception=traceback.format_exc()) self.results['changed'] = changed return self.results def rule_by_tag(self, policy, tag): """ Get the rule referenced by it's tag. Tag will be in format '1234566.0'. When doing a search_rule, you must omit the part after the dot(.) to find the rule. :param FirewallPolicy policy: policy reference :param str tag: tag :rtype: Rule or None """ resolved_tag = get_tag(tag) if resolved_tag: rule = policy.search_rule('@{}'.format(resolved_tag)) return rule[0] if rule else None def field_resolver(self, elements, types): """ Field resolver, specific to retrieving network or service level elements in different formats. If elements are referencing existing elements, they will be loaded in the cache for retrieval. Format #1, as list (elements are expected to exist): - tcp_service: - service1 - service2 Format #2, as dict, only used for specifying any: any: true Format #3, if you have retrieved the rule and the sources, services, and destinations are in href format, pass through. - sources: - http://1.1.1.1/elements/host/1 - http://1.1.1.1/elements/host/2 ... .. note:: This is optimal if sources, destinations or services are not being changed as it will not result in queries to SMC. This is accomplished by not setting `expand` when running the facts module as_yaml. :param list elements: list of elements as parsed from YAML file :param dict type_dict: type dictionary for elements that should be supported for this run. """ if isinstance(elements, dict): if 'any' in elements or 'none' in elements: return for name, value in elements.items(): if name not in types: self.fail(msg='Invalid element type specified: %s. Valid ' 'types are: %s' % (name, list(types))) if not isinstance(value, list): self.fail(msg='Elements specified for type: %s should be in list ' 'format, got: %s' % (name, type(value))) self.cache.add_many([elements]) elif isinstance(elements, list): for entry in elements: if not isinstance(entry, string_types) or not entry.startswith('http'): self.fail(msg='List entry is expected to be the raw href of ' 'the element. Received: %s' % entry) def get_value(self, typeof, element): """ Get single value from cache :param str typeof: typeof element by key :param str element to fetch """ return self.cache.get(typeof, element) def get_values(self, elements): """ Get the values for source, destination and service cells. If these are not provided, return 'any'. """ if not elements: return 'any' if isinstance(elements, dict): if 'any' in elements: return 'any' elif 'none' in elements: return None # Resolve out of cache, return as Element return [self.cache.get(typeof, value) for typeof, values in elements.items() for value in values] elif isinstance(elements, list): return elements
def exec_module(self, **kwargs): state = kwargs.pop('state', 'present') for name, value in kwargs.items(): setattr(self, name, value) changed = False engine = self.fetch_element(FirewallCluster) if state == 'present': # Resolve dependencies if not engine: if not self.interfaces: self.fail(msg='You must provide at least one interface ' 'configuration to create a cluster.') if not self.primary_mgt: self.fail(msg='You must provide a primary_mgt interface to create ' 'an engine.') if not self.cluster_mode: self.fail(msg='You must define a cluster mode to create an engine') itf = self.check_interfaces() # Management interface if not self.primary_mgt in itf: self.fail(msg='Management interface is not defined. Management was ' 'specified on interface: %s' % self.primary_mgt) if engine and self.interfaces and not self.skip_interfaces: itf = self.check_interfaces() cache = Cache() # SNMP settings if self.snmp and self.snmp.get('enabled', True): cache._add_entry('snmp_agent', self.snmp.get('snmp_agent', None)) if cache.missing: self.fail(msg='SNMP configured but the SNMP Agent specified is not ' 'found: %s' % cache.missing) # Only validate BGP if it's specifically set to enabled if self.bgp and self.bgp.get('enabled', True): # BGP Profile if specified if self.bgp.get('bgp_profile', None): cache._add_entry('bgp_profile', self.bgp['bgp_profile']) if cache.missing: self.fail(msg='A BGP Profile was specified that does not exist: ' '%s' % self.bgp['bgp_profile']) # Get external bgp peers, can be type 'engine' or 'external_bgp_peer' # Can also be empty if you don't want to attach a peer. peerings = self.bgp.get('bgp_peering', []) for peer in peerings: if 'name' not in peer: self.fail(msg='BGP Peering requires a name field to identify the ' 'BGP Peering element. Peer info provided: %s' % peer) if 'external_bgp_peer' not in peer and 'engine' not in peer: self.fail(msg='Missing the external_bgp_peer or engine parameter ' 'which defines the next hop for the BGP Peering') if 'external_bgp_peer' in peer: cache._add_entry('external_bgp_peer', peer['external_bgp_peer']) elif 'engine' in peer: cache._add_entry('fw_cluster', peer['engine']) if cache.missing: self.fail(msg='Missing external BGP Peering elements: %s' % cache.missing) as_system = self.bgp.get('autonomous_system', {}) if not engine: # We are trying to enable BGP on a new engine, Autonomous System # is required if not as_system: self.fail(msg='You must specify an Autonomous System when enabling ' 'BGP on a newly created engine.') if as_system: if 'name' not in as_system or 'as_number' not in as_system: self.fail(msg='Autonomous System requires a name and and ' 'as_number value.') spoofing = self.bgp.get('antispoofing_network', {}) self.validate_antispoofing_network(spoofing) cache.add(spoofing) if cache.missing: self.fail(msg='Missing elements in antispoofing configuration: %s' % cache.missing) networks = self.bgp.get('announced_network', []) announced_networks = self.validate_and_extract_announced(networks) cache.add(announced_networks) if cache.missing: self.fail(msg='Missing elements in announced configuration: %s' % cache.missing) self.cache = cache try: if state == 'present': if not engine: interfaces = [vars(intf) for intf in itf] cluster = {'interfaces': interfaces} cluster.update( name=self.name, cluster_mode=self.cluster_mode, primary_mgt=self.primary_mgt, backup_mgt=self.backup_mgt, primary_heartbeat=self.primary_heartbeat, log_server_ref=self.log_server, domain_server_address=self.domain_server_address, default_nat=self.default_nat, enable_antivirus=self.antivirus, enable_gti=self.file_reputation, location_ref=self.location, snmp=self.snmp, comment=self.comment) if self.check_mode: return self.results engine = FirewallCluster.create_bulk(**cluster) changed = True else: # Engine exists, check for modifications # Changes made up to check mode are done on the # cached instance of the engine and not sent to SMC if self.update_general(engine): changed = True if self.update_snmp(engine): changed = True if self.cluster_mode and engine.cluster_mode != self.cluster_mode: engine.data.update(cluster_mode=self.cluster_mode) changed = True if self.check_mode: return self.results # Check engine location value if self.update_location(engine): changed = True # First actual engine update happens here if changed: engine.update() # Reset management interfaces before operating on interfaces # in case interfaces are removed that might have previously # been used as interface options (primary mgt, etc) if self.reset_management(engine): changed = True # Set skip interfaces to bypass interface checks if not self.skip_interfaces: self.update_interfaces(engine) # Lastly, delete top level interfaces that are not defined in # the YAML or added while looping. Only delete if skip_interfaces # was not provided and that delete_undefined_interfaces is set to True if not self.skip_interfaces and self.delete_undefined_interfaces: self.check_for_deletes(engine) ###### # Check for BGP configuration on either newly created engine # or on the existing ###### if self.bgp: bgp = engine.bgp enabled = self.bgp.get('enabled', True) if not enabled and bgp.status: bgp.disable() changed = True elif enabled: if self.update_bgp(bgp): autonomous_system, created = get_or_create_asystem( self.bgp.get('autonomous_system')) if created: changed = True bgp.disable() # Reset BGP configuration bgp.enable( autonomous_system, announced_networks=[], antispoofing_networks=self.antispoofing_format(), router_id=self.bgp.get('router_id', ''), bgp_profile=self.cache.get('bgp_profile', self.bgp.get('bgp_profile', None))) for network in self.announced_network_format(): bgp.advertise_network(**network) changed = True if changed: engine.update() if enabled: # BGP Peering is last since the BGP configuration may be placed # on interfaces that might have been modified or added. It is # possible that this could fail peerings = self.bgp.get('bgp_peering', None) if peerings: for peer in peerings: peering, created = get_or_create_bgp_peering( peer.pop('name')) if created: changed = True # Update the peering on the interface if self.update_bgp_peering(engine, peering, peer): changed = True if self.tags: if self.add_tags(engine, self.tags): changed = True else: if self.clear_tags(engine): changed = True self.results['engine'] = engine.data.data elif state == 'absent': if engine: engine.delete() changed = True except SMCException as err: self.fail(msg=str(err), exception=traceback.format_exc()) if not changed and self.results.get('state'): changed = True self.results['changed'] = changed return self.results
class ForcepointEngineRouting(ForcepointModuleBase): def __init__(self): self.module_args = dict( name=dict(type='str', required=True), bgp_peering=dict(type='list', default=[]), ospfv2_area=dict(type='list', default=[]), netlink=dict(type='list', default=[]), static_route=dict(type='list', default=[]), antispoofing_network=dict(type='list', default=[]), state=dict(default='present', type='str', choices=['present', 'absent']) ) self.results = dict( changed=False, state=[] ) super(ForcepointEngineRouting, self).__init__(self.module_args, supports_check_mode=True) def exec_module(self, **kwargs): state = kwargs.pop('state', 'present') for name, value in kwargs.items(): setattr(self, name, value) changed = False engine = self.fetch_element(Engine) if not engine: self.fail(msg='Engine specified cannot be found: %s' % self.name) self.cache = Cache() try: if state == 'present': self.validate_elements(engine) if self.cache.missing: self.fail(msg='Missing element dependencies, cannot continue: %s' % self.cache.missing) if self.check_mode: return self.results if self.update_routing(engine): changed = True if self.update_antispoofing(engine): changed = True else: # No need to validate an elements type, instead just check for the # type and name of the element to remove from the interface routing if self.delete_routing(engine): changed = True except SMCException as e: self.fail(msg=str(e)) self.results['changed'] = changed return self.results def delete_routing(self, engine): """ Iterate through specified routes and remove and log the state. Catch exceptions so overall state can be reported after run. :rtype: bool """ changed = False for element in route_element: for it in getattr(self, element): if 'interface_id' not in it: break try: if 'antispoofing_network' in element: iface = engine.antispoofing.get(it.get('interface_id')) for dest in it.get('destination', []): klazz = lookup_class(dest.get('type')) modified = iface.remove(klazz(dest.get('name'))) if modified: self.results['state'].append( dict(action='deleted', name=dest.get('name'), type='antispoofing')) changed = True else: if 'name' not in it: break iface = engine.routing.get(it.get('interface_id')) klazz = lookup_class(element) if element != 'static_route'\ else lookup_class('router') modified = iface.remove_route_gateway(klazz(it.get('name'))) if modified: self.results['state'].append( dict(action='deleted', name=it.get('name'), type=klazz.typeof)) changed = True except InterfaceNotFound: self.results['state'].append( dict(action='Cannot find specified interface', name=it.get('interface_id'), type='interface')) except SMCException as e: self.results['state'].append( dict(action='Failed to delete with reason: %s' % str(e), name=it.get('name'), type=klazz.typeof)) return changed def update_antispoofing(self, engine): """ Update antispoofing on the engine :param Engine engine: engine ref :return: Whether routing was changed or not :rtype: bool """ changed = False for it in self.antispoofing_network: routing_node = engine.antispoofing.get(it.get('interface_id')) for destination in it.get('destination', []): element = self.cache.get( destination.get('type'), destination.get('name')) modified = routing_node.add(element) if modified: self.results['state'].append( {'name': 'interface %s' % it.get('interface_id'), 'type': 'antispoofing', 'action': 'updated'}) changed = True return changed def update_routing(self, engine): """ Generic update for each routing element type. :param Engine engine: engine ref :return: Whether routing was changed or not :rtype: bool """ mapper = {'bgp_peering': 'add_bgp_peering', 'ospfv2_area': 'add_ospf_area', 'netlink': 'add_traffic_handler', 'static_route': 'add_static_route'} changed = False for routing in mapper.keys(): for it in getattr(self, routing): routing_node = engine.routing.get(it.get('interface_id')) # Static Routes are expected to have Router elements element = self.cache.get(routing, it.get('name')) if 'static_route' \ not in routing else self.cache.get('router', it.get('name')) destinations = [] if 'destination' in it: destinations = [self.cache.get(dest.get('type'), dest.get('name')) for dest in it.get('destination', [])] # BGP Peering and OSPF areas are non-list destinations if routing in ('ospfv2_area', 'bgp_peering'): destinations = destinations[0] # TODO: Implement unicast ref for OSPF #if 'communication_mode' in it: # kwargs = {'unicast_ref': self.cache.get( # dest.get('host'), dest.get('unicast_ref'))} # Order of args: element, destination, network # destination and network can be None or [] modified = getattr(routing_node, mapper.get(routing))( element, destinations, it.get('network')) if modified: self.results['state'].append( {'name': 'interface %s' % it.get('interface_id'), 'type': routing, 'action': 'updated'}) changed = True return changed def validate_elements(self, engine): """ Validate each of the routing element types. The `add` API for each type is essentially the same but the required element types may differ slightly. For example, static route 'next hop' elements should be of type Router. Populate cache and set missing if elements are not found. :param Engine engine: engine ref :return: None """ for element in route_element: if 'antispoofing_network' in element: self.validate_antispoofing(engine) continue for it in getattr(self, element): if 'interface_id' not in it or 'name' not in it: self.fail(msg='All routing elements require the name and interface_id ' 'parameters. Invalid: %s, %s' % (element, it)) # if element == 'static_route': # self.cache._add_entry('router', it.get('name')) #else: # self.cache._add_entry(element, it.get('name')) if 'destination' in it and isinstance(it['destination'], list): for destinations in it['destination']: if 'name' not in destinations or 'type' not in destinations: self.fail(msg='Destination routing element must have the name ' 'and type attribute. Invalid: %s, %s' % (element, it)) if 'bgp_peering' in element: if destinations.get('type') not in ('external_bgp_peer', 'engine'): self.fail(msg='A BGP peering destination element can only be of type ' 'external_bgp_peer or engine. Invalid: %s' % element) elif 'ospfv2_area' in element: if destinations.get('type') != 'ospfv2_interface_settings': self.fail(msg='A OSPF destination element can only be of type ' 'ospfv2_interface_settings. Invalid: %s' % element) elif 'netlink' or 'static_route' in element: if destinations.get('type') not in srt_or_netlink_elem: self.fail(msg='A Netlink or static route destination element can ' 'only be of type: %s. Invalid: %s' % ( list(srt_or_netlink_elem), element)) if element == 'static_route': self.cache._add_entry('router', it.get('name')) # Add to cache for later check self.cache._add_entry( typeof=destinations.get('type'), name=destinations.get('name')) self.have_interface( engine, interface=it.get('interface_id'), network=it.get('network', None)) def validate_antispoofing(self, engine): """ Validate any antispoofing networks being added to the specified engine. :param Engine engine: engine ref """ for it in self.antispoofing_network: if 'interface_id' not in it: self.fail(msg='Interface ID is required when defining antispoofing: %s' % it) if 'destination' in it and isinstance(it['destination'], list): for destination in it['destination']: if 'name' not in destination or 'type' not in destination: self.fail(msg='Name and type is required when defining an ' 'antispoofing network: %s' % destination) if destination.get('type') not in antispoofing_elem: self.fail(msg='An Antispoofing destination element can only be of type: %s' % list(srt_or_netlink_elem)) self.cache._add_entry(destination['type'], destination['name']) self.have_interface( engine, interface=it.get('interface_id')) def have_interface(self, engine, interface, network=None): """ Check that we have the interface referenced in the element :param Engine engine: engine ref :param str interface: interface ID provided in yaml :param str network: optional network if specified. Allows to bind to a specified network on an interface with multiple networks :return: None """ try: iface = engine.routing.get(interface) if network: if not any(net.ip == network for net in iface): self.cache.missing.append( dict(msg='Cannot find specified network on interface', name=network, type='interface_network')) except InterfaceNotFound: self.cache.missing.append( dict(msg='Cannot find specified interface', name=interface, type='interface'))
def exec_module(self, **kwargs): state = kwargs.pop('state', 'present') for name, value in kwargs.items(): setattr(self, name, value) rbvpn = self.fetch_element(RouteVPN) changed = False if state == 'present': # Short circuit disable if rbvpn and self.enabled is not None and (rbvpn.enabled and not self.enabled): rbvpn.disable() self.results['changed'] = True return self.results local_engine = self.get_managed_gateway(self.local_gw) local_tunnel_interface = self.get_tunnel_interface( local_engine, self.local_gw.get('tunnel_interface')) local_internal_endpoint = self.get_ipsec_endpoint( local_engine, self.local_gw.get('interface_id'), address=self.local_gw.get('address')) if self.remote_gw.get('type', None) != 'external_gateway': remote_engine = self.get_managed_gateway(self.remote_gw) remote_tunnel_interface = self.get_tunnel_interface( remote_engine, self.remote_gw.get('tunnel_interface')) remote_internal_endpoint = self.get_ipsec_endpoint( remote_engine, self.remote_gw.get('interface_id'), address=self.remote_gw.get('address')) else: # External Gateway req = ('name', 'preshared_key', 'external_endpoint') for required in req: if required not in self.remote_gw: self.fail(msg='Missing required field for the external endpoint ' 'configuration: %s' % required) cache = Cache() external_gateway = dict(name=self.remote_gw['name']) # External Endpoints are defined in the External Gateway. # Build the data structures for a call to ExternalGateway.update_or_create ctypes = [] # connection_type element for endpoint in self.remote_gw['external_endpoint']: if 'name' not in endpoint or 'address' not in endpoint: self.fail(msg='An external endpoint must have at least a ' 'name and an address definition.') # SMC version 6.5 requires the connection type element to specify # the role for the given external endpoint if 'connection_type' not in endpoint: self.fail(msg='You must provide the connection_type parameter ' 'when creating an external endpoint') ctypes.append(endpoint.get('connection_type')) cache.add(dict(connection_type=ctypes)) if cache.missing: self.fail(msg=cache.missing) # Verify specified VPN Sites exist before continuing if 'vpn_site' in self.remote_gw: site_name = self.remote_gw.get('vpn_site', {}).pop('name', None) if not site_name: self.fail(msg='A VPN site requires a name to continue') # Get the elements cache.add(self.remote_gw.get('vpn_site', {})) vpn_site_types = self.remote_gw.get('vpn_site', {}).keys() # Save the VPN site types for retrieval if cache.missing: self.fail(msg='Could not find the specified elements for the ' 'VPN site configuration: %s' % cache.missing) site_element = [element.href for element_type in vpn_site_types for element in cache.get_type(element_type)] external_gateway.update( vpn_site=[dict(name=site_name, site_element=site_element)]) external_endpoint = [] for endpoint in self.remote_gw['external_endpoint']: endpoint.update(connection_type_ref=\ cache.get('connection_type',endpoint.pop('connection_type')).href) external_endpoint.append(endpoint) external_gateway.update(external_endpoint=external_endpoint) try: if state == 'present': if self.check_mode: return self.results # Create the tunnel endpoints if not rbvpn: local_gateway = TunnelEndpoint.create_ipsec_endpoint( local_engine.vpn.internal_gateway, local_tunnel_interface) # Enable the IPSEC listener on specified interface/s if self.update_ipsec_listener(local_internal_endpoint): changed = True is_external = self.remote_gw.get('type', None) == 'external_gateway' if not is_external: remote_gateway = TunnelEndpoint.create_ipsec_endpoint( remote_engine.vpn.internal_gateway, remote_tunnel_interface) if self.update_ipsec_listener(remote_internal_endpoint): changed = True else: # Update or Create gw, updated, created = ExternalGateway.update_or_create( with_status=True, **external_gateway) remote_gateway = TunnelEndpoint.create_ipsec_endpoint(gw) if created or updated: changed = True vpn = dict( name=self.name, local_endpoint=local_gateway, remote_endpoint=remote_gateway, preshared_key=self.preshared_key) if is_external: vpn.update(preshared_key=self.remote_gw['preshared_key']) rbvpn = RouteVPN.create_ipsec_tunnel(**vpn) changed = True else: #TODO: Update or create from top level RBVPN #rbvpn.update_or_create() if rbvpn and self.enabled is not None and (not rbvpn.enabled and self.enabled): rbvpn.enable() changed = True if self.remote_gw.get('type') == 'external_gateway': gw, updated, created = ExternalGateway.update_or_create( with_status=True, **external_gateway) if updated or created: changed = True self.results['state'] = rbvpn.data.data self.results['changed'] = changed elif state == 'absent': if rbvpn: rbvpn.delete() changed = True except SMCException as err: self.fail(msg=str(err), exception=traceback.format_exc()) self.results['changed'] = changed return self.results
class ForcepointOSPFElement(ForcepointModuleBase): def __init__(self): self.module_args = dict( elements=dict(type='list', required=True), state=dict(default='present', type='str', choices=['present', 'absent']) ) self.elements = None self.results = dict( changed=False, state=[] ) super(ForcepointOSPFElement, self).__init__(self.module_args, supports_check_mode=True) def exec_module(self, **kwargs): state = kwargs.pop('state', 'present') for name, value in kwargs.items(): setattr(self, name, value) changed = False try: if state == 'present': self.cache = Cache() self.check_elements() # Any missing dependencies.. if self.cache.missing: self.fail(msg='Missing dependent elements that are not created within this ' 'playbook: %s' % self.cache.missing) deferrals, elements = self.resolve_references(self.elements) if self.cache.missing: self.fail(msg='Missing elements that have a dependency, cannot continue: %s' % self.cache.missing) if self.check_mode: return self.results for element in elements: if self.create_or_update_element(element): changed = True if deferrals: _, ospf_elements = self.resolve_references(deferrals) for element in ospf_elements: if self.create_or_update_element(element): changed = True else: # No need to validate elements beyond type and name for element in self.elements: for typeof, values in element.items(): klazz = lookup_class(typeof) name = values.get('name') if name: try: klazz(name).delete() result = {'name': name, 'type': klazz.typeof, 'action': 'deleted'} changed = True except SMCException as e: result = {'name': name, 'type': klazz.typeof, 'action': 'failed to delete ' 'with reason: %s' % str(e)} finally: self.results['state'].append(result) except SMCException as err: self.fail(msg=str(err), exception=traceback.format_exc()) self.results['changed'] = changed return self.results def serialize_fields(self, typeof, element_values): """ Serialize fields to fit into the update_or_create constructors for the relevant types. :param str typeof: ospf element type :param dict element_values: values for dict from yaml :return: serialized dict for update_or_create """ new_values = copy.deepcopy(element_values) if 'ospfv2_profile' in typeof: for entry in new_values.get('redistribution_entry', []): if 'filter' in entry: if not entry.get('filter'): # Empty, clear existing entry.update(filter=[]) else: for typeof, value in entry.pop('filter', {}).items(): # Get element from cache entry.update(filter= self.cache.get(typeof, value[0])) if 'domain_settings_ref' in element_values: new_values.update(domain_settings_ref= self.cache.get_href('ospfv2_domain_settings', element_values.get( 'domain_settings_ref'))) elif 'ospfv2_area' in typeof: if 'interface_settings_ref' in new_values: new_values.update( interface_settings_ref=self.cache.get_href('ospfv2_interface_settings', new_values.pop('interface_settings_ref'))) for _filter in ('inbound_filters', 'outbound_filters'): if _filter in new_values: if not new_values.get(_filter): # Empty, clear existing new_values[_filter] = [] else: for k, value in new_values.pop(_filter, {}).items(): new_values.setdefault(_filter, []).append( self.cache.get_href(k, value[0])) return new_values def create_or_update_element(self, element): """ Create the element. :param dict element: the element dict from elements """ changed = False for typeof, values in element.items(): klazz = lookup_class(typeof) s_values = self.serialize_fields(typeof, values) obj, modified, created = klazz.update_or_create( with_status=True, **s_values) if created or modified: self.results['state'].append( {'name': obj.name, 'type': obj.typeof, 'action': 'created' if created else 'modified'}) changed = created or modified return changed def resolve_references(self, elementlist): """ Some elements have a dependency on another element being created. Check for those elements here and defer their creation until the end if the dependency is also being created. :rtype: tuple(list, list) """ deferrals, elements = [], [] for element in elementlist: if 'ospfv2_area' in element: area = element['ospfv2_area'] if area.get('interface_settings_ref'): if not self.dependency_being_created( elementlist, 'ospfv2_interface_settings', area['interface_settings_ref']): self.cache._add_entry('ospfv2_interface_settings', area['interface_settings_ref']) else: deferrals.append(element) continue elif 'ospfv2_profile' in element: profile = element['ospfv2_profile'] if profile.get('domain_settings_ref'): if not self.dependency_being_created( elementlist, 'ospfv2_domain_settings', profile['domain_settings_ref']): self.cache._add_entry('ospfv2_domain_settings', profile['domain_settings_ref']) else: deferrals.append(element) continue elements.append(element) return deferrals, elements def dependency_being_created(self, elementlist, typeof, name): """ Check whether the specified dependency element type is in the list to be created or not. If this returns False, the dependency should be fetched to verify it exists before creating the element that requires it. Element list is the supported element format. :param list elementlist: list of elements to check for dependency :param str typeof: valid bgp element typeof :param str name: name to find :rtype: bool """ for element in elementlist: if typeof in element: value = element.get(typeof) if value.get('name') == name: return True return False def check_elements(self): """ Check the elements for validity before continuing. Only return elements that can be processed without being deferred due to references. :rtype: list """ for element in self.elements: if not isinstance(element, dict): self.fail('OSPF element type must be defined as a dict, received: ' '%s, type: %s' % (element, type(element))) for ospf_element, values in element.items(): if ospf_element not in ospf_elements: self.fail(msg='OSPF element type specified is not a supported ' 'element type, provided: %s. Valid values: %s' % (ospf_element, list(ospf_elements))) if not isinstance(values, dict): self.fail(msg='Element values must be of type dict. Received ' 'values: %s of type: %s' % (values, type(values))) if 'name' not in values: self.fail(msg='Name is a required field when creating or ' 'modifying an element. Missing on definition: %s' % ospf_element) # Check each value against what the constructor requires allowed = allowed_args_by_lookup(ospf_element) for value in values: if value not in allowed: self.fail(msg='Provided an argument that is not valid for this ' 'element type. Argument: %s, element type: %s, valid args: %s' % (value, ospf_element, allowed)) # Redistribution entries must exist as they are not created in this module if ospf_element == 'ospfv2_profile': for redist in values.get('redistribution_entry', []): for _type, _val in redist.get('filter', {}).items(): if _type not in ('ip_access_list', 'route_map'): self.fail(msg='Redistribution entry filters must be either ' 'ip_access_list or route_map, received: %s' % _type) self.cache.add(redist['filter']) elif ospf_element == 'ospfv2_area': for _filter in ('inbound_filters', 'outbound_filters'): if _filter in values: for k, v in values.get(_filter).items(): if k not in ('ip_access_list', 'ip_prefix_list'): self.fail(msg='%s type invalid. Supported filters must ' 'be of type ip_access_list or ip_prefix_list. Received ' '%s' % (_filter, k)) self.cache.add({k: v})
def exec_module(self, **kwargs): state = kwargs.pop('state', 'present') for name, value in kwargs.items(): setattr(self, name, value) ELEMENT_TYPES = element_type_dict() GROUP_MEMBER_TYPES = ro_element_type_dict(map_only=True) GROUP_MEMBER_TYPES.update(ELEMENT_TYPES) try: if state == 'present': deferred_elements = ('group', 'netlink') # Validate elements before proceeding. groups, netlinks = [], [] for element in self.elements: self.is_element_valid(element, ELEMENT_TYPES if 'group' not in\ element else GROUP_MEMBER_TYPES) if 'group' in element: groups.append(element) elif 'netlink' in element: netlinks.append(element) if groups or netlinks: to_be_created = self.to_be_created_elements() self.cache = Cache() if groups: self.enum_group_members(groups, to_be_created) if self.cache.missing: self.fail( msg= 'Group members referenced are missing and are not being ' 'created in this playbook: %s' % self.cache.missing) if netlinks: self.enum_netlink_members(netlinks, to_be_created) if self.cache.missing: self.fail( msg= 'Netlink elements referenced are missing and are not being ' 'created in this playbook: %s' % self.cache.missing) for element in self.elements: for typeof, _ in element.items(): if typeof not in deferred_elements: result = update_or_create( element, ELEMENT_TYPES, check_mode=self.check_mode) self.results['state'].append(result) for group in groups: # Run through cache again, entries that exist will not be # added twice but this captures elements that might have been # added earlier by the playbook run _group = copy.deepcopy(group) members = _group.get('group', {}).get('members', {}) if members: self.cache.add_many([members]) # Add to new members list _members = [ self.cache.get(typeof, value) for typeof, member in members.items() for value in member ] else: # No members defined _members = [] _group.setdefault('group', {}).update(members=_members) result = update_or_create(_group, ELEMENT_TYPES, check_mode=self.check_mode) self.results['state'].append(result) for netlink in netlinks: _netlink = copy.deepcopy(netlink) gateway = _netlink.get('netlink', {}).get('gateway') self.cache._add_entry(gateway.get('type'), gateway.get('name')) _netlink.setdefault('netlink').update( gateway=self.cache.get(gateway.get('type'), gateway.get('name'))) # Update networks networks = _netlink.get('netlink').get('network') for net in networks: self.cache._add_entry('network', net) _netlink.setdefault('netlink').update(network=[ self.cache.get('network', net) for net in networks ]) result = update_or_create(_netlink, ELEMENT_TYPES, check_mode=self.check_mode) self.results['state'].append(result) if self.check_mode: return self.results else: for element in self.elements: for typeof in element: if typeof not in ELEMENT_TYPES: self.fail( msg= 'Element specified is not valid, got: {}, valid: {}' .format(typeof, ELEMENT_TYPES.keys())) else: if not self.check_mode: for elements in element[typeof]: result = delete_element( ELEMENT_TYPES.get(typeof)['type']( elements), self.ignore_err_if_not_found) self.results['state'].append(result) except SMCException as err: self.fail(msg=str(err), exception=traceback.format_exc()) for results in self.results['state']: if 'action' in results: self.results['changed'] = True break return self.results
class NetworkElement(ForcepointModuleBase): def __init__(self): self.module_args = dict(elements=dict(type='list', required=True), ignore_err_if_not_found=dict(type='bool', default=True), state=dict(default='present', type='str', choices=['present', 'absent'])) self.elements = None self.ignore_err_if_not_found = None self.results = dict(changed=False, state=[]) super(NetworkElement, self).__init__(self.module_args, supports_check_mode=True) def exec_module(self, **kwargs): state = kwargs.pop('state', 'present') for name, value in kwargs.items(): setattr(self, name, value) ELEMENT_TYPES = element_type_dict() GROUP_MEMBER_TYPES = ro_element_type_dict(map_only=True) GROUP_MEMBER_TYPES.update(ELEMENT_TYPES) try: if state == 'present': deferred_elements = ('group', 'netlink') # Validate elements before proceeding. groups, netlinks = [], [] for element in self.elements: self.is_element_valid(element, ELEMENT_TYPES if 'group' not in\ element else GROUP_MEMBER_TYPES) if 'group' in element: groups.append(element) elif 'netlink' in element: netlinks.append(element) if groups or netlinks: to_be_created = self.to_be_created_elements() self.cache = Cache() if groups: self.enum_group_members(groups, to_be_created) if self.cache.missing: self.fail( msg= 'Group members referenced are missing and are not being ' 'created in this playbook: %s' % self.cache.missing) if netlinks: self.enum_netlink_members(netlinks, to_be_created) if self.cache.missing: self.fail( msg= 'Netlink elements referenced are missing and are not being ' 'created in this playbook: %s' % self.cache.missing) for element in self.elements: for typeof, _ in element.items(): if typeof not in deferred_elements: result = update_or_create( element, ELEMENT_TYPES, check_mode=self.check_mode) self.results['state'].append(result) for group in groups: # Run through cache again, entries that exist will not be # added twice but this captures elements that might have been # added earlier by the playbook run _group = copy.deepcopy(group) members = _group.get('group', {}).get('members', {}) if members: self.cache.add_many([members]) # Add to new members list _members = [ self.cache.get(typeof, value) for typeof, member in members.items() for value in member ] else: # No members defined _members = [] _group.setdefault('group', {}).update(members=_members) result = update_or_create(_group, ELEMENT_TYPES, check_mode=self.check_mode) self.results['state'].append(result) for netlink in netlinks: _netlink = copy.deepcopy(netlink) gateway = _netlink.get('netlink', {}).get('gateway') self.cache._add_entry(gateway.get('type'), gateway.get('name')) _netlink.setdefault('netlink').update( gateway=self.cache.get(gateway.get('type'), gateway.get('name'))) # Update networks networks = _netlink.get('netlink').get('network') for net in networks: self.cache._add_entry('network', net) _netlink.setdefault('netlink').update(network=[ self.cache.get('network', net) for net in networks ]) result = update_or_create(_netlink, ELEMENT_TYPES, check_mode=self.check_mode) self.results['state'].append(result) if self.check_mode: return self.results else: for element in self.elements: for typeof in element: if typeof not in ELEMENT_TYPES: self.fail( msg= 'Element specified is not valid, got: {}, valid: {}' .format(typeof, ELEMENT_TYPES.keys())) else: if not self.check_mode: for elements in element[typeof]: result = delete_element( ELEMENT_TYPES.get(typeof)['type']( elements), self.ignore_err_if_not_found) self.results['state'].append(result) except SMCException as err: self.fail(msg=str(err), exception=traceback.format_exc()) for results in self.results['state']: if 'action' in results: self.results['changed'] = True break return self.results def to_be_created_elements(self): """ Get a dict of all elements that are to be created by this playbook. This is used when nested elements are being created that have requirements on other elements. This allows nested elements to be created alongside of their dependency. :return: dict of element by type: set([names]) to be created :rtype: dict """ nested_element = ('netlink', 'group') to_be_created = { } # dict of element by type: set([names]) to be created. for element in self.elements: for typeof, values in element.items(): if typeof not in nested_element: to_be_created.setdefault(typeof, set()).add(values.get('name')) return to_be_created def enum_group_members(self, groups, pending_elements): """ Check group membership. Groups reference only the type of element and the names of those members. If the element is being created, skip the existence check. If the member is not being created, attempt to fetch the member and save to cache. Return the cache. Check cache.missing before continuing to ensure all required elements are found. :param list groups: list of groups extracted from elements :param dict pending_elements: elements waiting to be created :return: None """ for group in groups: members = group.get('group', {}).get('members', {}) members = {} if members is None else members for typeof, member in members.items(): for name in member: if name not in pending_elements.get(typeof, set()): self.cache._add_entry(typeof, name) def enum_netlink_members(self, netlinks, pending_elements): """ Netlinks reference nested elements gateway and networks. Attempt to locate these either in definitions to be created by this playbook or existing already in SMC. Populate cache with href and name map and catch any missing before continuing. :return: None """ for netlink in netlinks: values = netlink.get('netlink', []) for req in ('gateway', 'network'): if req not in values: self.fail( msg='Netlink requires a gateway and list of networks, ' 'received: %s' % values) # Add requirements to cache gateway = values['gateway'] if not isinstance( gateway, dict) or 'name' not in gateway or 'type' not in gateway: self.fail( msg= 'Netlink gateway must be a dict with a name and type key value: %s' % gateway) if gateway.get('type') not in ('engine', 'router'): self.fail( msg= 'Netlink types can only be of type engine or router, got: %s' % gateway.get('type')) networks = values['network'] if not isinstance(networks, list): self.fail( msg='Netlink networks must be defined as a list, got: %s' % type(networks)) if gateway.get('name') not in pending_elements.get( gateway.get('type'), set()): self.cache._add_entry(gateway.get('type'), gateway.get('name')) for network in networks: if network not in pending_elements.get('network', set()): self.cache._add_entry('network', network) for attr in ('probe_address', 'domain_server_address'): if attr in values and not isinstance(values.get(attr), list): self.fail(msg='%s must be in list format' % attr)
def exec_module(self, **kwargs): state = kwargs.pop('state', 'present') for name, value in kwargs.items(): setattr(self, name, value) changed = False if state == 'present': external_gateway = {'name': self.name} cache = Cache() # External Endpoints are defined in the External Gateway. # Build the data structures for a call to ExternalGateway.update_or_create ctypes = [] # connection_type element for endpoint in self.external_endpoint: if 'name' not in endpoint or 'address' not in endpoint: self.fail(msg='An external endpoint must have at least a ' 'name and an address definition.') # SMC version 6.5 provides the ability to add connection_type settings if 'connection_type' in endpoint: ctypes.append(endpoint.get('connection_type')) cache.add(dict(connection_type=ctypes)) if cache.missing: self.fail(msg=cache.missing) if self.vpn_site: site_name = self.vpn_site.pop('name', None) if not site_name: self.fail(msg='VPN site requires a name attribute') vpn_site_types = self.vpn_site.keys( ) # Save the VPN site types for retrieval cache.add(self.vpn_site) if cache.missing: self.fail( msg='Could not find the specified elements for the ' 'VPN site configuration: %s' % cache.missing) site_element = [ element.href for element_type in vpn_site_types for element in cache.get_type(element_type) ] external_gateway.update( vpn_site=[dict(name=site_name, site_element=site_element)]) external_endpoint = [] for endpoint in self.external_endpoint: if 'connection_type' in endpoint: endpoint.update(connection_type_ref=\ cache.get('connection_type',endpoint.pop('connection_type')).href) external_endpoint.append(endpoint) external_gateway.update(external_endpoint=external_endpoint) try: if state == 'present': gateway, updated, created = ExternalGateway.update_or_create( with_status=True, **external_gateway) if created or updated: self.results['state'].append({ 'name': gateway.name, 'type': gateway.typeof, 'action': 'created' if created else 'modified' }) changed = True if self.tags: if self.add_tags(gateway, self.tags): changed = True elif state == 'absent': result = delete_element(ExternalGateway(self.name), self.ignore_err_if_not_found) if 'action' in result: changed = True self.results['state'].append(result) except SMCException as err: self.fail(msg=str(err), exception=traceback.format_exc()) self.results['changed'] = changed return self.results
def exec_module(self, **kwargs): state = kwargs.pop('state', 'present') for name, value in kwargs.items(): setattr(self, name, value) changed = False try: # Now that we have valid option settings, we can hit the db if self.policy: policy = FirewallPolicy.get(self.policy) else: policy = FirewallSubPolicy.get(self.sub_policy) if state == 'present': self.cache = Cache() for rule in self.rules: if 'tag' not in rule and 'name' not in rule: self.fail( msg='A rule must have either a rule tag or a ' 'name field: %s' % rule) if 'used_on' in rule: used_on = rule['used_on'] if not isinstance(used_on, string_types): self.fail( msg='Used on field should be the name of an ' 'engine or the value "ANY". Received: %s' % used_on) if all(ntype in rule for ntype in ('static_src_nat', 'dynamic_src_nat')): self.fail( msg= 'You must specify either static or dynamic source ' 'NAT, not both: %s' % rule) # Resolve elements if they exist, calls to SMC could happen here if self.field_resolver(rule.get('sources', {'none': True}), rule_targets): if 'static_src_nat' in rule: self.fail( msg= 'You must specify a source value when configuring ' 'static_src_nat. ANY and None are not valid: %s' % rule) if self.field_resolver( rule.get('destinations', {'none': True}), rule_targets): if 'static_dst_nat' in rule: self.fail( msg= 'You must specify a destination value when configuring ' 'static_dst_nat. ANY and None are not valid: %s' % rule) if 'services' in rule: self.field_resolver(rule.get('services'), service_targets) # Evaluate the NAT definitions first as they might have embedded # element references. for nat in nat_type: if nat not in rule: continue if not isinstance(rule[nat], dict): self.fail( msg= 'NAT definition must be type dict. Rule was: %s' % rule) nat_value_dict = rule.get(nat) translated_value = nat_value_dict.get( 'translated_value') if not translated_value or not isinstance( translated_value, dict): self.fail( msg= 'NAT translated value must exist and be in dict ' 'format. Rule was: %s' % rule) if all(port in translated_value for port in ('min_port', 'max_port')): min_port = translated_value.get('min_port') max_port = translated_value.get('max_port') # Port ranges are not valid for dynamic source NAT if any('-' in port for port in map(str, (min_port, max_port))) and \ 'dynamic_src_nat' in rule: self.fail( msg= 'Dynamic source NAT port definitions must be in ' 'single port (str or int) format. Ranges are not valid: %s' % rule) if not is_a_valid_port(min_port, max_port): self.fail( msg= 'Ports specified for nat type: %r are not valid. ' 'Ports for dynamic_src_nat must be between 1-65535 and port ' 'ranges used for static_dst_nat must be of equal lengths. ' 'Min port: %s, max_port: %s' % (nat, min_port, max_port)) if all(k in translated_value for k in ('name', 'type')): # Add elements to cache if defined self.cache._add_entry(translated_value.get('type'), translated_value.get('name')) if self.cache.missing: self.fail( msg= 'Missing required elements that are referenced in this ' 'configuration: %s' % self.cache.missing) if self.check_mode: return self.results # If we've gotten here, cache is populated and we're not missing anything for rule in self.rules: rule_dict = {} rule_dict.update(comment=rule.get('comment'), is_disabled=rule.get( 'is_disabled', False), name=rule.get('name')) for field in ('sources', 'destinations', 'services'): rule_dict[field] = self.get_values( rule.get(field, None)) for nat in nat_type: if nat in rule: rule_dict.update( self.nat_definition(nat, rule.get(nat))) if 'tag' not in rule: rule = policy.fw_ipv4_nat_rules.create(**rule_dict) changed = True self.results['state'].append({ 'rule': rule.name, 'type': rule.typeof, 'action': 'created' }) else: target_rule = self.rule_by_tag(policy, rule.get('tag')) if not target_rule: continue changes = compare_rules(target_rule, rule_dict) if rule.get('add_after', None): rule_at_pos = self.rule_by_tag( policy, rule.get('add_after')) if rule_at_pos: target_rule.move_rule_after(rule_at_pos) changes.append('add_after') elif rule.get('add_before', None): rule_at_pos = self.rule_by_tag( policy, rule.get('add_before')) if rule_at_pos: target_rule.move_rule_before(rule_at_pos) changes.append('add_before') elif changes: target_rule.save() if changes: changed = True self.results['state'].append({ 'rule': target_rule.name, 'type': target_rule.typeof, 'action': 'modified', 'changes': changes }) elif state == 'absent': for rule in self.rules: if 'tag' in rule: target_rule = self.rule_by_tag(policy, rule.get('tag')) if target_rule: target_rule.delete() changed = True self.results['state'].append({ 'rule': target_rule.name, 'type': target_rule.typeof, 'action': 'deleted' }) except SMCException as err: self.fail(msg=str(err), exception=traceback.format_exc()) self.results['changed'] = changed return self.results
class FirewallNATRule(ForcepointModuleBase): def __init__(self): self.module_args = dict(policy=dict(type='str'), sub_policy=dict(type='str'), rules=dict(type='list', default=[]), state=dict(default='present', type='str', choices=['present', 'absent'])) self.policy = None self.sub_policy = None self.rules = None mutually_exclusive = [ ['policy', 'sub_policy'], ] required_one_of = [['policy', 'sub_policy']] self.results = dict(changed=False, state=[]) super(FirewallNATRule, self).__init__(self.module_args, mutually_exclusive=mutually_exclusive, required_one_of=required_one_of, supports_check_mode=True) def exec_module(self, **kwargs): state = kwargs.pop('state', 'present') for name, value in kwargs.items(): setattr(self, name, value) changed = False try: # Now that we have valid option settings, we can hit the db if self.policy: policy = FirewallPolicy.get(self.policy) else: policy = FirewallSubPolicy.get(self.sub_policy) if state == 'present': self.cache = Cache() for rule in self.rules: if 'tag' not in rule and 'name' not in rule: self.fail( msg='A rule must have either a rule tag or a ' 'name field: %s' % rule) if 'used_on' in rule: used_on = rule['used_on'] if not isinstance(used_on, string_types): self.fail( msg='Used on field should be the name of an ' 'engine or the value "ANY". Received: %s' % used_on) if all(ntype in rule for ntype in ('static_src_nat', 'dynamic_src_nat')): self.fail( msg= 'You must specify either static or dynamic source ' 'NAT, not both: %s' % rule) # Resolve elements if they exist, calls to SMC could happen here if self.field_resolver(rule.get('sources', {'none': True}), rule_targets): if 'static_src_nat' in rule: self.fail( msg= 'You must specify a source value when configuring ' 'static_src_nat. ANY and None are not valid: %s' % rule) if self.field_resolver( rule.get('destinations', {'none': True}), rule_targets): if 'static_dst_nat' in rule: self.fail( msg= 'You must specify a destination value when configuring ' 'static_dst_nat. ANY and None are not valid: %s' % rule) if 'services' in rule: self.field_resolver(rule.get('services'), service_targets) # Evaluate the NAT definitions first as they might have embedded # element references. for nat in nat_type: if nat not in rule: continue if not isinstance(rule[nat], dict): self.fail( msg= 'NAT definition must be type dict. Rule was: %s' % rule) nat_value_dict = rule.get(nat) translated_value = nat_value_dict.get( 'translated_value') if not translated_value or not isinstance( translated_value, dict): self.fail( msg= 'NAT translated value must exist and be in dict ' 'format. Rule was: %s' % rule) if all(port in translated_value for port in ('min_port', 'max_port')): min_port = translated_value.get('min_port') max_port = translated_value.get('max_port') # Port ranges are not valid for dynamic source NAT if any('-' in port for port in map(str, (min_port, max_port))) and \ 'dynamic_src_nat' in rule: self.fail( msg= 'Dynamic source NAT port definitions must be in ' 'single port (str or int) format. Ranges are not valid: %s' % rule) if not is_a_valid_port(min_port, max_port): self.fail( msg= 'Ports specified for nat type: %r are not valid. ' 'Ports for dynamic_src_nat must be between 1-65535 and port ' 'ranges used for static_dst_nat must be of equal lengths. ' 'Min port: %s, max_port: %s' % (nat, min_port, max_port)) if all(k in translated_value for k in ('name', 'type')): # Add elements to cache if defined self.cache._add_entry(translated_value.get('type'), translated_value.get('name')) if self.cache.missing: self.fail( msg= 'Missing required elements that are referenced in this ' 'configuration: %s' % self.cache.missing) if self.check_mode: return self.results # If we've gotten here, cache is populated and we're not missing anything for rule in self.rules: rule_dict = {} rule_dict.update(comment=rule.get('comment'), is_disabled=rule.get( 'is_disabled', False), name=rule.get('name')) for field in ('sources', 'destinations', 'services'): rule_dict[field] = self.get_values( rule.get(field, None)) for nat in nat_type: if nat in rule: rule_dict.update( self.nat_definition(nat, rule.get(nat))) if 'tag' not in rule: rule = policy.fw_ipv4_nat_rules.create(**rule_dict) changed = True self.results['state'].append({ 'rule': rule.name, 'type': rule.typeof, 'action': 'created' }) else: target_rule = self.rule_by_tag(policy, rule.get('tag')) if not target_rule: continue changes = compare_rules(target_rule, rule_dict) if rule.get('add_after', None): rule_at_pos = self.rule_by_tag( policy, rule.get('add_after')) if rule_at_pos: target_rule.move_rule_after(rule_at_pos) changes.append('add_after') elif rule.get('add_before', None): rule_at_pos = self.rule_by_tag( policy, rule.get('add_before')) if rule_at_pos: target_rule.move_rule_before(rule_at_pos) changes.append('add_before') elif changes: target_rule.save() if changes: changed = True self.results['state'].append({ 'rule': target_rule.name, 'type': target_rule.typeof, 'action': 'modified', 'changes': changes }) elif state == 'absent': for rule in self.rules: if 'tag' in rule: target_rule = self.rule_by_tag(policy, rule.get('tag')) if target_rule: target_rule.delete() changed = True self.results['state'].append({ 'rule': target_rule.name, 'type': target_rule.typeof, 'action': 'deleted' }) except SMCException as err: self.fail(msg=str(err), exception=traceback.format_exc()) self.results['changed'] = changed return self.results def rule_by_tag(self, policy, tag): """ Get the rule referenced by it's tag. Tag will be in format '1234566.0'. When doing a search_rule, you must omit the part after the dot(.) to find the rule. :param FirewallPolicy policy: policy reference :param str tag: tag :rtype: Rule or None """ resolved_tag = get_tag(tag) if resolved_tag: rule = policy.search_rule('@{}'.format(resolved_tag)) return rule[0] if rule else None def field_resolver(self, elements, types): """ Field resolver, specific to retrieving network or service level elements in different formats. If elements are referencing existing elements, they will be loaded in the cache for retrieval. Format #1, as list (elements are expected to exist): - tcp_service: - service1 - service2 Format #2, as dict, only used for specifying any: any: true Format #3, if you have retrieved the rule and the sources, services, and destinations are in href format, pass through. - sources: - http://1.1.1.1/elements/host/1 - http://1.1.1.1/elements/host/2 ... .. note:: This is optimal if sources, destinations or services are not being changed as it will not result in queries to SMC. This is accomplished by not setting `expand` when running the facts module as_yaml. Return True if any or none are used. This is necessary to verify for NAT rules as source ANY/None or destinations ANY/None are not valid when doing static_src_nat or static_dst_nat. :param list elements: list of elements as parsed from YAML file :param dict type_dict: type dictionary for elements that should be supported for this run. :return: returns True if any or none are set on these fields. Otherwise None is returned and elements are added to cache or cache missing :rtype: bool or None """ if isinstance(elements, dict): if 'any' in elements or 'none' in elements: return True for name, value in elements.items(): if name not in types: self.fail(msg='Invalid element type specified: %s. Valid ' 'types are: %s' % (name, list(types))) if not isinstance(value, list): self.fail( msg='Elements specified for type: %s should be in list ' 'format, got: %s' % (name, type(value))) self.cache.add_many([elements]) elif isinstance(elements, list): for entry in elements: if not isinstance( entry, string_types) or not entry.startswith('http'): self.fail( msg='List entry is expected to be the raw href of ' 'the element. Received: %s' % entry) def get_values(self, elements): """ Get the values for source, destination and service cells. If these are not provided, return 'any'. """ if not elements: return 'any' if isinstance(elements, dict): if 'any' in elements: return 'any' elif 'none' in elements: return None # Resolve out of cache, return as Element return [ self.cache.get(typeof, value) for typeof, values in elements.items() for value in values ] elif isinstance(elements, list): return elements def nat_definition(self, nat_type, data_dict): """ Return a dict for the rule create constructor. :param str nat_type: nat type retrieved from yaml :param dict data_dict: nat dict from yaml definition :rtype: dict """ nat_dict = {} translated_value = data_dict.get('translated_value') if nat_type in ('dynamic_src_nat', 'static_src_nat'): # Validate ports first. Range of ports must have equal length if 'min_port' in translated_value and 'max_port' in translated_value: nat_dict['%s_ports' % nat_type] = (translated_value.get('min_port'), translated_value.get('max_port')) if 'name' in translated_value and 'type' in translated_value: nat_dict[nat_type] = self.cache.get(translated_value['type'], translated_value['name']) else: nat_dict[nat_type] = translated_value.get('ip_descriptor') return nat_dict