def main(): module = AnsibleModule(argument_spec=RULE_ARGUMENT_SPEC, required_if=RULE_REQUIRED_IF, supports_check_mode=True) pfmodule = PFSenseRuleModule(module) pfmodule.run(module.params) pfmodule.commit_changes()
def _remove_all_rules(self, interface): """ delete all interface rules """ # we use the pfsense_rule module to delete the rules since, at least for floating rules, # it implies to recalculate separators positions # if we have to just remove the deleted interface of a floating rule we do it ourselves todel = [] for rule_elt in self.pfsense.rules: if rule_elt.find('floating') is not None: interfaces = rule_elt.find('interface').text.split(',') old_ifs = ','.join([ self.pfsense.get_interface_display_name(old_interface) for old_interface in interfaces ]) if interface in interfaces: if len(interfaces) > 1: interfaces.remove(interface) new_ifs = ','.join([ self.pfsense.get_interface_display_name( new_interface) for new_interface in interfaces ]) rule_elt.find('interface').text = ','.join(interfaces) cmd = 'update rule \'{0}\' on \'floating({1})\' set interface=\'{2}\''.format( rule_elt.find('descr').text, old_ifs, new_ifs) self.result['commands'].append(cmd) continue todel.append(rule_elt) else: continue else: iface = rule_elt.find('interface') if iface is not None and iface.text == interface: todel.append(rule_elt) if todel: pfsense_rules = PFSenseRuleModule(self.module, self.pfsense) for rule_elt in todel: params = {} params['state'] = 'absent' params['name'] = rule_elt.find('descr').text params['interface'] = rule_elt.find('interface').text if rule_elt.find('floating') is not None: params['floating'] = True pfsense_rules.run(params) if pfsense_rules.result['commands']: self.result['commands'].extend( pfsense_rules.result['commands'])
class PFSenseModuleAggregate(object): """ module managing pfsense aggregated aliases, rules, rule separators, interfaces and VLANs """ def __init__(self, module): self.module = module self.pfsense = PFSenseModule(module) self.pfsense_aliases = PFSenseAliasModule(module, self.pfsense) self.pfsense_interfaces = PFSenseInterfaceModule(module, self.pfsense) self.pfsense_nat_outbounds = PFSenseNatOutboundModule( module, self.pfsense) self.pfsense_nat_port_forwards = PFSenseNatPortForwardModule( module, self.pfsense) self.pfsense_rules = PFSenseRuleModule(module, self.pfsense) self.pfsense_rule_separators = PFSenseRuleSeparatorModule( module, self.pfsense) self.pfsense_vlans = PFSenseVlanModule(module, self.pfsense) def _update(self): run = False cmd = 'require_once("filter.inc");\n' # TODO: manage one global list of commands as ordering can be important between modules if self.pfsense_vlans.result['changed']: run = True cmd += self.pfsense_vlans.get_update_cmds() if self.pfsense_interfaces.result['changed']: run = True cmd += self.pfsense_interfaces.get_update_cmds() cmd += 'if (filter_configure() == 0) { \n' if self.pfsense_aliases.result['changed']: run = True cmd += 'clear_subsystem_dirty(\'aliases\');\n' if self.pfsense_nat_port_forwards.result[ 'changed'] or self.pfsense_nat_outbounds.result['changed']: run = True cmd += 'clear_subsystem_dirty(\'natconf\');\n' if (self.pfsense_rules.result['changed'] or self.pfsense_rule_separators.result['changed'] or self.pfsense_nat_port_forwards.result['changed'] or self.pfsense_nat_outbounds.result['changed']): run = True cmd += 'clear_subsystem_dirty(\'filter\');\n' cmd += '}' if run: return self.pfsense.phpshell(cmd) return ('', '', '') def _parse_floating_interfaces(self, interfaces): """ parse interfaces """ res = set() for interface in interfaces.split(','): res.add(self.pfsense.parse_interface(interface)) return res def want_rule(self, rule_elt, rules, name_field='name'): """ return True if we want to keep rule_elt """ descr = rule_elt.find('descr') interface = rule_elt.find('interface') floating = rule_elt.find('floating') is not None # probably not a rule if descr is None or interface is None: return True for rule in rules: if rule['state'] == 'absent': continue if rule[name_field] != descr.text: continue rule_floating = ( rule.get('floating') is not None and (isinstance(rule['floating'], bool) and rule['floating'] or rule['floating'].lower() in ['yes', 'true'])) if floating != rule_floating: continue if floating or self.pfsense.parse_interface( rule['interface']) == interface.text: return True return False def want_rule_separator(self, separator_elt, rule_separators): """ return True if we want to keep separator_elt """ name = separator_elt.find('text').text interface = separator_elt.find('if').text for separator in rule_separators: if separator['state'] == 'absent': continue if separator['name'] != name: continue if separator.get('floating'): if interface == 'floatingrules': return True elif self.pfsense.parse_interface( separator['interface']) == interface: return True return False @staticmethod def want_alias(alias_elt, aliases): """ return True if we want to keep alias_elt """ name = alias_elt.find('name') alias_type = alias_elt.find('type') # probably not an alias if name is None or type is None: return True for alias in aliases: if alias['state'] == 'absent': continue if alias['name'] == name.text and alias['type'] == alias_type.text: return True return False @staticmethod def want_interface(interface_elt, interfaces): """ return True if we want to keep interface_elt """ descr_elt = interface_elt.find('descr') if descr_elt is not None and descr_elt.text: name = descr_elt.text else: name = interface_elt.tag for interface in interfaces: if interface['state'] == 'absent': continue if interface['descr'] == name: return True return False @staticmethod def want_vlan(vlan_elt, vlans): """ return True if we want to keep vlan_elt """ tag = int(vlan_elt.find('tag').text) interface = vlan_elt.find('if') for vlan in vlans: if vlan['state'] == 'absent': continue if vlan['vlan_id'] == tag and vlan['interface'] == interface.text: return True return False @staticmethod def is_filtered(interface_filter, params): if interface_filter is None: return False if 'floating' in params: if isinstance(params['floating'], str): floating = params['floating'].lower() else: floating = 'true' if params['floating'] else 'false' if floating != 'false' and floating != 'no': return 'floating' not in interface_filter return params['interface'].lower() not in interface_filter def run_rules(self): """ process input params to add/update/delete all rules """ want = self.module.params['aggregated_rules'] interface_filter = self.module.params['interface_filter'].lower( ).split(' ') if self.module.params.get( 'interface_filter') is not None else None if want is None: return # delete every other rule if required if self.module.params['purge_rules']: todel = [] for rule_elt in self.pfsense_rules.root_elt: if not self.want_rule(rule_elt, want): params = {} params['state'] = 'absent' params['name'] = rule_elt.find('descr').text if rule_elt.find('floating') is not None: params['floating'] = True interfaces = rule_elt.find('interface').text.split(',') params['interface'] = list() for interface in interfaces: target = self.pfsense.get_interface_display_name( interface, return_none=True) if target is not None: params['interface'].append(target) else: params['interface'].append(interface) params['interface'] = ','.join(params['interface']) else: params[ 'interface'] = self.pfsense.get_interface_display_name( rule_elt.find('interface').text, return_none=True) if params['interface'] is None: continue todel.append(params) for params in todel: if self.is_filtered(interface_filter, params): continue self.pfsense_rules.run(params) # generating order if required if self.module.params.get('order_rules'): last_rules = dict() for params in want: if params.get('before') is not None or params.get( 'after') is not None: self.module.fail_json( msg= "You can't use after or before parameters on rules when using order_rules (see {0})" .format(params['name'])) if params.get('state') == 'absent': continue if params.get('floating'): key = 'floating' else: key = params['interface'] # first rule on interface if key not in last_rules: params['after'] = 'top' last_rules[key] = params['name'] continue params['after'] = last_rules[key] last_rules[key] = params['name'] # processing aggregated parameters for params in want: if self.is_filtered(interface_filter, params): continue self.pfsense_rules.run(params) def run_nat_outbounds_rules(self): """ process input params to add/update/delete all nat_outbound rules """ want = self.module.params['aggregated_nat_outbounds'] interface_filter = self.module.params['interface_filter'].lower( ).split(' ') if self.module.params.get( 'interface_filter') is not None else None if want is None: return # delete every other rule if required if self.module.params['purge_nat_outbounds']: todel = [] for rule_elt in self.pfsense_nat_outbounds.root_elt: if not self.want_rule(rule_elt, want, name_field='descr'): params = {} params['state'] = 'absent' params['descr'] = rule_elt.find('descr').text params[ 'interface'] = self.pfsense.get_interface_display_name( rule_elt.find('interface').text, return_none=True) if params['interface'] is None: continue todel.append(params) for params in todel: if self.is_filtered(interface_filter, params): continue self.pfsense_nat_outbounds.run(params) # processing aggregated parameters for params in want: if self.is_filtered(interface_filter, params): continue self.pfsense_nat_outbounds.run(params) def run_nat_port_forwards_rules(self): """ process input params to add/update/delete all nat_port_forwards_rule rules """ want = self.module.params['aggregated_nat_port_forwards'] interface_filter = self.module.params['interface_filter'].lower( ).split(' ') if self.module.params.get( 'interface_filter') is not None else None if want is None: return # delete every other rule if required if self.module.params['purge_nat_port_forwards']: todel = [] for rule_elt in self.pfsense_nat_port_forwards.root_elt: if not self.want_rule(rule_elt, want, name_field='descr'): params = {} params['state'] = 'absent' params['descr'] = rule_elt.find('descr').text params[ 'interface'] = self.pfsense.get_interface_display_name( rule_elt.find('interface').text, return_none=True) if params['interface'] is None: continue todel.append(params) for params in todel: if self.is_filtered(interface_filter, params): continue self.pfsense_nat_port_forwards.run(params) # processing aggregated parameters for params in want: if self.is_filtered(interface_filter, params): continue self.pfsense_nat_port_forwards.run(params) def run_aliases(self): """ process input params to add/update/delete all aliases """ want = self.module.params['aggregated_aliases'] if want is None: return # processing aggregated parameter for param in want: self.pfsense_aliases.run(param) # delete every other alias if required if self.module.params['purge_aliases']: todel = [] for alias_elt in self.pfsense_aliases.root_elt: if not self.want_alias(alias_elt, want): params = {} params['state'] = 'absent' params['name'] = alias_elt.find('name').text todel.append(params) for params in todel: self.pfsense_aliases.run(params) def run_interfaces(self): """ process input params to add/update/delete all interfaces """ want = self.module.params['aggregated_interfaces'] if want is None: return # processing aggregated parameter for param in want: self.pfsense_interfaces.run(param) # delete every other if required if self.module.params['purge_interfaces']: todel = [] for interface_elt in self.pfsense_interfaces.root_elt: if not self.want_interface(interface_elt, want): params = {} params['state'] = 'absent' descr_elt = interface_elt.find('descr') if descr_elt is not None and descr_elt.text: params['descr'] = descr_elt.text todel.append(params) for params in todel: self.pfsense_interfaces.run(params) def run_rule_separators(self): """ process input params to add/update/delete all separators """ want = self.module.params['aggregated_rule_separators'] interface_filter = self.module.params['interface_filter'].lower( ).split(' ') if self.module.params.get( 'interface_filter') is not None else None if want is None: return # processing aggregated parameter for params in want: if self.is_filtered(interface_filter, params): continue self.pfsense_rule_separators.run(params) # delete every other if required if self.module.params['purge_rule_separators']: todel = [] for interface_elt in self.pfsense_rule_separators.separators: for separator_elt in interface_elt: if not self.want_rule_separator(separator_elt, want): params = {} params['state'] = 'absent' params['name'] = separator_elt.find('text').text if interface_elt.tag == 'floatingrules': params['floating'] = True else: params[ 'interface'] = self.pfsense.get_interface_display_name( interface_elt.tag, return_none=True) if params['interface'] is None: continue todel.append(params) for params in todel: if self.is_filtered(interface_filter, params): continue self.pfsense_rule_separators.run(params) def run_vlans(self): """ process input params to add/update/delete all VLANs """ want = self.module.params['aggregated_vlans'] if want is None: return # processing aggregated parameter for param in want: self.pfsense_vlans.run(param) # delete every other if required if self.module.params['purge_vlans']: todel = [] for vlan_elt in self.pfsense_vlans.root_elt: if not self.want_vlan(vlan_elt, want): params = {} params['state'] = 'absent' params['interface'] = vlan_elt.find('if').text params['vlan_id'] = int(vlan_elt.find('tag').text) todel.append(params) for params in todel: self.pfsense_vlans.run(params) def commit_changes(self): """ apply changes and exit module """ stdout = '' stderr = '' changed = (self.pfsense_aliases.result['changed'] or self.pfsense_interfaces.result['changed'] or self.pfsense_nat_outbounds.result['changed'] or self.pfsense_nat_port_forwards.result['changed'] or self.pfsense_rules.result['changed'] or self.pfsense_rule_separators.result['changed'] or self.pfsense_vlans.result['changed']) if changed and not self.module.check_mode: self.pfsense.write_config(descr='aggregated change') (dummy, stdout, stderr) = self._update() result = {} result['result_aliases'] = self.pfsense_aliases.result['commands'] result['result_interfaces'] = self.pfsense_interfaces.result[ 'commands'] result['result_nat_outbounds'] = self.pfsense_nat_outbounds.result[ 'commands'] result[ 'result_nat_port_forwards'] = self.pfsense_nat_port_forwards.result[ 'commands'] result['result_rules'] = self.pfsense_rules.result['commands'] result['result_rule_separators'] = self.pfsense_rule_separators.result[ 'commands'] result['result_vlans'] = self.pfsense_vlans.result['commands'] result['changed'] = changed result['stdout'] = stdout result['stderr'] = stderr self.module.exit_json(**result)
class PFSenseModuleAggregate(object): """ module managing pfsense aggregated aliases, rules, rule separators, interfaces and vlans """ def __init__(self, module): self.module = module self.pfsense = PFSenseModule(module) self.pfsense_aliases = PFSenseAliasModule(module, self.pfsense) self.pfsense_interfaces = PFSenseInterfaceModule(module, self.pfsense) self.pfsense_rules = PFSenseRuleModule(module, self.pfsense) self.pfsense_rule_separators = PFSenseRuleSeparatorModule(module, self.pfsense) self.pfsense_vlans = PFSenseVlanModule(module, self.pfsense) def _update(self): run = False cmd = 'require_once("filter.inc");\n' if self.pfsense_vlans.result['changed']: run = True cmd += self.pfsense_vlans.get_update_cmds() if self.pfsense_interfaces.result['changed']: run = True cmd += self.pfsense_interfaces.get_update_cmds() cmd += 'if (filter_configure() == 0) { \n' if self.pfsense_aliases.result['changed']: run = True cmd += 'clear_subsystem_dirty(\'aliases\');\n' if self.pfsense_rules.result['changed'] or self.pfsense_rule_separators.result['changed']: run = True cmd += 'clear_subsystem_dirty(\'filter\');\n' cmd += '}' if run: return self.pfsense.phpshell(cmd) return ('', '', '') def want_rule(self, rule_elt, rules): """ return True if we want to keep rule_elt """ descr = rule_elt.find('descr') interface = rule_elt.find('interface') # probably not a rule if descr is None or interface is None: return True for rule in rules: if rule['state'] == 'absent': continue if rule['name'] == descr.text and self.pfsense.parse_interface(rule['interface']) == interface.text: return True return False def want_rule_separator(self, separator_elt, rule_separators): """ return True if we want to keep separator_elt """ name = separator_elt.find('text').text interface = separator_elt.find('if').text for separator in rule_separators: if separator['state'] == 'absent': continue if separator['name'] != name: continue if self.pfsense.parse_interface(separator['interface']) == interface or interface == 'floatingrules' and separator.get('floating'): return True return False @staticmethod def want_alias(alias_elt, aliases): """ return True if we want to keep alias_elt """ name = alias_elt.find('name') alias_type = alias_elt.find('type') # probably not an alias if name is None or type is None: return True for alias in aliases: if alias['state'] == 'absent': continue if alias['name'] == name.text and alias['type'] == alias_type.text: return True return False def want_interface(self, interface_elt, interfaces): """ return True if we want to keep interface_elt """ descr_elt = interface_elt.find('descr') if descr_elt is not None and descr_elt.text: name = descr_elt.text else: name = interface_elt.tag for interface in interfaces: if interface['state'] == 'absent': continue if interface['descr'] == name: return True return False @staticmethod def want_vlan(vlan_elt, vlans): """ return True if we want to keep vlan_elt """ tag = int(vlan_elt.find('tag').text) interface = vlan_elt.find('if') for vlan in vlans: if vlan['state'] == 'absent': continue if vlan['vlan_id'] == tag and vlan['interface'] == interface.text: return True return False def run_rules(self): """ process input params to add/update/delete all rules """ want = self.module.params['aggregated_rules'] if want is None: return # delete every other rule if required if self.module.params['purge_rules']: todel = [] for rule_elt in self.pfsense_rules.root_elt: if not self.want_rule(rule_elt, want): params = {} params['state'] = 'absent' params['name'] = rule_elt.find('descr').text params['interface'] = self.pfsense.get_interface_display_name(rule_elt.find('interface').text) if rule_elt.find('floating') is not None: params['floating'] = True todel.append(params) for params in todel: self.pfsense_rules.run(params) # generating order if required if self.module.params.get('order_rules'): last_rules = dict() for params in want: if params.get('before') is not None or params.get('after') is not None: self.module.fail_json(msg="You can't use after or before parameters on rules when using order_rules (see {0})".format(params['name'])) if params.get('state') == 'absent': continue if params.get('floating'): key = 'floating' else: key = params['interface'] # first rule on interface if key not in last_rules: params['after'] = 'top' last_rules[key] = params['name'] continue params['after'] = last_rules[key] last_rules[key] = params['name'] # processing aggregated parameters for params in want: self.pfsense_rules.run(params) def run_aliases(self): """ process input params to add/update/delete all aliases """ want = self.module.params['aggregated_aliases'] if want is None: return # processing aggregated parameter for param in want: self.pfsense_aliases.run(param) # delete every other alias if required if self.module.params['purge_aliases']: todel = [] for alias_elt in self.pfsense_aliases.root_elt: if not self.want_alias(alias_elt, want): params = {} params['state'] = 'absent' params['name'] = alias_elt.find('name').text todel.append(params) for params in todel: self.pfsense_aliases.run(params) def run_interfaces(self): """ process input params to add/update/delete all interfaces """ want = self.module.params['aggregated_interfaces'] if want is None: return # processing aggregated parameter for param in want: self.pfsense_interfaces.run(param) # delete every other if required if self.module.params['purge_interfaces']: todel = [] for interface_elt in self.pfsense_interfaces.root_elt: if not self.want_interface(interface_elt, want): params = {} params['state'] = 'absent' descr_elt = interface_elt.find('descr') if descr_elt is not None and descr_elt.text: params['descr'] = descr_elt.text todel.append(params) for params in todel: self.pfsense_interfaces.run(params) def run_rule_separators(self): """ process input params to add/update/delete all separators """ want = self.module.params['aggregated_rule_separators'] if want is None: return # processing aggregated parameter for param in want: self.pfsense_rule_separators.run(param) # delete every other if required if self.module.params['purge_rule_separators']: todel = [] for interface_elt in self.pfsense_rule_separators.separators: for separator_elt in interface_elt: if not self.want_rule_separator(separator_elt, want): params = {} params['state'] = 'absent' params['name'] = separator_elt.find('text').text if interface_elt.tag == 'floatingrules': params['floating'] = True else: params['interface'] = self.pfsense.get_interface_display_name(interface_elt.tag) todel.append(params) for params in todel: self.pfsense_rule_separators.run(params) def run_vlans(self): """ process input params to add/update/delete all vlans """ want = self.module.params['aggregated_vlans'] if want is None: return # processing aggregated parameter for param in want: self.pfsense_vlans.run(param) # delete every other if required if self.module.params['purge_vlans']: todel = [] for vlan_elt in self.pfsense_vlans.root_elt: if not self.want_vlan(vlan_elt, want): params = {} params['state'] = 'absent' params['interface'] = vlan_elt.find('if').text params['vlan_id'] = int(vlan_elt.find('tag').text) todel.append(params) for params in todel: self.pfsense_vlans.run(params) def commit_changes(self): """ apply changes and exit module """ stdout = '' stderr = '' changed = ( self.pfsense_aliases.result['changed'] or self.pfsense_interfaces.result['changed'] or self.pfsense_rules.result['changed'] or self.pfsense_rule_separators.result['changed'] or self.pfsense_vlans.result['changed'] ) if changed and not self.module.check_mode: self.pfsense.write_config(descr='aggregated change') (dummy, stdout, stderr) = self._update() result = {} result['result_aliases'] = self.pfsense_aliases.result['commands'] result['result_interfaces'] = self.pfsense_interfaces.result['commands'] result['result_rules'] = self.pfsense_rules.result['commands'] result['result_rule_separators'] = self.pfsense_rule_separators.result['commands'] result['result_vlans'] = self.pfsense_vlans.result['commands'] result['changed'] = changed result['stdout'] = stdout result['stderr'] = stderr self.module.exit_json(**result)
class PFSenseNatPortForwardModule(PFSenseModuleBase): """ module managing pfsense NAT rules """ @staticmethod def get_argument_spec(): """ return argument spec """ return NAT_PORT_FORWARD_ARGUMENT_SPEC ############################## # init # def __init__(self, module, pfsense=None): super(PFSenseNatPortForwardModule, self).__init__(module, pfsense) self.name = "pfsense_nat_port_forward" self.obj = dict() self.after = None self.before = None self.position_changed = False self.root_elt = self.pfsense.get_element('nat') if self.root_elt is None: self.root_elt = self.pfsense.new_element('nat') self.pfsense.root.append(self.root_elt) self.pfsense_rule_module = None ############################## # params processing # def _params_to_obj(self): """ return a dict from module params """ obj = dict() self.obj = obj obj['descr'] = self.params['descr'] if self.params['state'] == 'present': obj['interface'] = self.pfsense.parse_interface( self.params['interface']) self._get_ansible_param(obj, 'protocol') self._get_ansible_param(obj, 'poolopts') self._get_ansible_param(obj, 'source_hash_key') self._get_ansible_param(obj, 'natport') self._get_ansible_param(obj, 'natreflection') if obj['natreflection'] == 'system-default': del obj['natreflection'] if self.params['associated_rule'] == 'pass': obj['associated-rule-id'] = 'pass' elif self.params[ 'associated_rule'] == 'unassociated' and self._find_target( ) is not None: self.module.fail_json( msg= 'You cannot set an unassociated filter rule if the NAT rule already exists.' ) else: obj['associated-rule-id'] = '' self._get_ansible_param_bool(obj, 'disabled') self._get_ansible_param_bool(obj, 'nordr') self._get_ansible_param_bool(obj, 'nosync') if 'after' in self.params and self.params['after'] is not None: self.after = self.params['after'] if 'before' in self.params and self.params['before'] is not None: self.before = self.params['before'] obj['source'] = self.pfsense.parse_address(self.params['source'], allow_self=False) obj['destination'] = self.pfsense.parse_address( self.params['destination']) self._parse_target_address(obj) return obj def _parse_target_address(self, obj): """ validate param address field and returns it as a dict """ if self.params.get('target') is None or self.params['target'] == '': self.module.fail_json( msg='The field Redirect target IP is required.') param = self.params['target'] addr = param.split(':') if len(addr) > 2: self.module.fail_json(msg='Cannot parse address %s' % (param)) address = addr[0] ports = addr[1] if len(addr) > 1 else None if self.pfsense.find_alias( address, 'host') is not None or self.pfsense.is_ipv4_address(address): obj['target'] = address else: self.module.fail_json( msg= '"%s" is not a valid redirect target IP address or host alias.' % (param)) if ports is not None and self.pfsense.is_port_or_alias(ports): obj['local-port'] = ports else: self.module.fail_json( msg= '"{0}" is not a valid redirect target port. It must be a port alias or integer between 1 and 65535.' .format(ports)) def _validate_params(self): """ do some extra checks on input parameters """ if self.params.get('after') and self.params.get('before'): self.module.fail_json(msg='Cannot specify both after and before') elif self.params.get('after'): if self.params['after'] == self.params['descr']: self.module.fail_json( msg='Cannot specify the current rule in after') elif self.params.get('before'): if self.params['before'] == self.params['descr']: self.module.fail_json( msg='Cannot specify the current rule in before') ############################## # XML processing # def _copy_and_add_target(self): """ create the XML target_elt """ self._set_associated_rule() self.pfsense.copy_dict_to_element(self.obj, self.target_elt) self.diff['after'] = self.pfsense.element_to_dict(self.target_elt) self._insert(self.target_elt) def _copy_and_update_target(self): """ update the XML target_elt """ before = self.pfsense.element_to_dict(self.target_elt) self.diff['before'] = before changed = self._set_associated_rule(before) if self.pfsense.copy_dict_to_element(self.obj, self.target_elt): changed = True if self._remove_deleted_params(): changed = True if self._update_rule_position(self.target_elt): changed = True self.diff['after'] = self.pfsense.element_to_dict(self.target_elt) return (before, changed) def _create_associated_rule(self): if self.pfsense_rule_module is None: self.pfsense_rule_module = PFSenseRuleModule( self.module, self.pfsense) params = dict() params['name'] = 'NAT ' + self.params['descr'] params['state'] = 'present' params['action'] = 'pass' params['ipprotocol'] = 'inet' params['statetype'] = 'keep state' params['interface'] = self.params['interface'] params['source'] = self.params['source'] params['destination'] = self.params['target'] params['disabled'] = self.params['disabled'] params['protocol'] = self.params['protocol'] if self.params['associated_rule'] == 'associated': params['associated-rule-id'] = self.pfsense.uniqid('nat_', True) self.obj['associated-rule-id'] = params['associated-rule-id'] self.result['commands'] = list() self.pfsense_rule_module.run(params) self.result['commands'] += self.pfsense_rule_module.result['commands'] def _create_target(self): """ create the XML target_elt """ target_elt = self.pfsense.new_element('rule') return target_elt def _delete_associated_rule(self, ruleid, interface=None): if ruleid is None or ruleid == '' or ruleid == 'pass': return if interface is None: interface = self.params['interface'] self.pfsense_rule_module = PFSenseRuleModule(self.module, self.pfsense) params = dict() if self.params['descr'] is None: params['name'] = 'NAT ' else: params['name'] = 'NAT ' + self.params['descr'] params['interface'] = interface params['state'] = 'absent' params['associated-rule-id'] = ruleid self.pfsense_rule_module.run(params) self.result['commands'] += self.pfsense_rule_module.result['commands'] def _find_rule_by_descr(self, descr): """ find the XML target_elt """ for idx, rule_elt in enumerate(self.root_elt): if rule_elt.tag != 'rule': continue if rule_elt.find('descr').text == descr: return (rule_elt, idx) return (None, None) def _find_target(self): """ find the XML target_elt """ for rule_elt in self.root_elt: if rule_elt.tag != 'rule': continue if rule_elt.find('descr').text == self.obj['descr']: return rule_elt return None def _get_expected_rule_position(self): """ get expected rule position in interface/floating """ if self.before == 'bottom': return len(self.root_elt) elif self.after == 'top': return 0 elif self.after is not None: return self._get_rule_position(self.after) + 1 elif self.before is not None: position = self._get_rule_position(self.before) - 1 if position < 0: return 0 return position else: position = self._get_rule_position(self.after, fail=False) if position is not None: return position return len(self.root_elt) return -1 def _get_expected_rule_xml_index(self): """ get expected rule index in xml """ if self.before == 'bottom': return len(self.root_elt) elif self.after == 'top': return 0 elif self.after is not None: found, i = self._find_rule_by_descr(self.after) if found is not None: return i + 1 else: self.module.fail_json(msg='Failed to insert after rule=%s' % (self.after)) elif self.before is not None: found, i = self._find_rule_by_descr(self.before) if found is not None: return i else: self.module.fail_json(msg='Failed to insert before rule=%s' % (self.before)) else: found, i = self._find_rule_by_descr(self.obj['descr']) if found is not None: return i return len(self.root_elt) return -1 @staticmethod def _get_params_to_remove(): """ returns the list of params to remove if they are not set """ return ['disabled', 'nordr', 'nosync', 'natreflection'] def _get_rule_position(self, descr=None, fail=True): """ get rule position in interface/floating """ if descr is None: descr = self.obj['descr'] (res, idx) = self._find_rule_by_descr(descr) if fail and res is None: self.module.fail_json(msg='Failed to find rule=%s' % (descr)) return idx def _insert(self, rule_elt): """ insert rule into xml """ rule_xml_idx = self._get_expected_rule_xml_index() self.root_elt.insert(rule_xml_idx, rule_elt) def _pre_remove_target_elt(self): """ processing before removing elt """ ruleid_elt = self.target_elt.find('associated-rule-id') if ruleid_elt is not None: self._delete_associated_rule(ruleid_elt.text) def _set_associated_rule(self, before=None): """ manage changes to the associated rule """ if before is None: if self.params['associated_rule'] == 'associated' or self.params[ 'associated_rule'] == 'unassociated': self._create_associated_rule() else: if self.params['associated_rule'] == 'associated': if before['associated-rule-id'].startswith('nat_'): if self.params['interface'] != before['interface']: self._delete_associated_rule( before['associated-rule-id'], before['interface']) else: self.obj['associated-rule-id'] = before[ 'associated-rule-id'] return self._create_associated_rule() elif before['associated-rule-id'].startswith('nat_'): self._delete_associated_rule(before['associated-rule-id']) def _update_rule_position(self, rule_elt): """ move rule in xml if required """ current_position = self._get_rule_position() expected_position = self._get_expected_rule_position() if current_position == expected_position: self.position_changed = False return False self.diff['before']['position'] = current_position self.diff['after']['position'] = expected_position self.root_elt.remove(rule_elt) self._insert(rule_elt) self.position_changed = True return True ############################## # run # def _update(self): """ make the target pfsense reload """ return self.pfsense.phpshell('''require_once("filter.inc"); if (filter_configure() == 0) { clear_subsystem_dirty('natconf'); clear_subsystem_dirty('filter'); }''' ) ############################## # Logging # def _get_obj_name(self): """ return obj's name """ return "'{0}'".format(self.obj['descr']) @staticmethod def fassociate(value): """ associated-rule-id value formatting function """ if value is None or value == '': return 'none' if value == 'pass': return 'pass' return 'associated' @staticmethod def fnatreflection(value): """ natreflection value formatting function """ if value is None or value == 'none': return "'system-default'" return value @staticmethod def fprotocol(value): """ protocol value formatting function """ if value is None or value == 'none': return 'any' return value def _log_fields(self, before=None): """ generate pseudo-CLI command fields parameters to create an obj """ values = '' fafter = self._obj_to_log_fields(self.obj) if before is None: values += self.format_cli_field(self.params, 'disabled', fvalue=self.fvalue_bool, default=False) values += self.format_cli_field(self.params, 'nordr', fvalue=self.fvalue_bool, default=False) values += self.format_cli_field(self.params, 'interface') values += self.format_cli_field(self.params, 'protocol', default='tcp') values += self.format_cli_field(self.params, 'source') values += self.format_cli_field(self.params, 'destination') values += self.format_cli_field(self.params, 'target') values += self.format_cli_field(self.params, 'natreflection', default='system-default') values += self.format_cli_field(self.params, 'associated_rule', default='associated') values += self.format_cli_field(self.params, 'nosync', fvalue=self.fvalue_bool, default=False) values += self.format_cli_field(self.params, 'after') values += self.format_cli_field(self.params, 'before') else: fbefore = self._obj_to_log_fields(before) fafter['before'] = self.before fafter['after'] = self.after values += self.format_updated_cli_field(self.obj, before, 'disabled', fvalue=self.fvalue_bool, default=False, add_comma=(values)) values += self.format_updated_cli_field(self.obj, before, 'nordr', fvalue=self.fvalue_bool, default=False, add_comma=(values)) values += self.format_updated_cli_field(fafter, fbefore, 'interface', add_comma=(values)) values += self.format_updated_cli_field(self.obj, before, 'protocol', fvalue=self.fprotocol, add_comma=(values)) values += self.format_updated_cli_field(fafter, fbefore, 'source', add_comma=(values)) values += self.format_updated_cli_field(fafter, fbefore, 'destination', add_comma=(values)) values += self.format_updated_cli_field(fafter, fbefore, 'target', add_comma=(values)) values += self.format_updated_cli_field(self.obj, before, 'natreflection', fvalue=self.fnatreflection, default='system-default', add_comma=(values)) values += self.format_updated_cli_field(self.obj, before, 'associated-rule-id', fvalue=self.fassociate, fname='associated_rule', add_comma=(values)) values += self.format_updated_cli_field(self.obj, before, 'nosync', fvalue=self.fvalue_bool, default=False, add_comma=(values)) if self.position_changed: values += self.format_updated_cli_field(fafter, {}, 'after', log_none=False, add_comma=(values)) values += self.format_updated_cli_field(fafter, {}, 'before', log_none=False, add_comma=(values)) return values @staticmethod def _obj_address_to_log_field(rule, addr): """ return formated address from dict """ field = '' if isinstance(rule[addr], dict): if 'any' in rule[addr]: field = 'any' if 'address' in rule[addr]: field = rule[addr]['address'] if 'port' in rule[addr]: if field: field += ':' field += rule[addr]['port'] else: field = rule[addr] return field def _obj_to_log_fields(self, rule): """ return formated source and destination from dict """ res = {} res['source'] = self._obj_address_to_log_field(rule, 'source') res['destination'] = self._obj_address_to_log_field( rule, 'destination') res['target'] = rule['target'] + ':' + rule['local-port'] res['interface'] = self.pfsense.get_interface_display_name( rule['interface']) return res