diff --git a/lib/ansible/modules/system/firewalld.py b/lib/ansible/modules/system/firewalld.py index 70b3bd7598a..33e3d21a3aa 100644 --- a/lib/ansible/modules/system/firewalld.py +++ b/lib/ansible/modules/system/firewalld.py @@ -71,9 +71,12 @@ options: version_added: "1.9" state: description: - - "Should this port accept(enabled) or reject(disabled) connections." + - > + Enable or disable a setting. + For ports: Should this port accept(enabled) or reject(disabled) connections. + The states "present" and "absent" can only be used in zone level operations (i.e. when no other parameters but zone and state are set). required: true - choices: [ "enabled", "disabled" ] + choices: [ "enabled", "disabled", "present", "absent" ] timeout: description: - "The amount of time the rule should be in effect for when non-permanent." @@ -88,6 +91,12 @@ options: notes: - Not tested on any Debian based system. - Requires the python2 bindings of firewalld, which may not be installed by default if the distribution switched to python 3 + - Zone transactions (creating, deleting) can be performed by using only the zone and state parameters "present" or "absent". + Note that zone transactions must explicitly be permanent. This is a limitation in firewalld. + This also means that you will have to reload firewalld after adding a zone that you wish to perfom immediate actions on. + The module will not take care of this for you implicitly because that would undo any previously performed immediate actions which were not + permanent. Therefor, if you require immediate access to a newly created zone it is recommended you reload firewalld immediately after the zone + creation returns with a changed state and before you perform any other immediate, non-permanent actions on that zone. requirements: [ 'firewalld >= 0.2.11' ] author: "Adam Miller (@maxamillion)" ''' @@ -135,6 +144,11 @@ EXAMPLES = ''' state: enabled permanent: true zone: dmz + +- firewalld: + zone: custom + state: present + permanent: true ''' from ansible.module_utils.basic import AnsibleModule @@ -149,6 +163,7 @@ try: from firewall.client import Rich_Rule from firewall.client import FirewallClient + from firewall.client import FirewallClientZoneSettings fw = None fw_offline = False import_failure = False @@ -165,7 +180,6 @@ try: # 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() @@ -183,18 +197,21 @@ class FirewallTransaction(object): global module def __init__(self, fw, action_args=(), zone=None, desired_state=None, - permanent=False, immediate=False, fw_offline=False): + permanent=False, immediate=False, fw_offline=False, + enabled_values=None, disabled_values=None): # 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 + :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 + :enabled_values: str[], acceptable values for enabling something (default: enabled) + :disabled_values: str[], acceptable values for disabling something (default: disabled) """ self.fw = fw @@ -204,6 +221,8 @@ class FirewallTransaction(object): self.permanent = permanent self.immediate = immediate self.fw_offline = fw_offline + self.enabled_values = enabled_values or ["enabled"] + self.disabled_values = disabled_values or ["disabled"] # List of messages that we'll call module.fail_json or module.exit_json # with. @@ -214,12 +233,6 @@ class FirewallTransaction(object): 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 # @@ -728,6 +741,52 @@ class SourceTransaction(FirewallTransaction): self.update_fw_settings(fw_zone, fw_settings) +class ZoneTransaction(FirewallTransaction): + """ + ZoneTransaction + """ + + def __init__(self, fw, action_args=None, zone=None, desired_state=None, + permanent=True, immediate=False, fw_offline=False, + enabled_values=None, disabled_values=None): + super(ZoneTransaction, self).__init__( + fw, action_args=action_args, desired_state=desired_state, zone=zone, + permanent=permanent, immediate=immediate, fw_offline=fw_offline, + enabled_values=enabled_values or ["present"], + disabled_values=disabled_values or ["absent"]) + + self.enabled_msg = "Added zone %s" % \ + (self.zone) + + self.disabled_msg = "Removed zone %s" % \ + (self.zone) + + self.tx_not_permanent_error_msg = "Zone operations must be permanent. " \ + "Make sure you didn't set the 'permanent' flag to 'false' or the 'immediate' flag to 'true'." + + def get_enabled_immediate(self): + module.fail_json(msg=self.tx_not_permanent_error_msg) + + def get_enabled_permanent(self): + if self.zone in fw.config().getZoneNames(): + return True + else: + return False + + def set_enabled_immediate(self): + module.fail_json(msg=self.tx_not_permanent_error_msg) + + def set_enabled_permanent(self): + fw.config().addZone(self.zone, FirewallClientZoneSettings()) + + def set_disabled_immediate(self): + module.fail_json(msg=self.tx_not_permanent_error_msg) + + def set_disabled_permanent(self): + zone_obj = self.fw.config().getZoneByName(self.zone) + zone_obj.remove() + + def main(): global module @@ -740,7 +799,7 @@ def main(): 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), + state=dict(choices=['enabled', 'disabled', 'present', 'absent'], required=True), timeout=dict(type='int', required=False, default=0), interface=dict(required=False, default=None), masquerade=dict(required=False, default=None), @@ -825,6 +884,10 @@ def main(): module.fail_json( msg='can only operate on port, service, rich_rule, or interface at once' ) + elif modification_count > 0 and desired_state in ['absent', 'present']: + module.fail_json( + msg='absent and present state can only be used in zone level operations' + ) if service is not None: @@ -926,6 +989,24 @@ def main(): changed, transaction_msgs = transaction.run() msgs = msgs + transaction_msgs + ''' If there are no changes within the zone we are operating on the zone itself ''' + if modification_count == 0 and desired_state in ['absent', 'present']: + + transaction = ZoneTransaction( + fw, + action_args=(), + 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 zone %s to %s" % (zone, desired_state)) + if fw_offline: msgs.append("(offline operation: only on-disk configs were altered)")