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>
pull/33806/head
Felix Kaechele 7 years ago committed by Martin Krizek
parent 9ff5c15f57
commit 8475171f67

@ -71,9 +71,12 @@ options:
version_added: "1.9" version_added: "1.9"
state: state:
description: 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 required: true
choices: [ "enabled", "disabled" ] choices: [ "enabled", "disabled", "present", "absent" ]
timeout: timeout:
description: description:
- "The amount of time the rule should be in effect for when non-permanent." - "The amount of time the rule should be in effect for when non-permanent."
@ -88,6 +91,12 @@ options:
notes: notes:
- Not tested on any Debian based system. - 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 - 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' ] requirements: [ 'firewalld >= 0.2.11' ]
author: "Adam Miller (@maxamillion)" author: "Adam Miller (@maxamillion)"
''' '''
@ -135,6 +144,11 @@ EXAMPLES = '''
state: enabled state: enabled
permanent: true permanent: true
zone: dmz zone: dmz
- firewalld:
zone: custom
state: present
permanent: true
''' '''
from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import AnsibleModule
@ -149,6 +163,7 @@ try:
from firewall.client import Rich_Rule from firewall.client import Rich_Rule
from firewall.client import FirewallClient from firewall.client import FirewallClient
from firewall.client import FirewallClientZoneSettings
fw = None fw = None
fw_offline = False fw_offline = False
import_failure = False import_failure = False
@ -165,7 +180,6 @@ try:
# NOTE: # NOTE:
# online and offline operations do not share a common firewalld API # online and offline operations do not share a common firewalld API
from firewall.core.fw_test import Firewall_test from firewall.core.fw_test import Firewall_test
from firewall.client import FirewallClientZoneSettings
fw = Firewall_test() fw = Firewall_test()
fw.start() fw.start()
@ -183,7 +197,8 @@ class FirewallTransaction(object):
global module global module
def __init__(self, fw, action_args=(), zone=None, desired_state=None, 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) # type: (firewall.client, tuple, str, bool, bool, bool)
""" """
initializer the transaction initializer the transaction
@ -195,6 +210,8 @@ class FirewallTransaction(object):
:permanent: bool, action should be permanent :permanent: bool, action should be permanent
:immediate: bool, action should take place immediately :immediate: bool, action should take place immediately
:fw_offline: bool, action takes place as if the firewall were offline :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 self.fw = fw
@ -204,6 +221,8 @@ class FirewallTransaction(object):
self.permanent = permanent self.permanent = permanent
self.immediate = immediate self.immediate = immediate
self.fw_offline = fw_offline 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 # List of messages that we'll call module.fail_json or module.exit_json
# with. # with.
@ -214,12 +233,6 @@ class FirewallTransaction(object):
self.enabled_msg = None self.enabled_msg = None
self.disabled_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 # exception handling
# #
@ -728,6 +741,52 @@ class SourceTransaction(FirewallTransaction):
self.update_fw_settings(fw_zone, fw_settings) 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(): def main():
global module global module
@ -740,7 +799,7 @@ def main():
immediate=dict(type='bool', default=False), immediate=dict(type='bool', default=False),
source=dict(required=False, default=None), source=dict(required=False, default=None),
permanent=dict(type='bool', 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), timeout=dict(type='int', required=False, default=0),
interface=dict(required=False, default=None), interface=dict(required=False, default=None),
masquerade=dict(required=False, default=None), masquerade=dict(required=False, default=None),
@ -825,6 +884,10 @@ def main():
module.fail_json( module.fail_json(
msg='can only operate on port, service, rich_rule, or interface at once' 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: if service is not None:
@ -926,6 +989,24 @@ def main():
changed, transaction_msgs = transaction.run() changed, transaction_msgs = transaction.run()
msgs = msgs + transaction_msgs 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: if fw_offline:
msgs.append("(offline operation: only on-disk configs were altered)") msgs.append("(offline operation: only on-disk configs were altered)")

Loading…
Cancel
Save