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