From 75127092f23690246af2ff6e35ec81d186b832aa Mon Sep 17 00:00:00 2001 From: Adam Miller Date: Mon, 11 Sep 2017 14:10:07 -0700 Subject: [PATCH] refactor firewalld module with object abstraction (#26487) * refactor firewalld module with object abstraction This change creates a FirewallTransaction object that each individual transaction type is a sub-class of as they all follow the same pattern to enable or disable something in the firewall. Also, there's a few bugfixes here: - Fix the "source" type to handle permanent operations - Remove ambiguity of required parameters for only specific use cases that can lead to transactions effectively being a no-op. Instead, pick sane defaults and document them. - Change how imports are done so globals are no longer needed This is based on the original feedback by Toshio from the last refactor attempt: https://github.com/ansible/ansible-modules-extras/pull/3383 Signed-off-by: Adam Miller * fix line too long for pep8 for shippable tests Signed-off-by: Adam Miller * remove firewalld from pep8/legacy-files Signed-off-by: Adam Miller --- lib/ansible/modules/system/firewalld.py | 1480 +++++++++++------------ test/sanity/pep8/legacy-files.txt | 1 - 2 files changed, 675 insertions(+), 806 deletions(-) diff --git a/lib/ansible/modules/system/firewalld.py b/lib/ansible/modules/system/firewalld.py index cadea8b814..ff54f57571 100644 --- a/lib/ansible/modules/system/firewalld.py +++ b/lib/ansible/modules/system/firewalld.py @@ -60,7 +60,7 @@ options: description: - > Should this configuration be in the running firewalld configuration or persist across reboots. As of Ansible version 2.3, permanent operations can - operate on firewalld configs when it's not running (requires firewalld >= 3.0.9) + operate on firewalld configs when it's not running (requires firewalld >= 3.0.9). (NOTE: If this is false, immediate is assumed true.) required: false default: null immediate: @@ -139,388 +139,656 @@ EXAMPLES = ''' from ansible.module_utils.basic import AnsibleModule -import sys - -##################### -# Globals -# -fw = None +# globals module = None -fw_offline = False -Rich_Rule = None -FirewallClientZoneSettings = None +# Imports +try: + import firewall.config + FW_VERSION = firewall.config.VERSION -##################### -# exception handling -# -def action_handler(action_func, action_func_args): - """ - Function to wrap calls to make actions on firewalld in try/except - logic and emit (hopefully) useful error messages - """ - - msgs = [] + from firewall.client import Rich_Rule + from firewall.client import FirewallClient + fw = None + fw_offline = False + import_failure = False try: - return action_func(*action_func_args) - except Exception: - # Make python 2.4 shippable ci tests happy - e = sys.exc_info()[1] + fw = FirewallClient() + fw.getDefaultZone() + except AttributeError: + # Firewalld is not currently running, permanent-only operations - # If there are any commonly known errors that we should provide more - # context for to help the users diagnose what's wrong. Handle that here - if "INVALID_SERVICE" in "%s" % e: - msgs.append("Services are defined by port/tcp relationship and named as they are in /etc/services (on most systems)") + # Import other required parts of the firewalld API + # + # NOTE: + # online and offline operations do not share a common firewalld API + from firewall.core.fw_test import Firewall_test + from firewall.client import FirewallClientZoneSettings + fw = Firewall_test() + fw.start() + fw_offline = True - if len(msgs) > 0: - module.fail_json( - msg='ERROR: Exception caught: %s %s' % (e, ', '.join(msgs)) +except ImportError as e: + import_failure = True + + +class FirewallTransaction(object): + """ + FirewallTransaction + + This is the base class for all firewalld transactions we might want to have + """ + + global module + + def __init__(self, fw, action_args=(), zone=None, desired_state=None, + permanent=False, immediate=False, fw_offline=False): + # type: (firewall.client, tuple, str, bool, bool, bool) + """ + initializer the transaction + + :fw: firewall client instance + :action_args: tuple, args to pass for the action to take place + :zone: str, firewall zone + :desired_state: str, the desired state (enabled, disabled, etc) + :permanent: bool, action should be permanent + :immediate: bool, action should take place immediately + :fw_offline: bool, action takes place as if the firewall were offline + """ + + self.fw = fw + self.action_args = action_args + self.zone = zone + self.desired_state = desired_state + self.permanent = permanent + self.immediate = immediate + self.fw_offline = fw_offline + + # List of messages that we'll call module.fail_json or module.exit_json + # with. + self.msgs = [] + + # Allow for custom messages to be added for certain subclass transaction + # types + self.enabled_msg = None + self.disabled_msg = None + + # List of acceptable values to enable/diable something + # Right now these are only 1 each but in the event we want to add more + # later, this will make it easy. + self.enabled_values = ["enabled"] + self.disabled_values = ["disabled"] + + ##################### + # exception handling + # + def action_handler(self, action_func, action_func_args): + """ + Function to wrap calls to make actions on firewalld in try/except + logic and emit (hopefully) useful error messages + """ + + try: + return action_func(*action_func_args) + except Exception as e: + + # If there are any commonly known errors that we should provide more + # context for to help the users diagnose what's wrong. Handle that here + if "INVALID_SERVICE" in "%s" % e: + self.msgs.append("Services are defined by port/tcp relationship and named as they are in /etc/services (on most systems)") + + if len(self.msgs) > 0: + module.fail_json( + msg='ERROR: Exception caught: %s %s' % (e, ', '.join(self.msgs)) + ) + else: + module.fail_json(msg='ERROR: Exception caught: %s' % e) + + def get_fw_zone_settings(self): + if self.fw_offline: + fw_zone = self.fw.config.get_zone(self.zone) + fw_settings = FirewallClientZoneSettings( + list(self.fw.config.get_zone_config(fw_zone)) ) else: - module.fail_json(msg='ERROR: Exception caught: %s' % e) + fw_zone = self.fw.config().getZoneByName(self.zone) + fw_settings = fw_zone.getSettings() -##################### -# fw_offline helpers -# -def get_fw_zone_settings(zone): - if fw_offline: - fw_zone = fw.config.get_zone(zone) - fw_settings = FirewallClientZoneSettings( - list(fw.config.get_zone_config(fw_zone)) - ) - else: - fw_zone = fw.config().getZoneByName(zone) - fw_settings = fw_zone.getSettings() + return (fw_zone, fw_settings) - return (fw_zone, fw_settings) - -def update_fw_settings(fw_zone, fw_settings): - if fw_offline: - fw.config.set_zone_config(fw_zone, fw_settings.settings) - else: - fw_zone.update(fw_settings) - -##################### -# masquerade handling -# -def get_masquerade_enabled(zone): - if fw.queryMasquerade(zone) is True: - return True - else: - return False - -def get_masquerade_enabled_permanent(zone): - fw_zone, fw_settings = get_fw_zone_settings(zone) - if fw_settings.getMasquerade() is True: - return True - else: - return False - -def set_masquerade_enabled(zone): - fw.addMasquerade(zone) - -def set_masquerade_disabled(zone): - fw.removeMasquerade(zone) - -def set_masquerade_permanent(zone, masquerade): - fw_zone, fw_settings = get_fw_zone_settings(zone) - fw_settings.setMasquerade(masquerade) - update_fw_settings(fw_zone, fw_settings) - -################ -# port handling -# -def get_port_enabled(zone, port_proto): - if fw_offline: - fw_zone, fw_settings = get_fw_zone_settings(zone) - ports_list = fw_settings.getPorts() - else: - ports_list = fw.getPorts(zone) - - if port_proto in ports_list: - return True - else: - return False - -def set_port_enabled(zone, port, protocol, timeout): - fw.addPort(zone, port, protocol, timeout) - -def set_port_disabled(zone, port, protocol): - fw.removePort(zone, port, protocol) - -def get_port_enabled_permanent(zone, port_proto): - fw_zone, fw_settings = get_fw_zone_settings(zone) - - if tuple(port_proto) in fw_settings.getPorts(): - return True - else: - return False - -def set_port_enabled_permanent(zone, port, protocol): - fw_zone, fw_settings = get_fw_zone_settings(zone) - fw_settings.addPort(port, protocol) - update_fw_settings(fw_zone, fw_settings) - -def set_port_disabled_permanent(zone, port, protocol): - fw_zone, fw_settings = get_fw_zone_settings(zone) - fw_settings.removePort(port, protocol) - update_fw_settings(fw_zone, fw_settings) - -#################### -# source handling -# -def get_source(zone, source): - fw_zone, fw_settings = get_fw_zone_settings(zone) - if source in fw_settings.getSources(): - return True - else: - return False - -def add_source(zone, source): - fw_zone, fw_settings = get_fw_zone_settings(zone) - fw_settings.addSource(source) - update_fw_settings(fw_zone, fw_settings) - -def remove_source(zone, source): - fw_zone, fw_settings = get_fw_zone_settings(zone) - fw_settings.removeSource(source) - update_fw_settings(fw_zone, fw_settings) - -#################### -# interface handling -# -def get_interface(zone, interface): - if fw_offline: - fw_zone, fw_settings = get_fw_zone_settings(zone) - interface_list = fw_settings.getInterfaces() - else: - interface_list = fw.getInterfaces(zone) - if interface in fw.getInterfaces(zone): - return True - else: - return False - -def change_zone_of_interface(zone, interface): - fw.changeZoneOfInterface(zone, interface) - -def remove_interface(zone, interface): - fw.removeInterface(zone, interface) - -def get_interface_permanent(zone, interface): - fw_zone, fw_settings = get_fw_zone_settings(zone) - - if interface in fw_settings.getInterfaces(): - return True - else: - return False - -def change_zone_of_interface_permanent(zone, interface): - fw_zone, fw_settings = get_fw_zone_settings(zone) - if fw_offline: - iface_zone_objs = [ ] - for zone in fw.config.get_zones(): - old_zone_obj = fw.config.get_zone(zone) - if interface in old_zone_obj.interfaces: - iface_zone_objs.append(old_zone_obj) - if len(iface_zone_objs) > 1: - # Even it shouldn't happen, it's actually possible that - # the same interface is in several zone XML files - module.fail_json( - msg = 'ERROR: interface {} is in {} zone XML file, can only be in one'.format( - interface, - len(iface_zone_objs) - ) - ) - old_zone_obj = iface_zone_objs[0] - if old_zone_obj.name != zone: - old_zone_settings = FirewallClientZoneSettings( - fw.config.get_zone_config(old_zone_obj) - ) - old_zone_settings.removeInterface(interface) # remove from old - fw.config.set_zone_config(old_zone_obj, old_zone_settings.settings) - - fw_settings.addInterface(interface) # add to new - fw.config.set_zone_config(fw_zone, fw_settings.settings) - else: - old_zone_name = fw.config().getZoneOfInterface(interface) - if old_zone_name != zone: - if old_zone_name: - old_zone_obj = fw.config().getZoneByName(old_zone_name) - old_zone_settings = old_zone_obj.getSettings() - old_zone_settings.removeInterface(interface) # remove from old - old_zone_obj.update(old_zone_settings) - fw_settings.addInterface(interface) # add to new + def update_fw_settings(self, fw_zone, fw_settings): + if self.fw_offline: + self.fw.config.set_zone_config(fw_zone, fw_settings.settings) + else: fw_zone.update(fw_settings) -def remove_interface_permanent(zone, interface): - fw_zone, fw_settings = get_fw_zone_settings(zone) - fw_settings.removeInterface(interface) - update_fw_settings(fw_zone, fw_settings) + def get_enabled_immediate(self): + raise NotImplementedError -#################### -# service handling -# -def get_service_enabled(zone, service): - if service in fw.getServices(zone): - return True - else: - return False + def get_enabled_permanent(self): + raise NotImplementedError -def set_service_enabled(zone, service, timeout): - fw.addService(zone, service, timeout) + def set_enabled_immediate(self): + raise NotImplementedError -def set_service_disabled(zone, service): - fw.removeService(zone, service) + def set_enabled_permanent(self): + raise NotImplementedError -def get_service_enabled_permanent(zone, service): - fw_zone, fw_settings = get_fw_zone_settings(zone) + def set_disabled_immediate(self): + raise NotImplementedError - if service in fw_settings.getServices(): - return True - else: - return False + def set_disabled_permanent(self): + raise NotImplementedError -def set_service_enabled_permanent(zone, service): - fw_zone, fw_settings = get_fw_zone_settings(zone) - fw_settings.addService(service) - update_fw_settings(fw_zone, fw_settings) + def run(self): + """ + run -def set_service_disabled_permanent(zone, service): - fw_zone, fw_settings = get_fw_zone_settings(zone) - fw_settings.removeService(service) - update_fw_settings(fw_zone, fw_settings) + This fuction contains the "transaction logic" where as all operations + follow a similar pattern in order to perform their action but simply + call different functions to carry that action out. + """ -#################### -# rich rule handling -# -def get_rich_rule_enabled(zone, rule): - # Convert the rule string to standard format - # before checking whether it is present - rule = str(Rich_Rule(rule_str=rule)) - if rule in fw.getRichRules(zone): - return True - else: - return False + self.changed = False -def set_rich_rule_enabled(zone, rule, timeout): - fw.addRichRule(zone, rule, timeout) + if self.immediate and self.permanent: + is_enabled_permanent = self.action_handler( + self.get_enabled_permanent, + self.action_args + ) + is_enabled_immediate = self.action_handler( + self.get_enabled_immediate, + self.action_args + ) + self.msgs.append('Permanent and Non-Permanent(immediate) operation') -def set_rich_rule_disabled(zone, rule): - fw.removeRichRule(zone, rule) + if self.desired_state in self.enabled_values: + if not is_enabled_permanent or not is_enabled_immediate: + if module.check_mode: + module.exit_json(changed=True) + if not is_enabled_permanent: + self.action_handler( + self.set_enabled_permanent, + self.action_args + ) + self.changed = True + if not is_enabled_immediate: + self.action_handler( + self.set_enabled_immediate, + self.action_args + ) + self.changed = True + if self.changed and self.enabled_msg: + self.msgs.append(self.enabled_msg) -def get_rich_rule_enabled_permanent(zone, rule): - fw_zone, fw_settings = get_fw_zone_settings(zone) - # Convert the rule string to standard format - # before checking whether it is present - rule = str(Rich_Rule(rule_str=rule)) - if rule in fw_settings.getRichRules(): - return True - else: - return False + elif self.desired_state in self.disabled_values: + if is_enabled_permanent or is_enabled_immediate: + if module.check_mode: + module.exit_json(changed=True) + if is_enabled_permanent: + self.action_handler( + self.set_disabled_permanent, + self.action_args + ) + self.changed = True + if is_enabled_immediate: + self.action_handler( + self.set_disabled_immediate, + self.action_args + ) + self.changed = True + if self.changed and self.disabled_msg: + self.msgs.append(self.disabled_msg) -def set_rich_rule_enabled_permanent(zone, rule): - fw_zone, fw_settings = get_fw_zone_settings(zone) - fw_settings.addRichRule(rule) - update_fw_settings(fw_zone, fw_settings) + elif self.permanent and not self.immediate: + is_enabled = self.action_handler( + self.get_enabled_permanent, + self.action_args + ) + self.msgs.append('Permanent operation') + + if self.desired_state in self.enabled_values: + if not is_enabled: + if module.check_mode: + module.exit_json(changed=True) + + self.action_handler( + self.set_enabled_permanent, + self.action_args + ) + self.changed = True + if self.changed and self.enabled_msg: + self.msgs.append(self.enabled_msg) + + elif self.desired_state in self.disabled_values: + if is_enabled: + if module.check_mode: + module.exit_json(changed=True) + + self.action_handler( + self.set_disabled_permanent, + self.action_args + ) + self.changed = True + if self.changed and self.disabled_msg: + self.msgs.append(self.disabled_msg) + + elif self.immediate and not self.permanent: + is_enabled = self.action_handler( + self.get_enabled_immediate, + self.action_args + ) + self.msgs.append('Non-permanent operation') + + if self.desired_state in self.enabled_values: + if not is_enabled: + if module.check_mode: + module.exit_json(changed=True) + + self.action_handler( + self.set_enabled_immediate, + self.action_args + ) + self.changed = True + if self.changed and self.enabled_msg: + self.msgs.append(self.enabled_msg) + + elif self.desired_state in self.disabled_values: + if is_enabled: + if module.check_mode: + module.exit_json(changed=True) + + self.action_handler( + self.set_disabled_immediate, + self.action_args + ) + self.changed = True + if self.changed and self.disabled_msg: + self.msgs.append(self.disabled_msg) + + return (self.changed, self.msgs) + + +class ServiceTransaction(FirewallTransaction): + """ + ServiceTransaction + """ + + def __init__(self, fw, action_args=None, zone=None, desired_state=None, + permanent=False, immediate=False, fw_offline=False): + super(ServiceTransaction, self).__init__( + fw, action_args=action_args, desired_state=desired_state, zone=zone, + permanent=permanent, immediate=immediate, fw_offline=fw_offline) + + def get_enabled_immediate(self, service, timeout): + if service in self.fw.getServices(self.zone): + return True + else: + return False + + def get_enabled_permanent(self, service, timeout): + fw_zone, fw_settings = self.get_fw_zone_settings() + + if service in fw_settings.getServices(): + return True + else: + return False + + def set_enabled_immediate(self, service, timeout): + self.fw.addService(self.zone, service, timeout) + + def set_enabled_permanent(self, service, timeout): + fw_zone, fw_settings = self.get_fw_zone_settings() + fw_settings.addService(service) + self.update_fw_settings(fw_zone, fw_settings) + + def set_disabled_immediate(self, service, timeout): + self.fw.removeService(self.zone, service) + + def set_disabled_permanent(self, service, timeout): + fw_zone, fw_settings = self.get_fw_zone_settings() + fw_settings.removeService(service) + self.update_fw_settings(fw_zone, fw_settings) + + +class MasqueradeTransaction(FirewallTransaction): + """ + MasqueradeTransaction + """ + + def __init__(self, fw, action_args=None, zone=None, desired_state=None, + permanent=False, immediate=False, fw_offline=False): + super(MasqueradeTransaction, self).__init__( + fw, action_args=action_args, desired_state=desired_state, zone=zone, + permanent=permanent, immediate=immediate, fw_offline=fw_offline) + + self.enabled_msg = "Added masquerade to zone %s" % self.zone + self.disabled_msg = "Removed masquerade from zone %s" % self.zone + + def get_enabled_immediate(self): + if self.fw.queryMasquerade(self.zone) is True: + return True + else: + return False + + def get_enabled_permanent(self): + fw_zone, fw_settings = self.get_fw_zone_settings() + if fw_settings.getMasquerade() is True: + return True + else: + return False + + def set_enabled_immediate(self): + self.fw.addMasquerade(self.zone) + + def set_enabled_permanent(self): + fw_zone, fw_settings = self.get_fw_zone_settings() + fw_settings.setMasquerade(True) + self.update_fw_settings(fw_zone, fw_settings) + + def set_disabled_immediate(self): + self.fw.removeMasquerade(self.zone) + + def set_disabled_permanent(self): + fw_zone, fw_settings = self.get_fw_zone_settings() + fw_settings.setMasquerade(False) + self.update_fw_settings(fw_zone, fw_settings) + + +class PortTransaction(FirewallTransaction): + """ + PortTransaction + """ + + def __init__(self, fw, action_args=None, zone=None, desired_state=None, + permanent=False, immediate=False, fw_offline=False): + super(PortTransaction, self).__init__( + fw, action_args=action_args, desired_state=desired_state, zone=zone, + permanent=permanent, immediate=immediate, fw_offline=fw_offline) + + def get_enabled_immediate(self, port, protocol, timeout): + port_proto = [port, protocol] + if self.fw_offline: + fw_zone, fw_settings = self.get_fw_zone_settings() + ports_list = fw_settings.getPorts() + else: + ports_list = self.fw.getPorts(self.zone) + + if port_proto in ports_list: + return True + else: + return False + + def get_enabled_permanent(self, port, protocol, timeout): + port_proto = (port, protocol) + fw_zone, fw_settings = self.get_fw_zone_settings() + + if port_proto in fw_settings.getPorts(): + return True + else: + return False + + def set_enabled_immediate(self, port, protocol, timeout): + self.fw.addPort(self.zone, port, protocol, timeout) + + def set_enabled_permanent(self, port, protocol, timeout): + fw_zone, fw_settings = self.get_fw_zone_settings() + fw_settings.addPort(port, protocol) + self.update_fw_settings(fw_zone, fw_settings) + + def set_disabled_immediate(self, port, protocol, timeout): + self.fw.removePort(self.zone, port, protocol) + + def set_disabled_permanent(self, port, protocol, timeout): + fw_zone, fw_settings = self.get_fw_zone_settings() + fw_settings.removePort(port, protocol) + self.update_fw_settings(fw_zone, fw_settings) + + +class InterfaceTransaction(FirewallTransaction): + """ + InterfaceTransaction + """ + + def __init__(self, fw, action_args=None, zone=None, desired_state=None, + permanent=False, immediate=False, fw_offline=False): + super(InterfaceTransaction, self).__init__( + fw, action_args=action_args, desired_state=desired_state, zone=zone, + permanent=permanent, immediate=immediate, fw_offline=fw_offline) + + self.enabled_msg = "Changed %s to zone %s" % \ + (self.action_args[0], self.zone) + + self.disabled_msg = "Removed %s from zone %s" % \ + (self.action_args[0], self.zone) + + def get_enabled_immediate(self, interface): + if self.fw_offline: + fw_zone, fw_settings = self.get_fw_zone_settings() + interface_list = fw_settings.getInterfaces() + else: + interface_list = self.fw.getInterfaces(self.zone) + if interface in interface_list: + return True + else: + return False + + def get_enabled_permanent(self, interface): + fw_zone, fw_settings = self.get_fw_zone_settings() + + if interface in fw_settings.getInterfaces(): + return True + else: + return False + + def set_enabled_immediate(self, interface): + self.fw.changeZoneOfInterface(self.zone, interface) + + def set_enabled_permanent(self, interface): + fw_zone, fw_settings = self.get_fw_zone_settings() + if self.fw_offline: + iface_zone_objs = [] + for zone in self.fw.config.get_zones(): + old_zone_obj = self.fw.config.get_zone(zone) + if interface in old_zone_obj.interfaces: + iface_zone_objs.append(old_zone_obj) + if len(iface_zone_objs) > 1: + # Even it shouldn't happen, it's actually possible that + # the same interface is in several zone XML files + module.fail_json( + msg='ERROR: interface {} is in {} zone XML file, can only be in one'.format( + interface, + len(iface_zone_objs) + ) + ) + old_zone_obj = iface_zone_objs[0] + if old_zone_obj.name != self.zone: + old_zone_settings = FirewallClientZoneSettings( + self.fw.config.get_zone_config(old_zone_obj) + ) + old_zone_settings.removeInterface(interface) # remove from old + self.fw.config.set_zone_config( + old_zone_obj, + old_zone_settings.settings + ) + fw_settings.addInterface(interface) # add to new + self.fw.config.set_zone_config(fw_zone, fw_settings.settings) + else: + old_zone_name = self.fw.config().getZoneOfInterface(interface) + if old_zone_name != self.zone: + if old_zone_name: + old_zone_obj = self.fw.config().getZoneByName(old_zone_name) + old_zone_settings = old_zone_obj.getSettings() + old_zone_settings.removeInterface(interface) # remove from old + old_zone_obj.update(old_zone_settings) + fw_settings.addInterface(interface) # add to new + fw_zone.update(fw_settings) + + def set_disabled_immediate(self, interface): + self.fw.removeInterface(self.zone, interface) + + def set_disabled_permanent(self, interface): + fw_zone, fw_settings = self.get_fw_zone_settings() + fw_settings.removeInterface(interface) + self.update_fw_settings(fw_zone, fw_settings) + + +class RichRuleTransaction(FirewallTransaction): + """ + RichRuleTransaction + """ + + def __init__(self, fw, action_args=None, zone=None, desired_state=None, + permanent=False, immediate=False, fw_offline=False): + super(RichRuleTransaction, self).__init__( + fw, action_args=action_args, desired_state=desired_state, zone=zone, + permanent=permanent, immediate=immediate, fw_offline=fw_offline) + + def get_enabled_immediate(self, rule, timeout): + # Convert the rule string to standard format + # before checking whether it is present + rule = str(Rich_Rule(rule_str=rule)) + if rule in self.fw.getRichRules(self.zone): + return True + else: + return False + + def get_enabled_permanent(self, rule, timeout): + fw_zone, fw_settings = self.get_fw_zone_settings() + # Convert the rule string to standard format + # before checking whether it is present + rule = str(Rich_Rule(rule_str=rule)) + if rule in fw_settings.getRichRules(): + return True + else: + return False + + def set_enabled_immediate(self, rule, timeout): + self.fw.addRichRule(self.zone, rule, timeout) + + def set_enabled_permanent(self, rule, timeout): + fw_zone, fw_settings = self.get_fw_zone_settings() + fw_settings.addRichRule(rule) + self.update_fw_settings(fw_zone, fw_settings) + + def set_disabled_immediate(self, rule, timeout): + fw.removeRichRule(self.zone, rule) + + def set_disabled_permanent(self, rule, timeout): + fw_zone, fw_settings = self.get_fw_zone_settings() + fw_settings.removeRichRule(rule) + self.update_fw_settings(fw_zone, fw_settings) + + +class SourceTransaction(FirewallTransaction): + """ + SourceTransaction + """ + + def __init__(self, fw, action_args=None, zone=None, desired_state=None, + permanent=False, immediate=False, fw_offline=False): + super(SourceTransaction, self).__init__( + fw, action_args=action_args, desired_state=desired_state, zone=zone, + permanent=permanent, immediate=immediate, fw_offline=fw_offline) + + self.enabled_msg = "Added %s to zone %s" % \ + (self.action_args[0], self.zone) + + self.disabled_msg = "Removed %s from zone %s" % \ + (self.action_args[0], self.zone) + + def get_enabled_immediate(self, source): + if source in self.fw.getSources(self.zone): + return True + else: + return False + + def get_enabled_permanent(self, source): + fw_zone, fw_settings = self.get_fw_zone_settings() + if source in fw_settings.getSources(): + return True + else: + return False + + def set_enabled_immediate(self, source): + self.fw.addSource(self.zone, source) + + def set_enabled_permanent(self, source): + fw_zone, fw_settings = self.get_fw_zone_settings() + fw_settings.addSource(source) + self.update_fw_settings(fw_zone, fw_settings) + + def set_disabled_immediate(self, source): + self.fw.removeSource(self.zone, source) + + def set_disabled_permanent(self, source): + fw_zone, fw_settings = self.get_fw_zone_settings() + fw_settings.removeSource(source) + self.update_fw_settings(fw_zone, fw_settings) -def set_rich_rule_disabled_permanent(zone, rule): - fw_zone, fw_settings = get_fw_zone_settings(zone) - fw_settings.removeRichRule(rule) - update_fw_settings(fw_zone, fw_settings) def main(): - global module - ## make module global so we don't have to pass it to action_handler every - ## function call global module module = AnsibleModule( - argument_spec = dict( - service=dict(required=False,default=None), - port=dict(required=False,default=None), - rich_rule=dict(required=False,default=None), - zone=dict(required=False,default=None), - immediate=dict(type='bool',default=False), - source=dict(required=False,default=None), - permanent=dict(type='bool',required=False,default=None), + argument_spec=dict( + service=dict(required=False, default=None), + port=dict(required=False, default=None), + rich_rule=dict(required=False, default=None), + zone=dict(required=False, default=None), + immediate=dict(type='bool', default=False), + source=dict(required=False, default=None), + permanent=dict(type='bool', required=False, default=None), state=dict(choices=['enabled', 'disabled'], required=True), - timeout=dict(type='int',required=False,default=0), - interface=dict(required=False,default=None), - masquerade=dict(required=False,default=None), - offline=dict(type='bool',required=False,default=None), + timeout=dict(type='int', required=False, default=0), + interface=dict(required=False, default=None), + masquerade=dict(required=False, default=None), + offline=dict(type='bool', required=False, default=None), ), supports_check_mode=True ) - ## Handle running (online) daemon vs non-running (offline) daemon - global fw - global fw_offline - global Rich_Rule - global FirewallClientZoneSettings - - ## Imports - try: - import firewall.config - FW_VERSION = firewall.config.VERSION - - from firewall.client import Rich_Rule - from firewall.client import FirewallClient - fw = None - fw_offline = False - - try: - fw = FirewallClient() - fw.getDefaultZone() - except AttributeError: - ## Firewalld is not currently running, permanent-only operations - - ## Import other required parts of the firewalld API - ## - ## NOTE: - ## online and offline operations do not share a common firewalld API - from firewall.core.fw_test import Firewall_test - from firewall.client import FirewallClientZoneSettings - fw = Firewall_test() - fw.start() - fw_offline = True - - except ImportError: - ## Make python 2.4 shippable ci tests happy - e = sys.exc_info()[1] - module.fail_json(msg='firewalld and its python 2 module are required for this module, version 2.0.11 or newer required ' - '(3.0.9 or newer for offline operations) \n %s' % e) + if import_failure: + module.fail_json( + msg='firewalld and its python 2 module are required for this module, version 2.0.11 or newer required (3.0.9 or newer for offline operations) \n %s' + % e + ) if fw_offline: - ## Pre-run version checking + # Pre-run version checking if FW_VERSION < "0.3.9": - module.fail_json(msg='unsupported version of firewalld, offline operations require >= 3.0.9') + module.fail_json(msg='unsupported version of firewalld, offline operations require >= 0.3.9') else: - ## Pre-run version checking + # Pre-run version checking if FW_VERSION < "0.2.11": - module.fail_json(msg='unsupported version of firewalld, requires >= 2.0.11') + module.fail_json(msg='unsupported version of firewalld, requires >= 0.2.11') - ## Check for firewalld running + # Check for firewalld running try: if fw.connected is False: module.fail_json(msg='firewalld service must be running, or try with offline=true') except AttributeError: module.fail_json(msg="firewalld connection can't be established,\ - installed version (%s) likely too old. Requires firewalld >= 2.0.11" % FW_VERSION) + installed version (%s) likely too old. Requires firewalld >= 0.2.11" % FW_VERSION) + permanent = module.params['permanent'] + desired_state = module.params['state'] + immediate = module.params['immediate'] + timeout = module.params['timeout'] + interface = module.params['interface'] + masquerade = module.params['masquerade'] - ## Verify required params are provided - if module.params['source'] is None and module.params['permanent'] is None: - module.fail_json(msg='permanent is a required parameter') + # If neither permanent or immediate is provided, assume immediate (as + # written in the module's docs) + if not permanent and not immediate: + immediate = True - if module.params['interface'] is not None and module.params['zone'] is None: - module.fail(msg='zone is a required parameter') - - if module.params['immediate'] and fw_offline: + # Verify required params are provided + if immediate and fw_offline: module.fail(msg='firewall is not currently running, unable to perform immediate actions without a running firewall daemon') - ## Global Vars - changed=False + changed = False msgs = [] service = module.params['service'] rich_rule = module.params['rich_rule'] @@ -533,6 +801,7 @@ def main(): else: port = None + # If we weren't provided a zone, then just use the system default if module.params['zone'] is not None: zone = module.params['zone'] else: @@ -541,13 +810,6 @@ def main(): else: zone = fw.getDefaultZone() - permanent = module.params['permanent'] - desired_state = module.params['state'] - immediate = module.params['immediate'] - timeout = module.params['timeout'] - interface = module.params['interface'] - masquerade = module.params['masquerade'] - modification_count = 0 if service is not None: modification_count += 1 @@ -561,505 +823,113 @@ def main(): modification_count += 1 if modification_count > 1: - module.fail_json(msg='can only operate on port, service, rich_rule or interface at once') + module.fail_json( + msg='can only operate on port, service, rich_rule, or interface at once' + ) if service is not None: - if immediate and permanent: - is_enabled_permanent = action_handler( - get_service_enabled_permanent, - (zone, service) - ) - is_enabled_immediate = action_handler( - get_service_enabled, - (zone, service) - ) - msgs.append('Permanent and Non-Permanent(immediate) operation') - if desired_state == "enabled": - if not is_enabled_permanent or not is_enabled_immediate: - if module.check_mode: - module.exit_json(changed=True) - if not is_enabled_permanent: - action_handler( - set_service_enabled_permanent, - (zone, service) - ) - changed=True - if not is_enabled_immediate: - action_handler( - set_service_enabled, - (zone, service, timeout) - ) - changed=True - - - elif desired_state == "disabled": - if is_enabled_permanent or is_enabled_immediate: - if module.check_mode: - module.exit_json(changed=True) - if is_enabled_permanent: - action_handler( - set_service_disabled_permanent, - (zone, service) - ) - changed=True - if is_enabled_immediate: - action_handler( - set_service_disabled, - (zone, service) - ) - changed=True - - elif permanent and not immediate: - is_enabled = action_handler( - get_service_enabled_permanent, - (zone, service) - ) - msgs.append('Permanent operation') - - if desired_state == "enabled": - if is_enabled is False: - if module.check_mode: - module.exit_json(changed=True) - - action_handler( - set_service_enabled_permanent, - (zone, service) - ) - changed=True - elif desired_state == "disabled": - if is_enabled is True: - if module.check_mode: - module.exit_json(changed=True) - - action_handler( - set_service_disabled_permanent, - (zone, service) - ) - changed=True - elif immediate and not permanent: - is_enabled = action_handler( - get_service_enabled, - (zone, service) - ) - msgs.append('Non-permanent operation') - - - if desired_state == "enabled": - if is_enabled is False: - if module.check_mode: - module.exit_json(changed=True) - - action_handler( - set_service_enabled, - (zone, service, timeout) - ) - changed=True - elif desired_state == "disabled": - if is_enabled is True: - if module.check_mode: - module.exit_json(changed=True) - - action_handler( - set_service_disabled, - (zone, service) - ) - changed=True + transaction = ServiceTransaction( + fw, + action_args=(service, timeout), + zone=zone, + desired_state=desired_state, + permanent=permanent, + immediate=immediate, + fw_offline=fw_offline + ) + changed, transaction_msgs = transaction.run() + msgs = msgs + transaction_msgs if changed is True: msgs.append("Changed service %s to %s" % (service, desired_state)) - # FIXME - source type does not handle non-permanent mode, this was an - # oversight in the past. if source is not None: - is_enabled = action_handler(get_source, (zone, source)) - if desired_state == "enabled": - if is_enabled is False: - if module.check_mode: - module.exit_json(changed=True) - action_handler(add_source, (zone, source)) - changed=True - msgs.append("Added %s to zone %s" % (source, zone)) - elif desired_state == "disabled": - if is_enabled is True: - if module.check_mode: - module.exit_json(changed=True) + transaction = SourceTransaction( + fw, + action_args=(source,), + zone=zone, + desired_state=desired_state, + permanent=permanent, + immediate=immediate, + fw_offline=fw_offline + ) - action_handler(remove_source, (zone, source)) - changed=True - msgs.append("Removed %s from zone %s" % (source, zone)) + changed, transaction_msgs = transaction.run() + msgs = msgs + transaction_msgs if port is not None: - if immediate and permanent: - is_enabled_permanent = action_handler( - get_port_enabled_permanent, - (zone,[port, protocol]) - ) - is_enabled_immediate = action_handler( - get_port_enabled, - (zone, [port, protocol]) - ) - msgs.append('Permanent and Non-Permanent(immediate) operation') - if desired_state == "enabled": - if not is_enabled_permanent or not is_enabled_immediate: - if module.check_mode: - module.exit_json(changed=True) - if not is_enabled_permanent: - action_handler( - set_port_enabled_permanent, - (zone, port, protocol) - ) - changed=True - if not is_enabled_immediate: - action_handler( - set_port_enabled, - (zone, port, protocol, timeout) - ) - changed=True - - elif desired_state == "disabled": - if is_enabled_permanent or is_enabled_immediate: - if module.check_mode: - module.exit_json(changed=True) - if is_enabled_permanent: - action_handler( - set_port_disabled_permanent, - (zone, port, protocol) - ) - changed=True - if is_enabled_immediate: - action_handler( - set_port_disabled, - (zone, port, protocol) - ) - changed=True - - elif permanent and not immediate: - is_enabled = action_handler( - get_port_enabled_permanent, - (zone, [port, protocol]) - ) - msgs.append('Permanent operation') - - if desired_state == "enabled": - if is_enabled is False: - if module.check_mode: - module.exit_json(changed=True) - - action_handler( - set_port_enabled_permanent, - (zone, port, protocol) - ) - changed=True - elif desired_state == "disabled": - if is_enabled is True: - if module.check_mode: - module.exit_json(changed=True) - - action_handler( - set_port_disabled_permanent, - (zone, port, protocol) - ) - changed=True - if immediate and not permanent: - is_enabled = action_handler( - get_port_enabled, - (zone, [port,protocol]) - ) - msgs.append('Non-permanent operation') - - if desired_state == "enabled": - if is_enabled is False: - if module.check_mode: - module.exit_json(changed=True) - - action_handler( - set_port_enabled, - (zone, port, protocol, timeout) - ) - changed=True - elif desired_state == "disabled": - if is_enabled is True: - if module.check_mode: - module.exit_json(changed=True) - - action_handler( - set_port_disabled, - (zone, port, protocol) - ) - changed=True + transaction = PortTransaction( + fw, + action_args=(port, protocol, timeout), + zone=zone, + desired_state=desired_state, + permanent=permanent, + immediate=immediate, + fw_offline=fw_offline + ) + changed, transaction_msgs = transaction.run() + msgs = msgs + transaction_msgs if changed is True: - msgs.append("Changed port %s to %s" % ("%s/%s" % (port, protocol), \ - desired_state)) + msgs.append( + "Changed port %s to %s" % ( + "%s/%s" % (port, protocol), desired_state + ) + ) if rich_rule is not None: - if immediate and permanent: - is_enabled_permanent = action_handler( - get_rich_rule_enabled_permanent, - (zone, rich_rule) - ) - is_enabled_immediate = action_handler( - get_rich_rule_enabled, - (zone, rich_rule) - ) - msgs.append('Permanent and Non-Permanent(immediate) operation') - if desired_state == "enabled": - if not is_enabled_permanent or not is_enabled_immediate: - if module.check_mode: - module.exit_json(changed=True) - if not is_enabled_permanent: - action_handler( - set_rich_rule_enabled_permanent, - (zone, rich_rule) - ) - changed=True - if not is_enabled_immediate: - action_handler( - set_rich_rule_enabled, - (zone, rich_rule, timeout) - ) - changed=True - - elif desired_state == "disabled": - if is_enabled_permanent or is_enabled_immediate: - if module.check_mode: - module.exit_json(changed=True) - if is_enabled_permanent: - action_handler( - set_rich_rule_disabled_permanent, - (zone, rich_rule) - ) - changed=True - if is_enabled_immediate: - action_handler( - set_rich_rule_disabled, - (zone, rich_rule) - ) - changed=True - if permanent and not immediate: - is_enabled = action_handler( - get_rich_rule_enabled_permanent, - (zone, rich_rule) - ) - msgs.append('Permanent operation') - - if desired_state == "enabled": - if is_enabled is False: - if module.check_mode: - module.exit_json(changed=True) - - action_handler( - set_rich_rule_enabled_permanent, - (zone, rich_rule) - ) - changed=True - elif desired_state == "disabled": - if is_enabled is True: - if module.check_mode: - module.exit_json(changed=True) - - action_handler( - set_rich_rule_disabled_permanent, - (zone, rich_rule) - ) - changed=True - if immediate and not permanent: - is_enabled = action_handler( - get_rich_rule_enabled, - (zone, rich_rule) - ) - msgs.append('Non-permanent operation') - - if desired_state == "enabled": - if is_enabled is False: - if module.check_mode: - module.exit_json(changed=True) - - action_handler( - set_rich_rule_enabled, - (zone, rich_rule, timeout) - ) - changed=True - elif desired_state == "disabled": - if is_enabled is True: - if module.check_mode: - module.exit_json(changed=True) - - action_handler( - set_rich_rule_disabled, - (zone, rich_rule) - ) - changed=True + transaction = RichRuleTransaction( + fw, + action_args=(rich_rule, timeout), + zone=zone, + desired_state=desired_state, + permanent=permanent, + immediate=immediate, + fw_offline=fw_offline + ) + changed, transaction_msgs = transaction.run() + msgs = msgs + transaction_msgs if changed is True: msgs.append("Changed rich_rule %s to %s" % (rich_rule, desired_state)) if interface is not None: - if immediate and permanent: - is_enabled_permanent = action_handler( - get_interface_permanent, - (zone, interface) - ) - is_enabled_immediate = action_handler( - get_interface, - (zone, interface) - ) - msgs.append('Permanent and Non-Permanent(immediate) operation') - if desired_state == "enabled": - if not is_enabled_permanent or not is_enabled_immediate: - if module.check_mode: - module.exit_json(changed=True) - if not is_enabled_permanent: - change_zone_of_interface_permanent(zone, interface) - changed=True - if not is_enabled_immediate: - change_zone_of_interface(zone, interface) - changed=True - if changed: - msgs.append("Changed %s to zone %s" % (interface, zone)) + transaction = InterfaceTransaction( + fw, + action_args=(interface,), + zone=zone, + desired_state=desired_state, + permanent=permanent, + immediate=immediate, + fw_offline=fw_offline + ) - elif desired_state == "disabled": - if is_enabled_permanent or is_enabled_immediate: - if module.check_mode: - module.exit_json(changed=True) - if is_enabled_permanent: - remove_interface_permanent(zone, interface) - changed=True - if is_enabled_immediate: - remove_interface(zone, interface) - changed=True - if changed: - msgs.append("Removed %s from zone %s" % (interface, zone)) - - elif permanent and not immediate: - is_enabled = action_handler( - get_interface_permanent, - (zone, interface) - ) - msgs.append('Permanent operation') - if desired_state == "enabled": - if is_enabled is False: - if module.check_mode: - module.exit_json(changed=True) - - change_zone_of_interface_permanent(zone, interface) - changed=True - msgs.append("Changed %s to zone %s" % (interface, zone)) - elif desired_state == "disabled": - if is_enabled is True: - if module.check_mode: - module.exit_json(changed=True) - - remove_interface_permanent(zone, interface) - changed=True - msgs.append("Removed %s from zone %s" % (interface, zone)) - elif immediate and not permanent: - is_enabled = action_handler( - get_interface, - (zone, interface) - ) - msgs.append('Non-permanent operation') - if desired_state == "enabled": - if is_enabled is False: - if module.check_mode: - module.exit_json(changed=True) - - change_zone_of_interface(zone, interface) - changed=True - msgs.append("Changed %s to zone %s" % (interface, zone)) - elif desired_state == "disabled": - if is_enabled is True: - if module.check_mode: - module.exit_json(changed=True) - - remove_interface(zone, interface) - changed=True - msgs.append("Removed %s from zone %s" % (interface, zone)) + changed, transaction_msgs = transaction.run() + msgs = msgs + transaction_msgs if masquerade is not None: - if immediate and permanent: - is_enabled_permanent = action_handler( - get_masquerade_enabled_permanent, - (zone,) - ) - is_enabled_immediate = action_handler(get_masquerade_enabled, (zone,)) - msgs.append('Permanent and Non-Permanent(immediate) operation') + transaction = MasqueradeTransaction( + fw, + action_args=(), + zone=zone, + desired_state=desired_state, + permanent=permanent, + immediate=immediate, + fw_offline=fw_offline + ) - if desired_state == "enabled": - if not is_enabled_permanent or not is_enabled_immediate: - if module.check_mode: - module.exit_json(changed=True) - if not is_enabled_permanent: - action_handler(set_masquerade_permanent, (zone, True)) - changed=True - if not is_enabled_immediate: - action_handler(set_masquerade_enabled, (zone,)) - changed=True - if changed: - msgs.append("Added masquerade to zone %s" % (zone)) - - elif desired_state == "disabled": - if is_enabled_permanent or is_enabled_immediate: - if module.check_mode: - module.exit_json(changed=True) - if is_enabled_permanent: - action_handler(set_masquerade_permanent, (zone, False)) - changed=True - if is_enabled_immediate: - action_handler(set_masquerade_disabled, (zone,)) - changed=True - if changed: - msgs.append("Removed masquerade from zone %s" % (zone)) - - elif permanent and not immediate: - is_enabled = action_handler(get_masquerade_enabled_permanent, (zone,)) - msgs.append('Permanent operation') - - if desired_state == "enabled": - if is_enabled is False: - if module.check_mode: - module.exit_json(changed=True) - - action_handler(set_masquerade_permanent, (zone, True)) - changed=True - msgs.append("Added masquerade to zone %s" % (zone)) - elif desired_state == "disabled": - if is_enabled is True: - if module.check_mode: - module.exit_json(changed=True) - - action_handler(set_masquerade_permanent, (zone, False)) - changed=True - msgs.append("Removed masquerade from zone %s" % (zone)) - elif immediate and not permanent: - is_enabled = action_handler(get_masquerade_enabled, (zone,)) - msgs.append('Non-permanent operation') - - if desired_state == "enabled": - if is_enabled is False: - if module.check_mode: - module.exit_json(changed=True) - - action_handler(set_masquerade_enabled, (zone)) - changed=True - msgs.append("Added masquerade to zone %s" % (zone)) - elif desired_state == "disabled": - if is_enabled is True: - if module.check_mode: - module.exit_json(changed=True) - - action_handler(set_masquerade_disabled, (zone)) - changed=True - msgs.append("Removed masquerade from zone %s" % (zone)) + changed, transaction_msgs = transaction.run() + msgs = msgs + transaction_msgs if fw_offline: msgs.append("(offline operation: only on-disk configs were altered)") + module.exit_json(changed=changed, msg=', '.join(msgs)) diff --git a/test/sanity/pep8/legacy-files.txt b/test/sanity/pep8/legacy-files.txt index e905dff232..dac7371768 100644 --- a/test/sanity/pep8/legacy-files.txt +++ b/test/sanity/pep8/legacy-files.txt @@ -425,7 +425,6 @@ lib/ansible/modules/system/crypttab.py lib/ansible/modules/system/debconf.py lib/ansible/modules/system/facter.py lib/ansible/modules/system/filesystem.py -lib/ansible/modules/system/firewalld.py lib/ansible/modules/system/gconftool2.py lib/ansible/modules/system/getent.py lib/ansible/modules/system/gluster_volume.py