firewalld: Implement zone operations (#32845)

* firewalld: Implement zone operations

Zones are removed or added when no other operations are used in
conjunction with the keywords 'present' or 'absent'.

This leads to a logical and natural syntax of:

- firewalld:
    zone: foo
    state: present

for adding or removing zones.

Signed-off-by: Felix Kaechele <felix@kaechele.ca>

* firewalld: zone ops: addressed review concerns

- Added more documentation on the peculiarities of the zone operations
- Output meaningful error messages when trying to use zones incorrectly

Signed-off-by: Felix Kaechele <felix@kaechele.ca>
This commit is contained in:
Felix Kaechele 2017-12-12 00:34:41 -08:00 committed by Martin Krizek
parent 9ff5c15f57
commit 8475171f67

View file

@ -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)")