1#!/usr/local/bin/python3.8
2# -*- coding: utf-8 -*-
3
4# Copyright: (c) 2013, Adam Miller <maxamillion@fedoraproject.org>
5# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
6
7from __future__ import absolute_import, division, print_function
8__metaclass__ = type
9
10DOCUMENTATION = r'''
11---
12module: firewalld
13short_description: Manage arbitrary ports/services with firewalld
14description:
15  - This module allows for addition or deletion of services and ports (either TCP or UDP) in either running or permanent firewalld rules.
16options:
17  service:
18    description:
19      - Name of a service to add/remove to/from firewalld.
20      - The service must be listed in output of firewall-cmd --get-services.
21    type: str
22  port:
23    description:
24      - Name of a port or port range to add/remove to/from firewalld.
25      - Must be in the form PORT/PROTOCOL or PORT-PORT/PROTOCOL for port ranges.
26    type: str
27  port_forward:
28    description:
29      - Port and protocol to forward using firewalld.
30    type: list
31    elements: dict
32    suboptions:
33      port:
34        type: str
35        required: true
36        description:
37          - Source port to forward from
38      proto:
39        type: str
40        required: true
41        description:
42          - protocol to forward
43        choices: [udp, tcp]
44      toport:
45        type: str
46        required: true
47        description:
48          - destination port
49      toaddr:
50        type: str
51        description:
52          - Optional address to forward to
53  rich_rule:
54    description:
55      - Rich rule to add/remove to/from firewalld.
56      - See L(Syntax for firewalld rich language rules,https://firewalld.org/documentation/man-pages/firewalld.richlanguage.html).
57    type: str
58  source:
59    description:
60      - The source/network you would like to add/remove to/from firewalld.
61    type: str
62  interface:
63    description:
64      - The interface you would like to add/remove to/from a zone in firewalld.
65    type: str
66  icmp_block:
67    description:
68      - The ICMP block you would like to add/remove to/from a zone in firewalld.
69    type: str
70  icmp_block_inversion:
71    description:
72      - Enable/Disable inversion of ICMP blocks for a zone in firewalld.
73    type: str
74  zone:
75    description:
76      - The firewalld zone to add/remove to/from.
77      - Note that the default zone can be configured per system but C(public) is default from upstream.
78      - Available choices can be extended based on per-system configs, listed here are "out of the box" defaults.
79      - Possible values include C(block), C(dmz), C(drop), C(external), C(home), C(internal), C(public), C(trusted), C(work).
80    type: str
81  permanent:
82    description:
83      - Should this configuration be in the running firewalld configuration or persist across reboots.
84      - As of Ansible 2.3, permanent operations can operate on firewalld configs when it is not running (requires firewalld >= 0.3.9).
85      - Note that if this is C(no), immediate is assumed C(yes).
86    type: bool
87  immediate:
88    description:
89      - Should this configuration be applied immediately, if set as permanent.
90    type: bool
91    default: no
92  state:
93    description:
94      - Enable or disable a setting.
95      - 'For ports: Should this port accept (enabled) or reject (disabled) connections.'
96      - The states C(present) and C(absent) can only be used in zone level operations (i.e. when no other parameters but zone and state are set).
97    type: str
98    required: true
99    choices: [ absent, disabled, enabled, present ]
100  timeout:
101    description:
102      - The amount of time in seconds the rule should be in effect for when non-permanent.
103    type: int
104    default: 0
105  masquerade:
106    description:
107      - The masquerade setting you would like to enable/disable to/from zones within firewalld.
108    type: str
109  offline:
110    description:
111      - Whether to run this module even when firewalld is offline.
112    type: bool
113  target:
114    description:
115      - firewalld Zone target
116      - If state is set to C(absent), this will reset the target to default
117    choices: [ default, ACCEPT, DROP, "%%REJECT%%" ]
118    type: str
119    version_added: 1.2.0
120notes:
121  - Not tested on any Debian based system.
122  - Requires the python2 bindings of firewalld, which may not be installed by default.
123  - For distributions where the python2 firewalld bindings are unavailable (e.g Fedora 28 and later) you will have to set the
124    ansible_python_interpreter for these hosts to the python3 interpreter path and install the python3 bindings.
125  - Zone transactions (creating, deleting) can be performed by using only the zone and state parameters "present" or "absent".
126    Note that zone transactions must explicitly be permanent. This is a limitation in firewalld.
127    This also means that you will have to reload firewalld after adding a zone that you wish to perform immediate actions on.
128    The module will not take care of this for you implicitly because that would undo any previously performed immediate actions which were not
129    permanent. Therefore, if you require immediate access to a newly created zone it is recommended you reload firewalld immediately after the zone
130    creation returns with a changed state and before you perform any other immediate, non-permanent actions on that zone.
131requirements:
132- firewalld >= 0.2.11
133author:
134- Adam Miller (@maxamillion)
135'''
136
137EXAMPLES = r'''
138- name: permit traffic in default zone for https service
139  ansible.posix.firewalld:
140    service: https
141    permanent: yes
142    state: enabled
143
144- name: do not permit traffic in default zone on port 8081/tcp
145  ansible.posix.firewalld:
146    port: 8081/tcp
147    permanent: yes
148    state: disabled
149
150- ansible.posix.firewalld:
151    port: 161-162/udp
152    permanent: yes
153    state: enabled
154
155- ansible.posix.firewalld:
156    zone: dmz
157    service: http
158    permanent: yes
159    state: enabled
160
161- ansible.posix.firewalld:
162    rich_rule: rule service name="ftp" audit limit value="1/m" accept
163    permanent: yes
164    state: enabled
165
166- ansible.posix.firewalld:
167    source: 192.0.2.0/24
168    zone: internal
169    state: enabled
170
171- ansible.posix.firewalld:
172    zone: trusted
173    interface: eth2
174    permanent: yes
175    state: enabled
176
177- ansible.posix.firewalld:
178    masquerade: yes
179    state: enabled
180    permanent: yes
181    zone: dmz
182
183- ansible.posix.firewalld:
184    zone: custom
185    state: present
186    permanent: yes
187
188- ansible.posix.firewalld:
189    zone: drop
190    state: enabled
191    permanent: yes
192    icmp_block_inversion: yes
193
194- ansible.posix.firewalld:
195    zone: drop
196    state: enabled
197    permanent: yes
198    icmp_block: echo-request
199
200- ansible.posix.firewalld:
201    zone: internal
202    state: present
203    permanent: yes
204    target: ACCEPT
205
206- name: Redirect port 443 to 8443 with Rich Rule
207  ansible.posix.firewalld:
208    rich_rule: rule family=ipv4 forward-port port=443 protocol=tcp to-port=8443
209    zone: public
210    permanent: yes
211    immediate: yes
212    state: enabled
213'''
214
215from ansible.module_utils.basic import AnsibleModule
216from ansible_collections.ansible.posix.plugins.module_utils.firewalld import FirewallTransaction, fw_offline
217
218try:
219    from firewall.client import Rich_Rule
220    from firewall.client import FirewallClientZoneSettings
221except ImportError:
222    # The import errors are handled via FirewallTransaction, don't need to
223    # duplicate that here
224    pass
225
226
227class IcmpBlockTransaction(FirewallTransaction):
228    """
229    IcmpBlockTransaction
230    """
231
232    def __init__(self, module, action_args=None, zone=None, desired_state=None, permanent=False, immediate=False):
233        super(IcmpBlockTransaction, self).__init__(
234            module, action_args=action_args, desired_state=desired_state, zone=zone, permanent=permanent, immediate=immediate
235        )
236
237    def get_enabled_immediate(self, icmp_block, timeout):
238        return icmp_block in self.fw.getIcmpBlocks(self.zone)
239
240    def get_enabled_permanent(self, icmp_block, timeout):
241        fw_zone, fw_settings = self.get_fw_zone_settings()
242        return icmp_block in fw_settings.getIcmpBlocks()
243
244    def set_enabled_immediate(self, icmp_block, timeout):
245        self.fw.addIcmpBlock(self.zone, icmp_block, timeout)
246
247    def set_enabled_permanent(self, icmp_block, timeout):
248        fw_zone, fw_settings = self.get_fw_zone_settings()
249        fw_settings.addIcmpBlock(icmp_block)
250        self.update_fw_settings(fw_zone, fw_settings)
251
252    def set_disabled_immediate(self, icmp_block, timeout):
253        self.fw.removeIcmpBlock(self.zone, icmp_block)
254
255    def set_disabled_permanent(self, icmp_block, timeout):
256        fw_zone, fw_settings = self.get_fw_zone_settings()
257        fw_settings.removeIcmpBlock(icmp_block)
258        self.update_fw_settings(fw_zone, fw_settings)
259
260
261class IcmpBlockInversionTransaction(FirewallTransaction):
262    """
263    IcmpBlockInversionTransaction
264    """
265
266    def __init__(self, module, action_args=None, zone=None, desired_state=None, permanent=False, immediate=False):
267        super(IcmpBlockInversionTransaction, self).__init__(
268            module, action_args=action_args, desired_state=desired_state, zone=zone, permanent=permanent, immediate=immediate
269        )
270
271    def get_enabled_immediate(self):
272        if self.fw.queryIcmpBlockInversion(self.zone) is True:
273            return True
274        else:
275            return False
276
277    def get_enabled_permanent(self):
278        fw_zone, fw_settings = self.get_fw_zone_settings()
279        if fw_settings.getIcmpBlockInversion() is True:
280            return True
281        else:
282            return False
283
284    def set_enabled_immediate(self):
285        self.fw.addIcmpBlockInversion(self.zone)
286
287    def set_enabled_permanent(self):
288        fw_zone, fw_settings = self.get_fw_zone_settings()
289        fw_settings.setIcmpBlockInversion(True)
290        self.update_fw_settings(fw_zone, fw_settings)
291
292    def set_disabled_immediate(self):
293        self.fw.removeIcmpBlockInversion(self.zone)
294
295    def set_disabled_permanent(self):
296        fw_zone, fw_settings = self.get_fw_zone_settings()
297        fw_settings.setIcmpBlockInversion(False)
298        self.update_fw_settings(fw_zone, fw_settings)
299
300
301class ServiceTransaction(FirewallTransaction):
302    """
303    ServiceTransaction
304    """
305
306    def __init__(self, module, action_args=None, zone=None, desired_state=None, permanent=False, immediate=False):
307        super(ServiceTransaction, self).__init__(
308            module, action_args=action_args, desired_state=desired_state, zone=zone, permanent=permanent, immediate=immediate
309        )
310
311    def get_enabled_immediate(self, service, timeout):
312        if service in self.fw.getServices(self.zone):
313            return True
314        else:
315            return False
316
317    def get_enabled_permanent(self, service, timeout):
318        fw_zone, fw_settings = self.get_fw_zone_settings()
319
320        if service in fw_settings.getServices():
321            return True
322        else:
323            return False
324
325    def set_enabled_immediate(self, service, timeout):
326        self.fw.addService(self.zone, service, timeout)
327
328    def set_enabled_permanent(self, service, timeout):
329        fw_zone, fw_settings = self.get_fw_zone_settings()
330        fw_settings.addService(service)
331        self.update_fw_settings(fw_zone, fw_settings)
332
333    def set_disabled_immediate(self, service, timeout):
334        self.fw.removeService(self.zone, service)
335
336    def set_disabled_permanent(self, service, timeout):
337        fw_zone, fw_settings = self.get_fw_zone_settings()
338        fw_settings.removeService(service)
339        self.update_fw_settings(fw_zone, fw_settings)
340
341
342class MasqueradeTransaction(FirewallTransaction):
343    """
344    MasqueradeTransaction
345    """
346
347    def __init__(self, module, action_args=None, zone=None, desired_state=None, permanent=False, immediate=False):
348        super(MasqueradeTransaction, self).__init__(
349            module, action_args=action_args, desired_state=desired_state, zone=zone, permanent=permanent, immediate=immediate
350        )
351
352        self.enabled_msg = "Added masquerade to zone %s" % self.zone
353        self.disabled_msg = "Removed masquerade from zone %s" % self.zone
354
355    def get_enabled_immediate(self):
356        if self.fw.queryMasquerade(self.zone) is True:
357            return True
358        else:
359            return False
360
361    def get_enabled_permanent(self):
362        fw_zone, fw_settings = self.get_fw_zone_settings()
363        if fw_settings.getMasquerade() is True:
364            return True
365        else:
366            return False
367
368    def set_enabled_immediate(self):
369        self.fw.addMasquerade(self.zone)
370
371    def set_enabled_permanent(self):
372        fw_zone, fw_settings = self.get_fw_zone_settings()
373        fw_settings.setMasquerade(True)
374        self.update_fw_settings(fw_zone, fw_settings)
375
376    def set_disabled_immediate(self):
377        self.fw.removeMasquerade(self.zone)
378
379    def set_disabled_permanent(self):
380        fw_zone, fw_settings = self.get_fw_zone_settings()
381        fw_settings.setMasquerade(False)
382        self.update_fw_settings(fw_zone, fw_settings)
383
384
385class PortTransaction(FirewallTransaction):
386    """
387    PortTransaction
388    """
389
390    def __init__(self, module, action_args=None, zone=None, desired_state=None, permanent=False, immediate=False):
391        super(PortTransaction, self).__init__(
392            module, action_args=action_args, desired_state=desired_state, zone=zone, permanent=permanent, immediate=immediate
393        )
394
395    def get_enabled_immediate(self, port, protocol, timeout):
396        if self.fw_offline:
397            dummy, fw_settings = self.get_fw_zone_settings()
398            return fw_settings.queryPort(port=port, protocol=protocol)
399        return self.fw.queryPort(zone=self.zone, port=port, protocol=protocol)
400
401    def get_enabled_permanent(self, port, protocol, timeout):
402        dummy, fw_settings = self.get_fw_zone_settings()
403        return fw_settings.queryPort(port=port, protocol=protocol)
404
405    def set_enabled_immediate(self, port, protocol, timeout):
406        self.fw.addPort(self.zone, port, protocol, timeout)
407
408    def set_enabled_permanent(self, port, protocol, timeout):
409        fw_zone, fw_settings = self.get_fw_zone_settings()
410        fw_settings.addPort(port, protocol)
411        self.update_fw_settings(fw_zone, fw_settings)
412
413    def set_disabled_immediate(self, port, protocol, timeout):
414        self.fw.removePort(self.zone, port, protocol)
415
416    def set_disabled_permanent(self, port, protocol, timeout):
417        fw_zone, fw_settings = self.get_fw_zone_settings()
418        fw_settings.removePort(port, protocol)
419        self.update_fw_settings(fw_zone, fw_settings)
420
421
422class InterfaceTransaction(FirewallTransaction):
423    """
424    InterfaceTransaction
425    """
426
427    def __init__(self, module, action_args=None, zone=None, desired_state=None, permanent=False, immediate=False):
428        super(InterfaceTransaction, self).__init__(
429            module, action_args=action_args, desired_state=desired_state, zone=zone, permanent=permanent, immediate=immediate
430        )
431
432        self.enabled_msg = "Changed %s to zone %s" % \
433            (self.action_args[0], self.zone)
434
435        self.disabled_msg = "Removed %s from zone %s" % \
436            (self.action_args[0], self.zone)
437
438    def get_enabled_immediate(self, interface):
439        if self.fw_offline:
440            fw_zone, fw_settings = self.get_fw_zone_settings()
441            interface_list = fw_settings.getInterfaces()
442        else:
443            interface_list = self.fw.getInterfaces(self.zone)
444        if interface in interface_list:
445            return True
446        else:
447            return False
448
449    def get_enabled_permanent(self, interface):
450        fw_zone, fw_settings = self.get_fw_zone_settings()
451
452        if interface in fw_settings.getInterfaces():
453            return True
454        else:
455            return False
456
457    def set_enabled_immediate(self, interface):
458        self.fw.changeZoneOfInterface(self.zone, interface)
459
460    def set_enabled_permanent(self, interface):
461        fw_zone, fw_settings = self.get_fw_zone_settings()
462        if self.fw_offline:
463            iface_zone_objs = []
464            for zone in self.fw.config.get_zones():
465                old_zone_obj = self.fw.config.get_zone(zone)
466                if interface in old_zone_obj.interfaces:
467                    iface_zone_objs.append(old_zone_obj)
468            if len(iface_zone_objs) > 1:
469                # Even it shouldn't happen, it's actually possible that
470                # the same interface is in several zone XML files
471                self.module.fail_json(
472                    msg='ERROR: interface {0} is in {1} zone XML file, can only be in one'.format(
473                        interface,
474                        len(iface_zone_objs)
475                    )
476                )
477            old_zone_obj = iface_zone_objs[0]
478            if old_zone_obj.name != self.zone:
479                old_zone_settings = FirewallClientZoneSettings(
480                    self.fw.config.get_zone_config(old_zone_obj)
481                )
482                old_zone_settings.removeInterface(interface)    # remove from old
483                self.fw.config.set_zone_config(
484                    old_zone_obj,
485                    old_zone_settings.settings
486                )
487                fw_settings.addInterface(interface)             # add to new
488                self.fw.config.set_zone_config(fw_zone, fw_settings.settings)
489        else:
490            old_zone_name = self.fw.config().getZoneOfInterface(interface)
491            if old_zone_name != self.zone:
492                if old_zone_name:
493                    old_zone_obj = self.fw.config().getZoneByName(old_zone_name)
494                    old_zone_settings = old_zone_obj.getSettings()
495                    old_zone_settings.removeInterface(interface)  # remove from old
496                    old_zone_obj.update(old_zone_settings)
497                fw_settings.addInterface(interface)              # add to new
498                fw_zone.update(fw_settings)
499
500    def set_disabled_immediate(self, interface):
501        self.fw.removeInterface(self.zone, interface)
502
503    def set_disabled_permanent(self, interface):
504        fw_zone, fw_settings = self.get_fw_zone_settings()
505        fw_settings.removeInterface(interface)
506        self.update_fw_settings(fw_zone, fw_settings)
507
508
509class RichRuleTransaction(FirewallTransaction):
510    """
511    RichRuleTransaction
512    """
513
514    def __init__(self, module, action_args=None, zone=None, desired_state=None, permanent=False, immediate=False):
515        super(RichRuleTransaction, self).__init__(
516            module, action_args=action_args, desired_state=desired_state, zone=zone, permanent=permanent, immediate=immediate
517        )
518
519    def get_enabled_immediate(self, rule, timeout):
520        # Convert the rule string to standard format
521        # before checking whether it is present
522        rule = str(Rich_Rule(rule_str=rule))
523        if rule in self.fw.getRichRules(self.zone):
524            return True
525        else:
526            return False
527
528    def get_enabled_permanent(self, rule, timeout):
529        fw_zone, fw_settings = self.get_fw_zone_settings()
530        # Convert the rule string to standard format
531        # before checking whether it is present
532        rule = str(Rich_Rule(rule_str=rule))
533        if rule in fw_settings.getRichRules():
534            return True
535        else:
536            return False
537
538    def set_enabled_immediate(self, rule, timeout):
539        self.fw.addRichRule(self.zone, rule, timeout)
540
541    def set_enabled_permanent(self, rule, timeout):
542        fw_zone, fw_settings = self.get_fw_zone_settings()
543        fw_settings.addRichRule(rule)
544        self.update_fw_settings(fw_zone, fw_settings)
545
546    def set_disabled_immediate(self, rule, timeout):
547        self.fw.removeRichRule(self.zone, rule)
548
549    def set_disabled_permanent(self, rule, timeout):
550        fw_zone, fw_settings = self.get_fw_zone_settings()
551        fw_settings.removeRichRule(rule)
552        self.update_fw_settings(fw_zone, fw_settings)
553
554
555class SourceTransaction(FirewallTransaction):
556    """
557    SourceTransaction
558    """
559
560    def __init__(self, module, action_args=None, zone=None, desired_state=None, permanent=False, immediate=False):
561        super(SourceTransaction, self).__init__(
562            module, action_args=action_args, desired_state=desired_state, zone=zone, permanent=permanent, immediate=immediate
563        )
564
565        self.enabled_msg = "Added %s to zone %s" % \
566            (self.action_args[0], self.zone)
567
568        self.disabled_msg = "Removed %s from zone %s" % \
569            (self.action_args[0], self.zone)
570
571    def get_enabled_immediate(self, source):
572        if source in self.fw.getSources(self.zone):
573            return True
574        else:
575            return False
576
577    def get_enabled_permanent(self, source):
578        fw_zone, fw_settings = self.get_fw_zone_settings()
579        if source in fw_settings.getSources():
580            return True
581        else:
582            return False
583
584    def set_enabled_immediate(self, source):
585        self.fw.addSource(self.zone, source)
586
587    def set_enabled_permanent(self, source):
588        fw_zone, fw_settings = self.get_fw_zone_settings()
589        fw_settings.addSource(source)
590        self.update_fw_settings(fw_zone, fw_settings)
591
592    def set_disabled_immediate(self, source):
593        self.fw.removeSource(self.zone, source)
594
595    def set_disabled_permanent(self, source):
596        fw_zone, fw_settings = self.get_fw_zone_settings()
597        fw_settings.removeSource(source)
598        self.update_fw_settings(fw_zone, fw_settings)
599
600
601class ZoneTargetTransaction(FirewallTransaction):
602    """
603    ZoneTargetTransaction
604    """
605
606    def __init__(self, module, action_args=None, zone=None, desired_state=None,
607                 permanent=True, immediate=False, enabled_values=None, disabled_values=None):
608        super(ZoneTargetTransaction, self).__init__(
609            module, action_args=action_args, desired_state=desired_state, zone=zone,
610            permanent=permanent, immediate=immediate,
611            enabled_values=enabled_values or ["present", "enabled"],
612            disabled_values=disabled_values or ["absent", "disabled"])
613
614        self.enabled_msg = "Set zone %s target to %s" % \
615            (self.zone, action_args[0])
616
617        self.disabled_msg = "Reset zone %s target to default" % \
618            (self.zone)
619
620        self.tx_not_permanent_error_msg = "Zone operations must be permanent. " \
621            "Make sure you didn't set the 'permanent' flag to 'false' or the 'immediate' flag to 'true'."
622
623    def get_enabled_immediate(self, target):
624        self.module.fail_json(msg=self.tx_not_permanent_error_msg)
625
626    def get_enabled_permanent(self, target):
627        fw_zone, fw_settings = self.get_fw_zone_settings()
628        current_target = fw_settings.getTarget()
629        return (current_target == target)
630
631    def set_enabled_immediate(self, target):
632        self.module.fail_json(msg=self.tx_not_permanent_error_msg)
633
634    def set_enabled_permanent(self, target):
635        fw_zone, fw_settings = self.get_fw_zone_settings()
636        fw_settings.setTarget(target)
637        self.update_fw_settings(fw_zone, fw_settings)
638
639    def set_disabled_immediate(self, target):
640        self.module.fail_json(msg=self.tx_not_permanent_error_msg)
641
642    def set_disabled_permanent(self, target):
643        fw_zone, fw_settings = self.get_fw_zone_settings()
644        fw_settings.setTarget("default")
645        self.update_fw_settings(fw_zone, fw_settings)
646
647
648class ZoneTransaction(FirewallTransaction):
649    """
650    ZoneTransaction
651    """
652
653    def __init__(self, module, action_args=None, zone=None, desired_state=None,
654                 permanent=True, immediate=False, enabled_values=None, disabled_values=None):
655        super(ZoneTransaction, self).__init__(
656            module, action_args=action_args, desired_state=desired_state, zone=zone,
657            permanent=permanent, immediate=immediate,
658            enabled_values=enabled_values or ["present"],
659            disabled_values=disabled_values or ["absent"])
660
661        self.enabled_msg = "Added zone %s" % \
662            (self.zone)
663
664        self.disabled_msg = "Removed zone %s" % \
665            (self.zone)
666
667        self.tx_not_permanent_error_msg = "Zone operations must be permanent. " \
668            "Make sure you didn't set the 'permanent' flag to 'false' or the 'immediate' flag to 'true'."
669
670    def get_enabled_immediate(self):
671        self.module.fail_json(msg=self.tx_not_permanent_error_msg)
672
673    def get_enabled_permanent(self):
674        zones = self.fw.config().listZones()
675        zone_names = [self.fw.config().getZone(z).get_property("name") for z in zones]
676        if self.zone in zone_names:
677            return True
678        else:
679            return False
680
681    def set_enabled_immediate(self):
682        self.module.fail_json(msg=self.tx_not_permanent_error_msg)
683
684    def set_enabled_permanent(self):
685        self.fw.config().addZone(self.zone, FirewallClientZoneSettings())
686
687    def set_disabled_immediate(self):
688        self.module.fail_json(msg=self.tx_not_permanent_error_msg)
689
690    def set_disabled_permanent(self):
691        zone_obj = self.fw.config().getZoneByName(self.zone)
692        zone_obj.remove()
693
694
695class ForwardPortTransaction(FirewallTransaction):
696    """
697    ForwardPortTransaction
698    """
699
700    def __init__(self, module, action_args=None, zone=None, desired_state=None, permanent=False, immediate=False):
701        super(ForwardPortTransaction, self).__init__(
702            module, action_args=action_args, desired_state=desired_state, zone=zone, permanent=permanent, immediate=immediate
703        )
704
705    def get_enabled_immediate(self, port, proto, toport, toaddr, timeout):
706        if self.fw_offline:
707            dummy, fw_settings = self.get_fw_zone_settings()
708            return fw_settings.queryForwardPort(port=port, protocol=proto, to_port=toport, to_addr=toaddr)
709        return self.fw.queryForwardPort(port=port, protocol=proto, to_port=toport, to_addr=toaddr)
710
711    def get_enabled_permanent(self, port, proto, toport, toaddr, timeout):
712        dummy, fw_settings = self.get_fw_zone_settings()
713        return fw_settings.queryForwardPort(port=port, protocol=proto, to_port=toport, to_addr=toaddr)
714
715    def set_enabled_immediate(self, port, proto, toport, toaddr, timeout):
716        self.fw.addForwardPort(self.zone, port, proto, toport, toaddr, timeout)
717
718    def set_enabled_permanent(self, port, proto, toport, toaddr, timeout):
719        fw_zone, fw_settings = self.get_fw_zone_settings()
720        fw_settings.addForwardPort(port, proto, toport, toaddr)
721        self.update_fw_settings(fw_zone, fw_settings)
722
723    def set_disabled_immediate(self, port, proto, toport, toaddr, timeout):
724        self.fw.removeForwardPort(self.zone, port, proto, toport, toaddr)
725
726    def set_disabled_permanent(self, port, proto, toport, toaddr, timeout):
727        fw_zone, fw_settings = self.get_fw_zone_settings()
728        fw_settings.removeForwardPort(port, proto, toport, toaddr)
729        self.update_fw_settings(fw_zone, fw_settings)
730
731
732def main():
733
734    module = AnsibleModule(
735        argument_spec=dict(
736            icmp_block=dict(type='str'),
737            icmp_block_inversion=dict(type='str'),
738            service=dict(type='str'),
739            port=dict(type='str'),
740            port_forward=dict(type='list', elements='dict'),
741            rich_rule=dict(type='str'),
742            zone=dict(type='str'),
743            immediate=dict(type='bool', default=False),
744            source=dict(type='str'),
745            permanent=dict(type='bool'),
746            state=dict(type='str', required=True, choices=['absent', 'disabled', 'enabled', 'present']),
747            timeout=dict(type='int', default=0),
748            interface=dict(type='str'),
749            masquerade=dict(type='str'),
750            offline=dict(type='bool'),
751            target=dict(type='str', choices=['default', 'ACCEPT', 'DROP', '%%REJECT%%']),
752        ),
753        supports_check_mode=True,
754        required_by=dict(
755            interface=('zone',),
756            target=('zone',),
757            source=('permanent',),
758        ),
759    )
760
761    permanent = module.params['permanent']
762    desired_state = module.params['state']
763    immediate = module.params['immediate']
764    timeout = module.params['timeout']
765    interface = module.params['interface']
766    masquerade = module.params['masquerade']
767
768    # Sanity checks
769    FirewallTransaction.sanity_check(module)
770
771    # If neither permanent or immediate is provided, assume immediate (as
772    # written in the module's docs)
773    if not permanent and not immediate:
774        immediate = True
775
776    # Verify required params are provided
777    if immediate and fw_offline:
778        module.fail_json(msg='firewall is not currently running, unable to perform immediate actions without a running firewall daemon')
779
780    changed = False
781    msgs = []
782    icmp_block = module.params['icmp_block']
783    icmp_block_inversion = module.params['icmp_block_inversion']
784    service = module.params['service']
785    rich_rule = module.params['rich_rule']
786    source = module.params['source']
787    zone = module.params['zone']
788    target = module.params['target']
789
790    if module.params['port'] is not None:
791        if '/' in module.params['port']:
792            port, protocol = module.params['port'].strip().split('/')
793        else:
794            protocol = None
795        if not protocol:
796            module.fail_json(msg='improper port format (missing protocol?)')
797    else:
798        port = None
799
800    port_forward_toaddr = ''
801    port_forward = None
802    if module.params['port_forward'] is not None:
803        if len(module.params['port_forward']) > 1:
804            module.fail_json(msg='Only one port forward supported at a time')
805        port_forward = module.params['port_forward'][0]
806        if 'port' not in port_forward:
807            module.fail_json(msg='port must be specified for port forward')
808        if 'proto' not in port_forward:
809            module.fail_json(msg='proto udp/tcp must be specified for port forward')
810        if 'toport' not in port_forward:
811            module.fail_json(msg='toport must be specified for port forward')
812        if 'toaddr' in port_forward:
813            port_forward_toaddr = port_forward['toaddr']
814
815    modification_count = 0
816    if icmp_block is not None:
817        modification_count += 1
818    if icmp_block_inversion is not None:
819        modification_count += 1
820    if service is not None:
821        modification_count += 1
822    if port is not None:
823        modification_count += 1
824    if port_forward is not None:
825        modification_count += 1
826    if rich_rule is not None:
827        modification_count += 1
828    if interface is not None:
829        modification_count += 1
830    if masquerade is not None:
831        modification_count += 1
832    if source is not None:
833        modification_count += 1
834    if target is not None:
835        modification_count += 1
836
837    if modification_count > 1:
838        module.fail_json(
839            msg='can only operate on port, service, rich_rule, masquerade, icmp_block, icmp_block_inversion, interface or source at once'
840        )
841    elif (modification_count > 0) and (desired_state in ['absent', 'present']) and (target is None):
842        module.fail_json(
843            msg='absent and present state can only be used in zone level operations'
844        )
845
846    if icmp_block is not None:
847
848        transaction = IcmpBlockTransaction(
849            module,
850            action_args=(icmp_block, timeout),
851            zone=zone,
852            desired_state=desired_state,
853            permanent=permanent,
854            immediate=immediate,
855        )
856
857        changed, transaction_msgs = transaction.run()
858        msgs = msgs + transaction_msgs
859        if changed is True:
860            msgs.append("Changed icmp-block %s to %s" % (icmp_block, desired_state))
861
862    if icmp_block_inversion is not None:
863
864        transaction = IcmpBlockInversionTransaction(
865            module,
866            action_args=(),
867            zone=zone,
868            desired_state=desired_state,
869            permanent=permanent,
870            immediate=immediate,
871        )
872
873        changed, transaction_msgs = transaction.run()
874        msgs = msgs + transaction_msgs
875        if changed is True:
876            msgs.append("Changed icmp-block-inversion %s to %s" % (icmp_block_inversion, desired_state))
877
878    if service is not None:
879
880        transaction = ServiceTransaction(
881            module,
882            action_args=(service, timeout),
883            zone=zone,
884            desired_state=desired_state,
885            permanent=permanent,
886            immediate=immediate,
887        )
888
889        changed, transaction_msgs = transaction.run()
890        msgs = msgs + transaction_msgs
891        if changed is True:
892            msgs.append("Changed service %s to %s" % (service, desired_state))
893
894    if source is not None:
895
896        transaction = SourceTransaction(
897            module,
898            action_args=(source,),
899            zone=zone,
900            desired_state=desired_state,
901            permanent=permanent,
902            immediate=immediate,
903        )
904
905        changed, transaction_msgs = transaction.run()
906        msgs = msgs + transaction_msgs
907
908    if port is not None:
909
910        transaction = PortTransaction(
911            module,
912            action_args=(port, protocol, timeout),
913            zone=zone,
914            desired_state=desired_state,
915            permanent=permanent,
916            immediate=immediate,
917        )
918
919        changed, transaction_msgs = transaction.run()
920        msgs = msgs + transaction_msgs
921        if changed is True:
922            msgs.append(
923                "Changed port %s to %s" % (
924                    "%s/%s" % (port, protocol), desired_state
925                )
926            )
927
928    if port_forward is not None:
929        transaction = ForwardPortTransaction(
930            module,
931            action_args=(str(port_forward['port']), port_forward['proto'],
932                         str(port_forward['toport']), port_forward_toaddr, timeout),
933            zone=zone,
934            desired_state=desired_state,
935            permanent=permanent,
936            immediate=immediate
937        )
938
939        changed, transaction_msgs = transaction.run()
940        msgs = msgs + transaction_msgs
941        if changed is True:
942            msgs.append(
943                "Changed port_forward %s to %s" % (
944                    "port=%s:proto=%s:toport=%s:toaddr=%s" % (
945                        port_forward['port'], port_forward['proto'],
946                        port_forward['toport'], port_forward_toaddr
947                    ), desired_state
948                )
949            )
950
951    if rich_rule is not None:
952
953        transaction = RichRuleTransaction(
954            module,
955            action_args=(rich_rule, timeout),
956            zone=zone,
957            desired_state=desired_state,
958            permanent=permanent,
959            immediate=immediate,
960        )
961
962        changed, transaction_msgs = transaction.run()
963        msgs = msgs + transaction_msgs
964        if changed is True:
965            msgs.append("Changed rich_rule %s to %s" % (rich_rule, desired_state))
966
967    if interface is not None:
968
969        transaction = InterfaceTransaction(
970            module,
971            action_args=(interface,),
972            zone=zone,
973            desired_state=desired_state,
974            permanent=permanent,
975            immediate=immediate,
976        )
977
978        changed, transaction_msgs = transaction.run()
979        msgs = msgs + transaction_msgs
980
981    if masquerade is not None:
982
983        transaction = MasqueradeTransaction(
984            module,
985            action_args=(),
986            zone=zone,
987            desired_state=desired_state,
988            permanent=permanent,
989            immediate=immediate,
990        )
991
992        changed, transaction_msgs = transaction.run()
993        msgs = msgs + transaction_msgs
994
995    if target is not None:
996
997        transaction = ZoneTargetTransaction(
998            module,
999            action_args=(target,),
1000            zone=zone,
1001            desired_state=desired_state,
1002            permanent=permanent,
1003            immediate=immediate,
1004        )
1005
1006        changed, transaction_msgs = transaction.run()
1007        msgs = msgs + transaction_msgs
1008
1009    ''' If there are no changes within the zone we are operating on the zone itself '''
1010    if modification_count == 0 and desired_state in ['absent', 'present']:
1011
1012        transaction = ZoneTransaction(
1013            module,
1014            action_args=(),
1015            zone=zone,
1016            desired_state=desired_state,
1017            permanent=permanent,
1018            immediate=immediate,
1019        )
1020
1021        changed, transaction_msgs = transaction.run()
1022        msgs = msgs + transaction_msgs
1023        if changed is True:
1024            msgs.append("Changed zone %s to %s" % (zone, desired_state))
1025
1026    if fw_offline:
1027        msgs.append("(offline operation: only on-disk configs were altered)")
1028
1029    module.exit_json(changed=changed, msg=', '.join(msgs))
1030
1031
1032if __name__ == '__main__':
1033    main()
1034