From 4f9b26e8af5890b7960291497020586426e7f1fc Mon Sep 17 00:00:00 2001
From: Kenny Woodson <kwoodson@redhat.com>
Date: Wed, 19 Jul 2017 08:51:14 -0400
Subject: First attempt at refactor of os_firewall

---
 playbooks/common/openshift-cluster/config.yml      |  15 ++
 .../common/openshift-cluster/openshift_hosted.yml  |   1 +
 roles/cockpit/defaults/main.yml                    |   6 +
 roles/cockpit/meta/main.yml                        |   5 +-
 roles/cockpit/tasks/firewall.yml                   |  40 +++
 roles/cockpit/tasks/main.yml                       |   4 +
 roles/docker/meta/main.yml                         |   1 -
 roles/etcd/defaults/main.yaml                      |  11 +
 roles/etcd/meta/main.yml                           |   7 +-
 roles/etcd/tasks/firewall.yml                      |  40 +++
 roles/etcd/tasks/main.yml                          |   4 +
 roles/lib_os_firewall/README.md                    |  63 +++++
 .../library/os_firewall_manage_iptables.py         | 283 +++++++++++++++++++++
 roles/nuage_master/defaults/main.yml               |   8 +
 roles/nuage_master/meta/main.yml                   |   5 +-
 roles/nuage_master/tasks/firewall.yml              |  40 +++
 roles/nuage_master/tasks/main.yaml                 |   4 +
 roles/nuage_node/defaults/main.yml                 |  11 +
 roles/nuage_node/meta/main.yml                     |   7 +-
 roles/nuage_node/tasks/firewall.yml                |  40 +++
 roles/nuage_node/tasks/main.yaml                   |   4 +
 roles/openshift_hosted/defaults/main.yml           |   9 +-
 roles/openshift_hosted/meta/main.yml               |   6 +-
 roles/openshift_hosted/tasks/registry/firewall.yml |  40 +++
 roles/openshift_hosted/tasks/registry/registry.yml |   7 +-
 roles/openshift_hosted/tasks/router/firewall.yml   |  40 +++
 roles/openshift_hosted/tasks/router/router.yml     |   4 +
 roles/openshift_loadbalancer/defaults/main.yml     |  12 +
 roles/openshift_loadbalancer/meta/main.yml         |  12 +-
 roles/openshift_loadbalancer/tasks/firewall.yml    |  40 +++
 roles/openshift_loadbalancer/tasks/main.yml        |   4 +
 roles/openshift_master/defaults/main.yml           |  18 ++
 roles/openshift_master/meta/main.yml               |  16 +-
 roles/openshift_master/tasks/firewall.yml          |  40 +++
 roles/openshift_master/tasks/main.yml              |   4 +
 roles/openshift_node/defaults/main.yml             |  14 +-
 roles/openshift_node/meta/main.yml                 |  27 +-
 roles/openshift_node/tasks/firewall.yml            |  40 +++
 roles/openshift_node/tasks/main.yml                |  32 +++
 roles/openshift_storage_nfs/defaults/main.yml      |   6 +
 roles/openshift_storage_nfs/meta/main.yml          |   5 +-
 roles/openshift_storage_nfs/tasks/firewall.yml     |  40 +++
 roles/openshift_storage_nfs/tasks/main.yml         |   4 +
 .../library/os_firewall_manage_iptables.py         | 283 ---------------------
 roles/os_firewall/tasks/firewall/firewalld.yml     |  16 --
 roles/os_firewall/tasks/firewall/iptables.yml      |  16 --
 46 files changed, 930 insertions(+), 404 deletions(-)
 create mode 100644 roles/cockpit/defaults/main.yml
 create mode 100644 roles/cockpit/tasks/firewall.yml
 create mode 100644 roles/etcd/tasks/firewall.yml
 create mode 100644 roles/lib_os_firewall/README.md
 create mode 100755 roles/lib_os_firewall/library/os_firewall_manage_iptables.py
 create mode 100644 roles/nuage_master/defaults/main.yml
 create mode 100644 roles/nuage_master/tasks/firewall.yml
 create mode 100644 roles/nuage_node/defaults/main.yml
 create mode 100644 roles/nuage_node/tasks/firewall.yml
 create mode 100644 roles/openshift_hosted/tasks/registry/firewall.yml
 create mode 100644 roles/openshift_hosted/tasks/router/firewall.yml
 create mode 100644 roles/openshift_loadbalancer/tasks/firewall.yml
 create mode 100644 roles/openshift_master/tasks/firewall.yml
 create mode 100644 roles/openshift_node/tasks/firewall.yml
 create mode 100644 roles/openshift_storage_nfs/tasks/firewall.yml
 delete mode 100755 roles/os_firewall/library/os_firewall_manage_iptables.py

diff --git a/playbooks/common/openshift-cluster/config.yml b/playbooks/common/openshift-cluster/config.yml
index 7136f1c1f..423573540 100644
--- a/playbooks/common/openshift-cluster/config.yml
+++ b/playbooks/common/openshift-cluster/config.yml
@@ -26,6 +26,21 @@
   tags:
   - always
 
+- name: Setup firewall
+  hosts: oo_all_hosts
+  tags:
+  - always
+  tasks:
+  # This should move to intialize_facts
+  - name: set os_firewall_enabled
+    set_fact:
+      os_firewall_enabled: true
+      os_firewall_use_firewalld: false
+
+  - name: Set proper firewall settings
+    include_role:
+      name: os_firewall
+
 - name: Disable excluders
   hosts: oo_masters_to_config:oo_nodes_to_config
   tags:
diff --git a/playbooks/common/openshift-cluster/openshift_hosted.yml b/playbooks/common/openshift-cluster/openshift_hosted.yml
index ce7f981ab..99a634970 100644
--- a/playbooks/common/openshift-cluster/openshift_hosted.yml
+++ b/playbooks/common/openshift-cluster/openshift_hosted.yml
@@ -29,6 +29,7 @@
   - role: openshift_default_storage_class
     when: openshift_cloudprovider_kind is defined and (openshift_cloudprovider_kind == 'aws' or openshift_cloudprovider_kind == 'gce')
   - role: openshift_hosted
+    r_openshift_hosted_use_calico: "{{ openshift.common.use_calico | default(false) | bool }}"
   - role: openshift_metrics
     when: openshift_hosted_metrics_deploy | default(false) | bool
   - role: openshift_logging
diff --git a/roles/cockpit/defaults/main.yml b/roles/cockpit/defaults/main.yml
new file mode 100644
index 000000000..d8231eced
--- /dev/null
+++ b/roles/cockpit/defaults/main.yml
@@ -0,0 +1,6 @@
+---
+r_cockpit_os_firewall_deny: []
+r_cockpit_os_firewall_allow:
+- service: cockpit-ws
+  port: 9090/tcp
+  cond: true
diff --git a/roles/cockpit/meta/main.yml b/roles/cockpit/meta/main.yml
index 0f507e75e..8c0ed3cb8 100644
--- a/roles/cockpit/meta/main.yml
+++ b/roles/cockpit/meta/main.yml
@@ -12,7 +12,4 @@ galaxy_info:
   categories:
   - cloud
 dependencies:
-- role: os_firewall
-  os_firewall_allow:
-  - service: cockpit-ws
-    port: 9090/tcp
+- role: lib_os_firewall
diff --git a/roles/cockpit/tasks/firewall.yml b/roles/cockpit/tasks/firewall.yml
new file mode 100644
index 000000000..b60cf7b28
--- /dev/null
+++ b/roles/cockpit/tasks/firewall.yml
@@ -0,0 +1,40 @@
+---
+- when: os_firewall_enabled | bool and not os_firewall_use_firewalld | bool
+  block:
+  - name: Add iptables allow rules
+    os_firewall_manage_iptables:
+      name: "{{ item.service }}"
+      action: add
+      protocol: "{{ item.port.split('/')[1] }}"
+      port: "{{ item.port.split('/')[0] }}"
+    when: item.cond
+    with_items: "{{ r_cockpit_os_firewall_allow }}"
+
+  - name: Remove iptables rules
+    os_firewall_manage_iptables:
+      name: "{{ item.service }}"
+      action: remove
+      protocol: "{{ item.port.split('/')[1] }}"
+      port: "{{ item.port.split('/')[0] }}"
+    when: item.cond
+    with_items: "{{ r_cockpit_os_firewall_deny }}"
+
+- when: os_firewall_enabled | bool and os_firewall_use_firewalld | bool
+  block:
+  - name: Add firewalld allow rules
+    firewalld:
+      port: "{{ item.port }}"
+      permanent: true
+      immediate: true
+      state: enabled
+    when: item.cond
+    with_items: "{{ r_cockpit_os_firewall_allow }}"
+
+  - name: Remove firewalld allow rules
+    firewalld:
+      port: "{{ item.port }}"
+      permanent: true
+      immediate: true
+      state: disabled
+    when: item.cond
+    with_items: "{{ r_cockpit_os_firewall_deny }}"
diff --git a/roles/cockpit/tasks/main.yml b/roles/cockpit/tasks/main.yml
index 57f49ea11..066ee3f3b 100644
--- a/roles/cockpit/tasks/main.yml
+++ b/roles/cockpit/tasks/main.yml
@@ -1,4 +1,8 @@
 ---
+- name: setup firewall
+  include: firewall.yml
+  static: yes
+
 - name: Install cockpit-ws
   package: name={{ item }} state=present
   with_items:
diff --git a/roles/docker/meta/main.yml b/roles/docker/meta/main.yml
index cd4083572..b773a417c 100644
--- a/roles/docker/meta/main.yml
+++ b/roles/docker/meta/main.yml
@@ -10,5 +10,4 @@ galaxy_info:
     versions:
     - 7
 dependencies:
-- role: os_firewall
 - role: lib_openshift
diff --git a/roles/etcd/defaults/main.yaml b/roles/etcd/defaults/main.yaml
index c0d1d5946..4c8d63b4c 100644
--- a/roles/etcd/defaults/main.yaml
+++ b/roles/etcd/defaults/main.yaml
@@ -7,4 +7,15 @@ etcd_listen_peer_urls: "{{ etcd_peer_url_scheme }}://{{ etcd_ip }}:{{ etcd_peer_
 etcd_advertise_client_urls: "{{ etcd_url_scheme }}://{{ etcd_ip }}:{{ etcd_client_port }}"
 etcd_listen_client_urls: "{{ etcd_url_scheme }}://{{ etcd_ip }}:{{ etcd_client_port }}"
 
+etcd_client_port: 2379
+etcd_peer_port: 2380
+
 etcd_systemd_dir: "/etc/systemd/system/{{ etcd_service }}.service.d"
+r_etcd_os_firewall_deny: []
+r_etcd_os_firewall_allow:
+- service: etcd
+  port: "{{etcd_client_port}}/tcp"
+  cond: true
+- service: etcd peering
+  port: "{{ etcd_peer_port }}/tcp"
+  cond: true
diff --git a/roles/etcd/meta/main.yml b/roles/etcd/meta/main.yml
index 689c07a84..9a955c822 100644
--- a/roles/etcd/meta/main.yml
+++ b/roles/etcd/meta/main.yml
@@ -17,11 +17,6 @@ galaxy_info:
   - system
 dependencies:
 - role: lib_openshift
-- role: os_firewall
-  os_firewall_allow:
-  - service: etcd
-    port: "{{etcd_client_port}}/tcp"
-  - service: etcd peering
-    port: "{{ etcd_peer_port }}/tcp"
+- role: lib_os_firewall
 - role: etcd_server_certificates
 - role: etcd_common
diff --git a/roles/etcd/tasks/firewall.yml b/roles/etcd/tasks/firewall.yml
new file mode 100644
index 000000000..6088b26ff
--- /dev/null
+++ b/roles/etcd/tasks/firewall.yml
@@ -0,0 +1,40 @@
+---
+- when: os_firewall_enabled | bool and not os_firewall_use_firewalld | bool
+  block:
+  - name: Add iptables allow rules
+    os_firewall_manage_iptables:
+      name: "{{ item.service }}"
+      action: add
+      protocol: "{{ item.port.split('/')[1] }}"
+      port: "{{ item.port.split('/')[0] }}"
+    when: item.cond
+    with_items: "{{ r_etcd_os_firewall_allow }}"
+
+  - name: Remove iptables rules
+    os_firewall_manage_iptables:
+      name: "{{ item.service }}"
+      action: remove
+      protocol: "{{ item.port.split('/')[1] }}"
+      port: "{{ item.port.split('/')[0] }}"
+    when: item.cond
+    with_items: "{{ r_etcd_os_firewall_deny }}"
+
+- when: os_firewall_enabled | bool and os_firewall_use_firewalld | bool
+  block:
+  - name: Add firewalld allow rules
+    firewalld:
+      port: "{{ item.port }}"
+      permanent: true
+      immediate: true
+      state: enabled
+    when: item.cond
+    with_items: "{{ r_etcd_os_firewall_allow }}"
+
+  - name: Remove firewalld allow rules
+    firewalld:
+      port: "{{ item.port }}"
+      permanent: true
+      immediate: true
+      state: disabled
+    when: item.cond
+    with_items: "{{ r_etcd_os_firewall_deny }}"
diff --git a/roles/etcd/tasks/main.yml b/roles/etcd/tasks/main.yml
index 8c2f392ee..78e543ef1 100644
--- a/roles/etcd/tasks/main.yml
+++ b/roles/etcd/tasks/main.yml
@@ -6,6 +6,10 @@
     etcd_hostname: "{{ etcd_hostname }}"
     etcd_ip: "{{ etcd_ip }}"
 
+- name: setup firewall
+  include: firewall.yml
+  static: yes
+
 - name: Install etcd
   package: name=etcd{{ '-' + etcd_version if etcd_version is defined else '' }} state=present
   when: not etcd_is_containerized | bool
diff --git a/roles/lib_os_firewall/README.md b/roles/lib_os_firewall/README.md
new file mode 100644
index 000000000..ba8c84865
--- /dev/null
+++ b/roles/lib_os_firewall/README.md
@@ -0,0 +1,63 @@
+lib_os_firewall
+===========
+
+lib_os_firewall manages iptables firewall settings for a minimal use
+case (Adding/Removing rules based on protocol and port number).
+
+Note: firewalld is not supported on Atomic Host
+https://bugzilla.redhat.com/show_bug.cgi?id=1403331
+
+Requirements
+------------
+
+Ansible 2.2
+
+Role Variables
+--------------
+
+| Name                      | Default |                                        |
+|---------------------------|---------|----------------------------------------|
+| os_firewall_allow         | []      | List of service,port mappings to allow |
+| os_firewall_deny          | []      | List of service, port mappings to deny |
+
+Dependencies
+------------
+
+None.
+
+Example Playbook
+----------------
+
+Use iptables and open tcp ports 80 and 443:
+```
+---
+- hosts: servers
+  vars:
+    os_firewall_use_firewalld: false
+    os_firewall_allow:
+    - service: httpd
+      port: 80/tcp
+    - service: https
+      port: 443/tcp
+  tasks:
+  - include_role:
+      name: lib_os_firewall
+
+  - name: set allow rules
+    os_firewall_manage_iptables:
+      name: "{{ item.service }}"
+      action: add
+      protocol: "{{ item.port.split('/')[1] }}"
+      port: "{{ item.port.split('/')[0] }}"
+    with_items: "{{ os_firewall_allow }}"
+```
+
+
+License
+-------
+
+Apache License, Version 2.0
+
+Author Information
+------------------
+Jason DeTiberus - jdetiber@redhat.com
diff --git a/roles/lib_os_firewall/library/os_firewall_manage_iptables.py b/roles/lib_os_firewall/library/os_firewall_manage_iptables.py
new file mode 100755
index 000000000..aeee3ede8
--- /dev/null
+++ b/roles/lib_os_firewall/library/os_firewall_manage_iptables.py
@@ -0,0 +1,283 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# pylint: disable=fixme, missing-docstring
+import subprocess
+
+DOCUMENTATION = '''
+---
+module: os_firewall_manage_iptables
+short_description: This module manages iptables rules for a given chain
+author: Jason DeTiberus
+requirements: [ ]
+'''
+EXAMPLES = '''
+'''
+
+
+class IpTablesError(Exception):
+    def __init__(self, msg, cmd, exit_code, output):
+        super(IpTablesError, self).__init__(msg)
+        self.msg = msg
+        self.cmd = cmd
+        self.exit_code = exit_code
+        self.output = output
+
+
+class IpTablesAddRuleError(IpTablesError):
+    pass
+
+
+class IpTablesRemoveRuleError(IpTablesError):
+    def __init__(self, chain, msg, cmd, exit_code, output):  # pylint: disable=too-many-arguments, line-too-long, redefined-outer-name
+        super(IpTablesRemoveRuleError, self).__init__(msg, cmd, exit_code,
+                                                      output)
+        self.chain = chain
+
+
+class IpTablesSaveError(IpTablesError):
+    pass
+
+
+class IpTablesCreateChainError(IpTablesError):
+    def __init__(self, chain, msg, cmd, exit_code, output):  # pylint: disable=too-many-arguments, line-too-long, redefined-outer-name
+        super(IpTablesCreateChainError, self).__init__(msg, cmd, exit_code,
+                                                       output)
+        self.chain = chain
+
+
+class IpTablesCreateJumpRuleError(IpTablesError):
+    def __init__(self, chain, msg, cmd, exit_code, output):  # pylint: disable=too-many-arguments, line-too-long, redefined-outer-name
+        super(IpTablesCreateJumpRuleError, self).__init__(msg, cmd, exit_code,
+                                                          output)
+        self.chain = chain
+
+
+# TODO: implement rollbacks for any events that were successful and an
+# exception was thrown later. For example, when the chain is created
+# successfully, but the add/remove rule fails.
+class IpTablesManager(object):  # pylint: disable=too-many-instance-attributes
+    def __init__(self, module):
+        self.module = module
+        self.ip_version = module.params['ip_version']
+        self.check_mode = module.check_mode
+        self.chain = module.params['chain']
+        self.create_jump_rule = module.params['create_jump_rule']
+        self.jump_rule_chain = module.params['jump_rule_chain']
+        self.cmd = self.gen_cmd()
+        self.save_cmd = self.gen_save_cmd()
+        self.output = []
+        self.changed = False
+
+    def save(self):
+        try:
+            self.output.append(subprocess.check_output(self.save_cmd, stderr=subprocess.STDOUT))
+        except subprocess.CalledProcessError as ex:
+            raise IpTablesSaveError(
+                msg="Failed to save iptables rules",
+                cmd=ex.cmd, exit_code=ex.returncode, output=ex.output)
+
+    def verify_chain(self):
+        if not self.chain_exists():
+            self.create_chain()
+        if self.create_jump_rule and not self.jump_rule_exists():
+            self.create_jump()
+
+    def add_rule(self, port, proto):
+        rule = self.gen_rule(port, proto)
+        if not self.rule_exists(rule):
+            self.verify_chain()
+
+            if self.check_mode:
+                self.changed = True
+                self.output.append("Create rule for %s %s" % (proto, port))
+            else:
+                cmd = self.cmd + ['-A'] + rule
+                try:
+                    self.output.append(subprocess.check_output(cmd))
+                    self.changed = True
+                    self.save()
+                except subprocess.CalledProcessError as ex:
+                    raise IpTablesCreateChainError(
+                        chain=self.chain,
+                        msg="Failed to create rule for "
+                            "%s %s" % (proto, port),
+                        cmd=ex.cmd, exit_code=ex.returncode,
+                        output=ex.output)
+
+    def remove_rule(self, port, proto):
+        rule = self.gen_rule(port, proto)
+        if self.rule_exists(rule):
+            if self.check_mode:
+                self.changed = True
+                self.output.append("Remove rule for %s %s" % (proto, port))
+            else:
+                cmd = self.cmd + ['-D'] + rule
+                try:
+                    self.output.append(subprocess.check_output(cmd))
+                    self.changed = True
+                    self.save()
+                except subprocess.CalledProcessError as ex:
+                    raise IpTablesRemoveRuleError(
+                        chain=self.chain,
+                        msg="Failed to remove rule for %s %s" % (proto, port),
+                        cmd=ex.cmd, exit_code=ex.returncode, output=ex.output)
+
+    def rule_exists(self, rule):
+        check_cmd = self.cmd + ['-C'] + rule
+        return True if subprocess.call(check_cmd) == 0 else False
+
+    @staticmethod
+    def port_as_argument(port):
+        if isinstance(port, int):
+            return str(port)
+        if isinstance(port, basestring):  # noqa: F405
+            return port.replace('-', ":")
+        return port
+
+    def gen_rule(self, port, proto):
+        return [self.chain, '-p', proto, '-m', 'state', '--state', 'NEW',
+                '-m', proto, '--dport', IpTablesManager.port_as_argument(port), '-j', 'ACCEPT']
+
+    def create_jump(self):
+        if self.check_mode:
+            self.changed = True
+            self.output.append("Create jump rule for chain %s" % self.chain)
+        else:
+            try:
+                cmd = self.cmd + ['-L', self.jump_rule_chain, '--line-numbers']
+                output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
+
+                # break the input rules into rows and columns
+                input_rules = [s.split() for s in to_native(output).split('\n')]
+
+                # Find the last numbered rule
+                last_rule_num = None
+                last_rule_target = None
+                for rule in input_rules[:-1]:
+                    if rule:
+                        try:
+                            last_rule_num = int(rule[0])
+                        except ValueError:
+                            continue
+                        last_rule_target = rule[1]
+
+                # Naively assume that if the last row is a REJECT or DROP rule,
+                # then we can insert our rule right before it, otherwise we
+                # assume that we can just append the rule.
+                if (last_rule_num and last_rule_target and last_rule_target in ['REJECT', 'DROP']):
+                    # insert rule
+                    cmd = self.cmd + ['-I', self.jump_rule_chain,
+                                      str(last_rule_num)]
+                else:
+                    # append rule
+                    cmd = self.cmd + ['-A', self.jump_rule_chain]
+                cmd += ['-j', self.chain]
+                output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
+                self.changed = True
+                self.output.append(output)
+                self.save()
+            except subprocess.CalledProcessError as ex:
+                if '--line-numbers' in ex.cmd:
+                    raise IpTablesCreateJumpRuleError(
+                        chain=self.chain,
+                        msg=("Failed to query existing " +
+                             self.jump_rule_chain +
+                             " rules to determine jump rule location"),
+                        cmd=ex.cmd, exit_code=ex.returncode,
+                        output=ex.output)
+                else:
+                    raise IpTablesCreateJumpRuleError(
+                        chain=self.chain,
+                        msg=("Failed to create jump rule for chain " +
+                             self.chain),
+                        cmd=ex.cmd, exit_code=ex.returncode,
+                        output=ex.output)
+
+    def create_chain(self):
+        if self.check_mode:
+            self.changed = True
+            self.output.append("Create chain %s" % self.chain)
+        else:
+            try:
+                cmd = self.cmd + ['-N', self.chain]
+                self.output.append(subprocess.check_output(cmd, stderr=subprocess.STDOUT))
+                self.changed = True
+                self.output.append("Successfully created chain %s" %
+                                   self.chain)
+                self.save()
+            except subprocess.CalledProcessError as ex:
+                raise IpTablesCreateChainError(
+                    chain=self.chain,
+                    msg="Failed to create chain: %s" % self.chain,
+                    cmd=ex.cmd, exit_code=ex.returncode, output=ex.output
+                )
+
+    def jump_rule_exists(self):
+        cmd = self.cmd + ['-C', self.jump_rule_chain, '-j', self.chain]
+        return True if subprocess.call(cmd) == 0 else False
+
+    def chain_exists(self):
+        cmd = self.cmd + ['-L', self.chain]
+        return True if subprocess.call(cmd) == 0 else False
+
+    def gen_cmd(self):
+        cmd = 'iptables' if self.ip_version == 'ipv4' else 'ip6tables'
+        # Include -w (wait for xtables lock) in default arguments.
+        default_args = ['-w']
+        return ["/usr/sbin/%s" % cmd] + default_args
+
+    def gen_save_cmd(self):  # pylint: disable=no-self-use
+        return ['/usr/libexec/iptables/iptables.init', 'save']
+
+
+def main():
+    module = AnsibleModule(  # noqa: F405
+        argument_spec=dict(
+            name=dict(required=True),
+            action=dict(required=True, choices=['add', 'remove',
+                                                'verify_chain']),
+            chain=dict(required=False, default='OS_FIREWALL_ALLOW'),
+            create_jump_rule=dict(required=False, type='bool', default=True),
+            jump_rule_chain=dict(required=False, default='INPUT'),
+            protocol=dict(required=False, choices=['tcp', 'udp']),
+            port=dict(required=False, type='str'),
+            ip_version=dict(required=False, default='ipv4',
+                            choices=['ipv4', 'ipv6']),
+        ),
+        supports_check_mode=True
+    )
+
+    action = module.params['action']
+    protocol = module.params['protocol']
+    port = module.params['port']
+
+    if action in ['add', 'remove']:
+        if not protocol:
+            error = "protocol is required when action is %s" % action
+            module.fail_json(msg=error)
+        if not port:
+            error = "port is required when action is %s" % action
+            module.fail_json(msg=error)
+
+    iptables_manager = IpTablesManager(module)
+
+    try:
+        if action == 'add':
+            iptables_manager.add_rule(port, protocol)
+        elif action == 'remove':
+            iptables_manager.remove_rule(port, protocol)
+        elif action == 'verify_chain':
+            iptables_manager.verify_chain()
+    except IpTablesError as ex:
+        module.fail_json(msg=ex.msg)
+
+    return module.exit_json(changed=iptables_manager.changed,
+                            output=iptables_manager.output)
+
+
+# pylint: disable=redefined-builtin, unused-wildcard-import, wildcard-import,  wrong-import-position
+# import module snippets
+from ansible.module_utils.basic import *  # noqa: F403,E402
+from ansible.module_utils._text import to_native  # noqa: E402
+if __name__ == '__main__':
+    main()
diff --git a/roles/nuage_master/defaults/main.yml b/roles/nuage_master/defaults/main.yml
new file mode 100644
index 000000000..7b5015a02
--- /dev/null
+++ b/roles/nuage_master/defaults/main.yml
@@ -0,0 +1,8 @@
+---
+nuage_mon_rest_server_port: '9443'
+
+r_nuage_master_os_firewall_deny: []
+r_nuage_master_os_firewall_allow:
+- service: openshift-monitor
+  port: "{{ nuage_mon_rest_server_port }}/tcp"
+  cond: true
diff --git a/roles/nuage_master/meta/main.yml b/roles/nuage_master/meta/main.yml
index e3ed9ac71..3da340c85 100644
--- a/roles/nuage_master/meta/main.yml
+++ b/roles/nuage_master/meta/main.yml
@@ -16,8 +16,5 @@ dependencies:
 - role: nuage_ca
 - role: nuage_common
 - role: openshift_etcd_client_certificates
-- role: os_firewall
 - role: lib_openshift
-  os_firewall_allow:
-  - service: openshift-monitor
-    port: "{{ nuage_mon_rest_server_port }}/tcp"
+- role: lib_os_firewall
diff --git a/roles/nuage_master/tasks/firewall.yml b/roles/nuage_master/tasks/firewall.yml
new file mode 100644
index 000000000..b47699966
--- /dev/null
+++ b/roles/nuage_master/tasks/firewall.yml
@@ -0,0 +1,40 @@
+---
+- when: os_firewall_enabled | bool and not os_firewall_use_firewalld | bool
+  block:
+  - name: Add iptables allow rules
+    os_firewall_manage_iptables:
+      name: "{{ item.service }}"
+      action: add
+      protocol: "{{ item.port.split('/')[1] }}"
+      port: "{{ item.port.split('/')[0] }}"
+    when: item.cond
+    with_items: "{{ r_nuage_master_os_firewall_allow }}"
+
+  - name: Remove iptables rules
+    os_firewall_manage_iptables:
+      name: "{{ item.service }}"
+      action: remove
+      protocol: "{{ item.port.split('/')[1] }}"
+      port: "{{ item.port.split('/')[0] }}"
+    when: item.cond
+    with_items: "{{ r_nuage_master_os_firewall_deny }}"
+
+- when: os_firewall_enabled | bool and os_firewall_use_firewalld | bool
+  block:
+  - name: Add firewalld allow rules
+    firewalld:
+      port: "{{ item.port }}"
+      permanent: true
+      immediate: true
+      state: enabled
+    when: item.cond
+    with_items: "{{ r_nuage_master_os_firewall_allow }}"
+
+  - name: Remove firewalld allow rules
+    firewalld:
+      port: "{{ item.port }}"
+      permanent: true
+      immediate: true
+      state: disabled
+    when: item.cond
+    with_items: "{{ r_nuage_master_os_firewall_deny }}"
diff --git a/roles/nuage_master/tasks/main.yaml b/roles/nuage_master/tasks/main.yaml
index 4f8adb63e..cb7f75398 100644
--- a/roles/nuage_master/tasks/main.yaml
+++ b/roles/nuage_master/tasks/main.yaml
@@ -1,4 +1,8 @@
 ---
+- name: setup firewall
+  include: firewall.yml
+  static: yes
+
 - name: Create directory /usr/share/nuage-openshift-monitor
   become: yes
   file: path=/usr/share/nuage-openshift-monitor state=directory
diff --git a/roles/nuage_node/defaults/main.yml b/roles/nuage_node/defaults/main.yml
new file mode 100644
index 000000000..c31c8a7dd
--- /dev/null
+++ b/roles/nuage_node/defaults/main.yml
@@ -0,0 +1,11 @@
+---
+nuage_mon_rest_server_port: '9443'
+
+r_nuage_node_os_firewall_deny: []
+r_nuage_node_os_firewall_allow:
+- service: vxlan
+  port: 4789/udp
+  cond: true
+- service: nuage-monitor
+  port: "{{ nuage_mon_rest_server_port }}/tcp"
+  cond: true
diff --git a/roles/nuage_node/meta/main.yml b/roles/nuage_node/meta/main.yml
index 3e2a5e0c9..9b0315054 100644
--- a/roles/nuage_node/meta/main.yml
+++ b/roles/nuage_node/meta/main.yml
@@ -15,9 +15,4 @@ galaxy_info:
 dependencies:
 - role: nuage_common
 - role: nuage_ca
-- role: os_firewall
-  os_firewall_allow:
-  - service: vxlan
-    port: 4789/udp
-  - service: nuage-monitor
-    port: "{{ nuage_mon_rest_server_port }}/tcp"
+- role: lib_os_firewall
diff --git a/roles/nuage_node/tasks/firewall.yml b/roles/nuage_node/tasks/firewall.yml
new file mode 100644
index 000000000..cb0bffb09
--- /dev/null
+++ b/roles/nuage_node/tasks/firewall.yml
@@ -0,0 +1,40 @@
+---
+- when: os_firewall_enabled | bool and not os_firewall_use_firewalld | bool
+  block:
+  - name: Add iptables allow rules
+    os_firewall_manage_iptables:
+      name: "{{ item.service }}"
+      action: add
+      protocol: "{{ item.port.split('/')[1] }}"
+      port: "{{ item.port.split('/')[0] }}"
+    when: item.cond
+    with_items: "{{ r_nuage_node_os_firewall_allow }}"
+
+  - name: Remove iptables rules
+    os_firewall_manage_iptables:
+      name: "{{ item.service }}"
+      action: remove
+      protocol: "{{ item.port.split('/')[1] }}"
+      port: "{{ item.port.split('/')[0] }}"
+    when: item.cond
+    with_items: "{{ r_nuage_node_os_firewall_deny }}"
+
+- when: os_firewall_enabled | bool and os_firewall_use_firewalld | bool
+  block:
+  - name: Add firewalld allow rules
+    firewalld:
+      port: "{{ item.port }}"
+      permanent: true
+      immediate: true
+      state: enabled
+    when: item.cond
+    with_items: "{{ r_nuage_node_os_firewall_allow }}"
+
+  - name: Remove firewalld allow rules
+    firewalld:
+      port: "{{ item.port }}"
+      permanent: true
+      immediate: true
+      state: disabled
+    when: item.cond
+    with_items: "{{ r_nuage_node_os_firewall_deny }}"
diff --git a/roles/nuage_node/tasks/main.yaml b/roles/nuage_node/tasks/main.yaml
index 928f9e2e6..9cd743304 100644
--- a/roles/nuage_node/tasks/main.yaml
+++ b/roles/nuage_node/tasks/main.yaml
@@ -54,3 +54,7 @@
     - restart node
 
 - include: iptables.yml
+
+- name: setup firewall
+  include: firewall.yml
+  static: yes
diff --git a/roles/openshift_hosted/defaults/main.yml b/roles/openshift_hosted/defaults/main.yml
index 0391e5602..f1fd0f4b7 100644
--- a/roles/openshift_hosted/defaults/main.yml
+++ b/roles/openshift_hosted/defaults/main.yml
@@ -26,12 +26,15 @@ openshift_hosted_routers:
   - 443:443
   certificate: "{{ openshift_hosted_router_certificate | default({}) }}"
 
-
 openshift_hosted_router_certificate: {}
 openshift_hosted_registry_cert_expire_days: 730
 openshift_hosted_router_create_certificate: True
 
-os_firewall_allow:
+r_openshift_hosted_router_os_firewall_deny: []
+r_openshift_hosted_router_os_firewall_allow: []
+
+r_openshift_hosted_registry_os_firewall_deny: []
+r_openshift_hosted_registry_os_firewall_allow:
 - service: Docker Registry Port
   port: 5000/tcp
-  when: openshift.common.use_calico | bool
+  cond: "{{ r_openshift_hosted_use_calico }}"
diff --git a/roles/openshift_hosted/meta/main.yml b/roles/openshift_hosted/meta/main.yml
index 9e3f37130..28fd396d6 100644
--- a/roles/openshift_hosted/meta/main.yml
+++ b/roles/openshift_hosted/meta/main.yml
@@ -15,8 +15,4 @@ dependencies:
 - role: openshift_cli
 - role: openshift_hosted_facts
 - role: lib_openshift
-- role: os_firewall
-  os_firewall_allow:
-  - service: Docker Registry Port
-    port: 5000/tcp
-  when: openshift.common.use_calico | bool
+- role: lib_os_firewall
diff --git a/roles/openshift_hosted/tasks/registry/firewall.yml b/roles/openshift_hosted/tasks/registry/firewall.yml
new file mode 100644
index 000000000..ea9f50047
--- /dev/null
+++ b/roles/openshift_hosted/tasks/registry/firewall.yml
@@ -0,0 +1,40 @@
+---
+- when: os_firewall_enabled | bool and not os_firewall_use_firewalld | bool
+  block:
+  - name: Add iptables allow rules
+    os_firewall_manage_iptables:
+      name: "{{ item.service }}"
+      action: add
+      protocol: "{{ item.port.split('/')[1] }}"
+      port: "{{ item.port.split('/')[0] }}"
+    when: item.cond
+    with_items: "{{ r_openshift_hosted_registry_os_firewall_allow }}"
+
+  - name: Remove iptables rules
+    os_firewall_manage_iptables:
+      name: "{{ item.service }}"
+      action: remove
+      protocol: "{{ item.port.split('/')[1] }}"
+      port: "{{ item.port.split('/')[0] }}"
+    when: item.cond
+    with_items: "{{ r_openshift_hosted_registry_os_firewall_deny }}"
+
+- when: os_firewall_enabled | bool and os_firewall_use_firewalld | bool
+  block:
+  - name: Add firewalld allow rules
+    firewalld:
+      port: "{{ item.port }}"
+      permanent: true
+      immediate: true
+      state: enabled
+    when: item.cond
+    with_items: "{{ r_openshift_hosted_registry_os_firewall_allow }}"
+
+  - name: Remove firewalld allow rules
+    firewalld:
+      port: "{{ item.port }}"
+      permanent: true
+      immediate: true
+      state: disabled
+    when: item.cond
+    with_items: "{{ r_openshift_hosted_registry_os_firewall_deny }}"
diff --git a/roles/openshift_hosted/tasks/registry/registry.yml b/roles/openshift_hosted/tasks/registry/registry.yml
index b946ec8ca..dcd9c87fc 100644
--- a/roles/openshift_hosted/tasks/registry/registry.yml
+++ b/roles/openshift_hosted/tasks/registry/registry.yml
@@ -1,6 +1,10 @@
 ---
-- block:
+- name: setup firewall
+  include: firewall.yml
+  static: yes
 
+- when: openshift.hosted.registry.replicas | default(none) is none
+  block:
   - name: Retrieve list of openshift nodes matching registry selector
     oc_obj:
       state: list
@@ -28,7 +32,6 @@
       l_default_replicas: "{{ l_node_count if openshift.hosted.registry.storage.kind | default(none) is not none else 1 }}"
     when: l_node_count | int > 0
 
-  when: openshift.hosted.registry.replicas | default(none) is none
 
 - name: set openshift_hosted facts
   set_fact:
diff --git a/roles/openshift_hosted/tasks/router/firewall.yml b/roles/openshift_hosted/tasks/router/firewall.yml
new file mode 100644
index 000000000..f8643aab7
--- /dev/null
+++ b/roles/openshift_hosted/tasks/router/firewall.yml
@@ -0,0 +1,40 @@
+---
+- when: os_firewall_enabled | bool and not os_firewall_use_firewalld | bool
+  block:
+  - name: Add iptables allow rules
+    os_firewall_manage_iptables:
+      name: "{{ item.service }}"
+      action: add
+      protocol: "{{ item.port.split('/')[1] }}"
+      port: "{{ item.port.split('/')[0] }}"
+    when: item.cond
+    with_items: "{{ r_openshift_hosted_router_os_firewall_allow }}"
+
+  - name: Remove iptables rules
+    os_firewall_manage_iptables:
+      name: "{{ item.service }}"
+      action: remove
+      protocol: "{{ item.port.split('/')[1] }}"
+      port: "{{ item.port.split('/')[0] }}"
+    when: item.cond
+    with_items: "{{ r_openshift_hosted_router_os_firewall_deny }}"
+
+- when: os_firewall_enabled | bool and os_firewall_use_firewalld | bool
+  block:
+  - name: Add firewalld allow rules
+    firewalld:
+      port: "{{ item.port }}"
+      permanent: true
+      immediate: true
+      state: enabled
+    when: item.cond
+    with_items: "{{ r_openshift_hosted_router_os_firewall_allow }}"
+
+  - name: Remove firewalld allow rules
+    firewalld:
+      port: "{{ item.port }}"
+      permanent: true
+      immediate: true
+      state: disabled
+    when: item.cond
+    with_items: "{{ r_openshift_hosted_router_os_firewall_deny }}"
diff --git a/roles/openshift_hosted/tasks/router/router.yml b/roles/openshift_hosted/tasks/router/router.yml
index dd485a64a..72a1ead80 100644
--- a/roles/openshift_hosted/tasks/router/router.yml
+++ b/roles/openshift_hosted/tasks/router/router.yml
@@ -1,4 +1,8 @@
 ---
+- name: setup firewall
+  include: firewall.yml
+  static: yes
+
 - name: Retrieve list of openshift nodes matching router selector
   oc_obj:
     state: list
diff --git a/roles/openshift_loadbalancer/defaults/main.yml b/roles/openshift_loadbalancer/defaults/main.yml
index 6190383b6..4a20f5b5a 100644
--- a/roles/openshift_loadbalancer/defaults/main.yml
+++ b/roles/openshift_loadbalancer/defaults/main.yml
@@ -12,3 +12,15 @@ haproxy_backends:
   - name: web01
     address: 127.0.0.1:9000
     opts: check
+
+r_openshift_loadbalancer_os_firewall_deny: []
+r_openshift_loadbalancer_os_firewall_allow:
+- service: haproxy stats
+  port: "9000/tcp"
+  cond: true
+- service: haproxy balance
+  port: "{{ openshift_master_api_port | default(8443) }}/tcp"
+  cond: true
+- service: nuage mon
+  port: "{{ nuage_mon_rest_server_port | default(9443) }}/tcp"
+  cond: "{{ openshift_use_nuage | default(false) | bool }}"
diff --git a/roles/openshift_loadbalancer/meta/main.yml b/roles/openshift_loadbalancer/meta/main.yml
index 0dffb545f..073bdd94d 100644
--- a/roles/openshift_loadbalancer/meta/main.yml
+++ b/roles/openshift_loadbalancer/meta/main.yml
@@ -10,16 +10,6 @@ galaxy_info:
     versions:
     - 7
 dependencies:
+- role: lib_os_firewall
 - role: openshift_facts
-- role: os_firewall
-  os_firewall_allow:
-  - service: haproxy stats
-    port: "9000/tcp"
-  - service: haproxy balance
-    port: "{{ openshift_master_api_port | default(8443) }}/tcp"
-- role: os_firewall
-  os_firewall_allow:
-  - service: nuage mon
-    port: "{{ nuage_mon_rest_server_port | default(9443) }}/tcp"
-  when: openshift_use_nuage | default(false) | bool
 - role: openshift_repos
diff --git a/roles/openshift_loadbalancer/tasks/firewall.yml b/roles/openshift_loadbalancer/tasks/firewall.yml
new file mode 100644
index 000000000..c8628f6f8
--- /dev/null
+++ b/roles/openshift_loadbalancer/tasks/firewall.yml
@@ -0,0 +1,40 @@
+---
+- when: os_firewall_enabled | bool and not os_firewall_use_firewalld | bool
+  block:
+  - name: Add iptables allow rules
+    os_firewall_manage_iptables:
+      name: "{{ item.service }}"
+      action: add
+      protocol: "{{ item.port.split('/')[1] }}"
+      port: "{{ item.port.split('/')[0] }}"
+    when: item.cond
+    with_items: "{{ r_openshift_loadbalancer_os_firewall_allow }}"
+
+  - name: Remove iptables rules
+    os_firewall_manage_iptables:
+      name: "{{ item.service }}"
+      action: remove
+      protocol: "{{ item.port.split('/')[1] }}"
+      port: "{{ item.port.split('/')[0] }}"
+    when: item.cond
+    with_items: "{{ r_openshift_loadbalancer_os_firewall_deny }}"
+
+- when: os_firewall_enabled | bool and os_firewall_use_firewalld | bool
+  block:
+  - name: Add firewalld allow rules
+    firewalld:
+      port: "{{ item.port }}"
+      permanent: true
+      immediate: true
+      state: enabled
+    when: item.cond
+    with_items: "{{ r_openshift_loadbalancer_os_firewall_allow }}"
+
+  - name: Remove firewalld allow rules
+    firewalld:
+      port: "{{ item.port }}"
+      permanent: true
+      immediate: true
+      state: disabled
+    when: item.cond
+    with_items: "{{ r_openshift_loadbalancer_os_firewall_deny }}"
diff --git a/roles/openshift_loadbalancer/tasks/main.yml b/roles/openshift_loadbalancer/tasks/main.yml
index 68bb4ace8..69b061fc5 100644
--- a/roles/openshift_loadbalancer/tasks/main.yml
+++ b/roles/openshift_loadbalancer/tasks/main.yml
@@ -1,4 +1,8 @@
 ---
+- name: setup firewall
+  include: firewall.yml
+  static: yes
+
 - name: Install haproxy
   package: name=haproxy state=present
   when: not openshift.common.is_containerized | bool
diff --git a/roles/openshift_master/defaults/main.yml b/roles/openshift_master/defaults/main.yml
index 2d3ce5bcd..547801fa5 100644
--- a/roles/openshift_master/defaults/main.yml
+++ b/roles/openshift_master/defaults/main.yml
@@ -2,3 +2,21 @@
 openshift_node_ips: []
 r_openshift_master_clean_install: false
 r_openshift_master_etcd3_storage: false
+r_openshift_master_os_firewall_enable: true
+r_openshift_master_os_firewall_deny: []
+r_openshift_master_os_firewall_allow:
+- service: api server https
+  port: "{{ openshift.master.api_port }}/tcp"
+  cond: true
+- service: api controllers https
+  port: "{{ openshift.master.controllers_port }}/tcp"
+  cond: true
+- service: skydns tcp
+  port: "{{ openshift.master.dns_port }}/tcp"
+  cond: true
+- service: skydns udp
+  port: "{{ openshift.master.dns_port }}/udp"
+  cond: true
+- service: etcd embedded
+  port: 4001/tcp
+  cond: "{{ groups.oo_etcd_to_config | default([]) | length == 0 }}"
diff --git a/roles/openshift_master/meta/main.yml b/roles/openshift_master/meta/main.yml
index 907f25bc5..bd2383f61 100644
--- a/roles/openshift_master/meta/main.yml
+++ b/roles/openshift_master/meta/main.yml
@@ -13,6 +13,7 @@ galaxy_info:
   - cloud
 dependencies:
 - role: lib_openshift
+- role: lib_os_firewall
 - role: openshift_master_facts
 - role: openshift_hosted_facts
 - role: openshift_master_certificates
@@ -25,21 +26,6 @@ dependencies:
 - role: openshift_cloud_provider
 - role: openshift_builddefaults
 - role: openshift_buildoverrides
-- role: os_firewall
-  os_firewall_allow:
-  - service: api server https
-    port: "{{ openshift.master.api_port }}/tcp"
-  - service: api controllers https
-    port: "{{ openshift.master.controllers_port }}/tcp"
-  - service: skydns tcp
-    port: "{{ openshift.master.dns_port }}/tcp"
-  - service: skydns udp
-    port: "{{ openshift.master.dns_port }}/udp"
-- role: os_firewall
-  os_firewall_allow:
-  - service: etcd embedded
-    port: 4001/tcp
-  when: groups.oo_etcd_to_config | default([]) | length == 0
 - role: nickhammond.logrotate
 - role: contiv
   contiv_role: netmaster
diff --git a/roles/openshift_master/tasks/firewall.yml b/roles/openshift_master/tasks/firewall.yml
new file mode 100644
index 000000000..15073da98
--- /dev/null
+++ b/roles/openshift_master/tasks/firewall.yml
@@ -0,0 +1,40 @@
+---
+- when: os_firewall_enabled | bool and not os_firewall_use_firewalld | bool
+  block:
+  - name: Add iptables allow rules
+    os_firewall_manage_iptables:
+      name: "{{ item.service }}"
+      action: add
+      protocol: "{{ item.port.split('/')[1] }}"
+      port: "{{ item.port.split('/')[0] }}"
+    when: item.cond
+    with_items: "{{ r_openshift_master_os_firewall_allow }}"
+
+  - name: Remove iptables rules
+    os_firewall_manage_iptables:
+      name: "{{ item.service }}"
+      action: remove
+      protocol: "{{ item.port.split('/')[1] }}"
+      port: "{{ item.port.split('/')[0] }}"
+    when: item.cond
+    with_items: "{{ r_openshift_master_os_firewall_deny }}"
+
+- when: os_firewall_enabled | bool and os_firewall_use_firewalld | bool
+  block:
+  - name: Add firewalld allow rules
+    firewalld:
+      port: "{{ item.port }}"
+      permanent: true
+      immediate: true
+      state: enabled
+    when: item.cond
+    with_items: "{{ r_openshift_master_os_firewall_allow }}"
+
+  - name: Remove firewalld allow rules
+    firewalld:
+      port: "{{ item.port }}"
+      permanent: true
+      immediate: true
+      state: disabled
+    when: item.cond
+    with_items: "{{ r_openshift_master_os_firewall_deny }}"
diff --git a/roles/openshift_master/tasks/main.yml b/roles/openshift_master/tasks/main.yml
index 1f182a25c..acf49db26 100644
--- a/roles/openshift_master/tasks/main.yml
+++ b/roles/openshift_master/tasks/main.yml
@@ -23,6 +23,10 @@
     msg: "Pacemaker based HA is not supported at this time when used with containerized installs"
   when: openshift_master_ha | bool and openshift_master_cluster_method == "pacemaker" and openshift.common.is_containerized | bool
 
+- name: Open up firewall ports
+  include: firewall.yml
+  static: yes
+
 - name: Install Master package
   package:
     name: "{{ openshift.common.service_type }}-master{{ openshift_pkg_version | default('') | oo_image_tag_to_rpm_version(include_dash=True) }}"
diff --git a/roles/openshift_node/defaults/main.yml b/roles/openshift_node/defaults/main.yml
index 47073ee0f..52218f683 100644
--- a/roles/openshift_node/defaults/main.yml
+++ b/roles/openshift_node/defaults/main.yml
@@ -1,14 +1,24 @@
 ---
-os_firewall_allow:
+r_openshift_node_os_firewall_deny: []
+r_openshift_node_os_firewall_allow:
 - service: Kubernetes kubelet
   port: 10250/tcp
+  cond: true
 - service: http
   port: 80/tcp
+  cond: true
 - service: https
   port: 443/tcp
+  cond: true
 - service: OpenShift OVS sdn
   port: 4789/udp
   when: openshift.common.use_openshift_sdn | default(true) | bool
 - service: Calico BGP Port
   port: 179/tcp
-  when: openshift.common.use_calico | bool
+  cond: "{{ openshift.common.use_calico | bool }}"
+- service: Kubernetes service NodePort TCP
+  port: "{{ openshift_node_port_range | default('') }}/tcp"
+  cond: "{{ openshift_node_port_range is defined }}"
+- service: Kubernetes service NodePort UDP
+  port: "{{ openshift_node_port_range | default('') }}/udp"
+  cond: "{{ openshift_node_port_range is defined }}"
diff --git a/roles/openshift_node/meta/main.yml b/roles/openshift_node/meta/main.yml
index 4fb841add..06373de04 100644
--- a/roles/openshift_node/meta/main.yml
+++ b/roles/openshift_node/meta/main.yml
@@ -14,36 +14,11 @@ galaxy_info:
 dependencies:
 - role: openshift_node_facts
 - role: lib_openshift
+- role: lib_os_firewall
 - role: openshift_common
 - role: openshift_clock
 - role: openshift_docker
 - role: openshift_node_certificates
 - role: openshift_cloud_provider
-- role: os_firewall
-  os_firewall_allow:
-  - service: Kubernetes kubelet
-    port: 10250/tcp
-  - service: http
-    port: 80/tcp
-  - service: https
-    port: 443/tcp
-- role: os_firewall
-  os_firewall_allow:
-  - service: OpenShift OVS sdn
-    port: 4789/udp
-  when: openshift.common.use_openshift_sdn | default(true) | bool
-- role: os_firewall
-  os_firewall_allow:
-  - service: Calico BGP Port
-    port: 179/tcp
-  when: openshift.common.use_calico | bool
-
-- role: os_firewall
-  os_firewall_allow:
-  - service: Kubernetes service NodePort TCP
-    port: "{{ openshift_node_port_range | default('') }}/tcp"
-  - service: Kubernetes service NodePort UDP
-    port: "{{ openshift_node_port_range | default('') }}/udp"
-  when: openshift_node_port_range is defined
 - role: openshift_node_dnsmasq
   when: openshift.common.use_dnsmasq | bool
diff --git a/roles/openshift_node/tasks/firewall.yml b/roles/openshift_node/tasks/firewall.yml
new file mode 100644
index 000000000..323eaae70
--- /dev/null
+++ b/roles/openshift_node/tasks/firewall.yml
@@ -0,0 +1,40 @@
+---
+- when: os_firewall_enabled | bool and not os_firewall_use_firewalld | bool
+  block:
+  - name: Add iptables allow rules
+    os_firewall_manage_iptables:
+      name: "{{ item.service }}"
+      action: add
+      protocol: "{{ item.port.split('/')[1] }}"
+      port: "{{ item.port.split('/')[0] }}"
+    when: item.cond
+    with_items: "{{ r_openshift_node_os_firewall_allow }}"
+
+  - name: Remove iptables rules
+    os_firewall_manage_iptables:
+      name: "{{ item.service }}"
+      action: remove
+      protocol: "{{ item.port.split('/')[1] }}"
+      port: "{{ item.port.split('/')[0] }}"
+    when: item.cond
+    with_items: "{{ r_openshift_node_os_firewall_deny }}"
+
+- when: os_firewall_enabled | bool and os_firewall_use_firewalld | bool
+  block:
+  - name: Add firewalld allow rules
+    firewalld:
+      port: "{{ item.port }}"
+      permanent: true
+      immediate: true
+      state: enabled
+    when: item.cond
+    with_items: "{{ r_openshift_node_os_firewall_allow }}"
+
+  - name: Remove firewalld allow rules
+    firewalld:
+      port: "{{ item.port }}"
+      permanent: true
+      immediate: true
+      state: disabled
+    when: item.cond
+    with_items: "{{ r_openshift_node_os_firewall_deny }}"
diff --git a/roles/openshift_node/tasks/main.yml b/roles/openshift_node/tasks/main.yml
index ca4fef360..3353a22e3 100644
--- a/roles/openshift_node/tasks/main.yml
+++ b/roles/openshift_node/tasks/main.yml
@@ -6,6 +6,38 @@
     - (not ansible_selinux or ansible_selinux.status != 'enabled') and deployment_type in ['enterprise', 'online', 'atomic-enterprise', 'openshift-enterprise']
     - not openshift_docker_use_crio | default(false)
 
+- name: setup firewall
+  include: firewall.yml
+  static: yes
+
+- name: Set node facts
+  openshift_facts:
+    role: "{{ item.role }}"
+    local_facts: "{{ item.local_facts }}"
+  with_items:
+    # Reset node labels to an empty dictionary.
+    - role: node
+      local_facts:
+        labels: {}
+    - role: node
+      local_facts:
+        annotations: "{{ openshift_node_annotations | default(none) }}"
+        debug_level: "{{ openshift_node_debug_level | default(openshift.common.debug_level) }}"
+        iptables_sync_period: "{{ openshift_node_iptables_sync_period | default(None) }}"
+        kubelet_args: "{{ openshift_node_kubelet_args | default(None) }}"
+        labels: "{{ lookup('oo_option', 'openshift_node_labels') | default( openshift_node_labels | default(none), true) }}"
+        registry_url: "{{ oreg_url_node | default(oreg_url) | default(None) }}"
+        schedulable: "{{ openshift_schedulable | default(openshift_scheduleable) | default(None) }}"
+        sdn_mtu: "{{ openshift_node_sdn_mtu | default(None) }}"
+        storage_plugin_deps: "{{ osn_storage_plugin_deps | default(None) }}"
+        set_node_ip: "{{ openshift_set_node_ip | default(None) }}"
+        node_image: "{{ osn_image | default(None) }}"
+        ovs_image: "{{ osn_ovs_image | default(None) }}"
+        proxy_mode: "{{ openshift_node_proxy_mode | default('iptables') }}"
+        local_quota_per_fsgroup: "{{ openshift_node_local_quota_per_fsgroup | default(None) }}"
+        dns_ip: "{{ openshift_dns_ip | default(none) | get_dns_ip(hostvars[inventory_hostname])}}"
+        env_vars: "{{ openshift_node_env_vars | default(None) }}"
+
 # https://docs.openshift.com/container-platform/3.4/admin_guide/overcommit.html#disabling-swap-memory
 - name: Check for swap usage
   command: grep "^[^#].*swap" /etc/fstab
diff --git a/roles/openshift_storage_nfs/defaults/main.yml b/roles/openshift_storage_nfs/defaults/main.yml
index 7f3c054e7..f6c0a1108 100644
--- a/roles/openshift_storage_nfs/defaults/main.yml
+++ b/roles/openshift_storage_nfs/defaults/main.yml
@@ -1,4 +1,10 @@
 ---
+r_openshift_storage_nfs_os_firewall_deny: []
+r_openshift_storage_nfs_os_firewall_allow:
+- service: nfs
+  port: "2049/tcp"
+  cond: true
+
 openshift:
   hosted:
     registry:
diff --git a/roles/openshift_storage_nfs/meta/main.yml b/roles/openshift_storage_nfs/meta/main.yml
index 62e38bd8c..b360d0658 100644
--- a/roles/openshift_storage_nfs/meta/main.yml
+++ b/roles/openshift_storage_nfs/meta/main.yml
@@ -10,9 +10,6 @@ galaxy_info:
     versions:
     - 7
 dependencies:
-- role: os_firewall
-  os_firewall_allow:
-  - service: nfs
-    port: "2049/tcp"
+- role: lib_os_firewall
 - role: openshift_hosted_facts
 - role: openshift_repos
diff --git a/roles/openshift_storage_nfs/tasks/firewall.yml b/roles/openshift_storage_nfs/tasks/firewall.yml
new file mode 100644
index 000000000..224042d1e
--- /dev/null
+++ b/roles/openshift_storage_nfs/tasks/firewall.yml
@@ -0,0 +1,40 @@
+---
+- when: os_firewall_enabled | bool and not os_firewall_use_firewalld | bool
+  block:
+  - name: Add iptables allow rules
+    os_firewall_manage_iptables:
+      name: "{{ item.service }}"
+      action: add
+      protocol: "{{ item.port.split('/')[1] }}"
+      port: "{{ item.port.split('/')[0] }}"
+    when: item.cond
+    with_items: "{{ r_openshift_storage_nfs_os_firewall_allow }}"
+
+  - name: Remove iptables rules
+    os_firewall_manage_iptables:
+      name: "{{ item.service }}"
+      action: remove
+      protocol: "{{ item.port.split('/')[1] }}"
+      port: "{{ item.port.split('/')[0] }}"
+    when: item.cond
+    with_items: "{{ r_openshift_storage_nfs_os_firewall_deny }}"
+
+- when: os_firewall_enabled | bool and os_firewall_use_firewalld | bool
+  block:
+  - name: Add firewalld allow rules
+    firewalld:
+      port: "{{ item.port }}"
+      permanent: true
+      immediate: true
+      state: enabled
+    when: item.cond
+    with_items: "{{ r_openshift_storage_nfs_os_firewall_allow }}"
+
+  - name: Remove firewalld allow rules
+    firewalld:
+      port: "{{ item.port }}"
+      permanent: true
+      immediate: true
+      state: disabled
+    when: item.cond
+    with_items: "{{ r_openshift_storage_nfs_os_firewall_deny }}"
diff --git a/roles/openshift_storage_nfs/tasks/main.yml b/roles/openshift_storage_nfs/tasks/main.yml
index 019ada2fb..51f8f4e0e 100644
--- a/roles/openshift_storage_nfs/tasks/main.yml
+++ b/roles/openshift_storage_nfs/tasks/main.yml
@@ -1,4 +1,8 @@
 ---
+- name: setup firewall
+  include: firewall.yml
+  static: yes
+
 - name: Install nfs-utils
   package: name=nfs-utils state=present
 
diff --git a/roles/os_firewall/library/os_firewall_manage_iptables.py b/roles/os_firewall/library/os_firewall_manage_iptables.py
deleted file mode 100755
index aeee3ede8..000000000
--- a/roles/os_firewall/library/os_firewall_manage_iptables.py
+++ /dev/null
@@ -1,283 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-# pylint: disable=fixme, missing-docstring
-import subprocess
-
-DOCUMENTATION = '''
----
-module: os_firewall_manage_iptables
-short_description: This module manages iptables rules for a given chain
-author: Jason DeTiberus
-requirements: [ ]
-'''
-EXAMPLES = '''
-'''
-
-
-class IpTablesError(Exception):
-    def __init__(self, msg, cmd, exit_code, output):
-        super(IpTablesError, self).__init__(msg)
-        self.msg = msg
-        self.cmd = cmd
-        self.exit_code = exit_code
-        self.output = output
-
-
-class IpTablesAddRuleError(IpTablesError):
-    pass
-
-
-class IpTablesRemoveRuleError(IpTablesError):
-    def __init__(self, chain, msg, cmd, exit_code, output):  # pylint: disable=too-many-arguments, line-too-long, redefined-outer-name
-        super(IpTablesRemoveRuleError, self).__init__(msg, cmd, exit_code,
-                                                      output)
-        self.chain = chain
-
-
-class IpTablesSaveError(IpTablesError):
-    pass
-
-
-class IpTablesCreateChainError(IpTablesError):
-    def __init__(self, chain, msg, cmd, exit_code, output):  # pylint: disable=too-many-arguments, line-too-long, redefined-outer-name
-        super(IpTablesCreateChainError, self).__init__(msg, cmd, exit_code,
-                                                       output)
-        self.chain = chain
-
-
-class IpTablesCreateJumpRuleError(IpTablesError):
-    def __init__(self, chain, msg, cmd, exit_code, output):  # pylint: disable=too-many-arguments, line-too-long, redefined-outer-name
-        super(IpTablesCreateJumpRuleError, self).__init__(msg, cmd, exit_code,
-                                                          output)
-        self.chain = chain
-
-
-# TODO: implement rollbacks for any events that were successful and an
-# exception was thrown later. For example, when the chain is created
-# successfully, but the add/remove rule fails.
-class IpTablesManager(object):  # pylint: disable=too-many-instance-attributes
-    def __init__(self, module):
-        self.module = module
-        self.ip_version = module.params['ip_version']
-        self.check_mode = module.check_mode
-        self.chain = module.params['chain']
-        self.create_jump_rule = module.params['create_jump_rule']
-        self.jump_rule_chain = module.params['jump_rule_chain']
-        self.cmd = self.gen_cmd()
-        self.save_cmd = self.gen_save_cmd()
-        self.output = []
-        self.changed = False
-
-    def save(self):
-        try:
-            self.output.append(subprocess.check_output(self.save_cmd, stderr=subprocess.STDOUT))
-        except subprocess.CalledProcessError as ex:
-            raise IpTablesSaveError(
-                msg="Failed to save iptables rules",
-                cmd=ex.cmd, exit_code=ex.returncode, output=ex.output)
-
-    def verify_chain(self):
-        if not self.chain_exists():
-            self.create_chain()
-        if self.create_jump_rule and not self.jump_rule_exists():
-            self.create_jump()
-
-    def add_rule(self, port, proto):
-        rule = self.gen_rule(port, proto)
-        if not self.rule_exists(rule):
-            self.verify_chain()
-
-            if self.check_mode:
-                self.changed = True
-                self.output.append("Create rule for %s %s" % (proto, port))
-            else:
-                cmd = self.cmd + ['-A'] + rule
-                try:
-                    self.output.append(subprocess.check_output(cmd))
-                    self.changed = True
-                    self.save()
-                except subprocess.CalledProcessError as ex:
-                    raise IpTablesCreateChainError(
-                        chain=self.chain,
-                        msg="Failed to create rule for "
-                            "%s %s" % (proto, port),
-                        cmd=ex.cmd, exit_code=ex.returncode,
-                        output=ex.output)
-
-    def remove_rule(self, port, proto):
-        rule = self.gen_rule(port, proto)
-        if self.rule_exists(rule):
-            if self.check_mode:
-                self.changed = True
-                self.output.append("Remove rule for %s %s" % (proto, port))
-            else:
-                cmd = self.cmd + ['-D'] + rule
-                try:
-                    self.output.append(subprocess.check_output(cmd))
-                    self.changed = True
-                    self.save()
-                except subprocess.CalledProcessError as ex:
-                    raise IpTablesRemoveRuleError(
-                        chain=self.chain,
-                        msg="Failed to remove rule for %s %s" % (proto, port),
-                        cmd=ex.cmd, exit_code=ex.returncode, output=ex.output)
-
-    def rule_exists(self, rule):
-        check_cmd = self.cmd + ['-C'] + rule
-        return True if subprocess.call(check_cmd) == 0 else False
-
-    @staticmethod
-    def port_as_argument(port):
-        if isinstance(port, int):
-            return str(port)
-        if isinstance(port, basestring):  # noqa: F405
-            return port.replace('-', ":")
-        return port
-
-    def gen_rule(self, port, proto):
-        return [self.chain, '-p', proto, '-m', 'state', '--state', 'NEW',
-                '-m', proto, '--dport', IpTablesManager.port_as_argument(port), '-j', 'ACCEPT']
-
-    def create_jump(self):
-        if self.check_mode:
-            self.changed = True
-            self.output.append("Create jump rule for chain %s" % self.chain)
-        else:
-            try:
-                cmd = self.cmd + ['-L', self.jump_rule_chain, '--line-numbers']
-                output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
-
-                # break the input rules into rows and columns
-                input_rules = [s.split() for s in to_native(output).split('\n')]
-
-                # Find the last numbered rule
-                last_rule_num = None
-                last_rule_target = None
-                for rule in input_rules[:-1]:
-                    if rule:
-                        try:
-                            last_rule_num = int(rule[0])
-                        except ValueError:
-                            continue
-                        last_rule_target = rule[1]
-
-                # Naively assume that if the last row is a REJECT or DROP rule,
-                # then we can insert our rule right before it, otherwise we
-                # assume that we can just append the rule.
-                if (last_rule_num and last_rule_target and last_rule_target in ['REJECT', 'DROP']):
-                    # insert rule
-                    cmd = self.cmd + ['-I', self.jump_rule_chain,
-                                      str(last_rule_num)]
-                else:
-                    # append rule
-                    cmd = self.cmd + ['-A', self.jump_rule_chain]
-                cmd += ['-j', self.chain]
-                output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
-                self.changed = True
-                self.output.append(output)
-                self.save()
-            except subprocess.CalledProcessError as ex:
-                if '--line-numbers' in ex.cmd:
-                    raise IpTablesCreateJumpRuleError(
-                        chain=self.chain,
-                        msg=("Failed to query existing " +
-                             self.jump_rule_chain +
-                             " rules to determine jump rule location"),
-                        cmd=ex.cmd, exit_code=ex.returncode,
-                        output=ex.output)
-                else:
-                    raise IpTablesCreateJumpRuleError(
-                        chain=self.chain,
-                        msg=("Failed to create jump rule for chain " +
-                             self.chain),
-                        cmd=ex.cmd, exit_code=ex.returncode,
-                        output=ex.output)
-
-    def create_chain(self):
-        if self.check_mode:
-            self.changed = True
-            self.output.append("Create chain %s" % self.chain)
-        else:
-            try:
-                cmd = self.cmd + ['-N', self.chain]
-                self.output.append(subprocess.check_output(cmd, stderr=subprocess.STDOUT))
-                self.changed = True
-                self.output.append("Successfully created chain %s" %
-                                   self.chain)
-                self.save()
-            except subprocess.CalledProcessError as ex:
-                raise IpTablesCreateChainError(
-                    chain=self.chain,
-                    msg="Failed to create chain: %s" % self.chain,
-                    cmd=ex.cmd, exit_code=ex.returncode, output=ex.output
-                )
-
-    def jump_rule_exists(self):
-        cmd = self.cmd + ['-C', self.jump_rule_chain, '-j', self.chain]
-        return True if subprocess.call(cmd) == 0 else False
-
-    def chain_exists(self):
-        cmd = self.cmd + ['-L', self.chain]
-        return True if subprocess.call(cmd) == 0 else False
-
-    def gen_cmd(self):
-        cmd = 'iptables' if self.ip_version == 'ipv4' else 'ip6tables'
-        # Include -w (wait for xtables lock) in default arguments.
-        default_args = ['-w']
-        return ["/usr/sbin/%s" % cmd] + default_args
-
-    def gen_save_cmd(self):  # pylint: disable=no-self-use
-        return ['/usr/libexec/iptables/iptables.init', 'save']
-
-
-def main():
-    module = AnsibleModule(  # noqa: F405
-        argument_spec=dict(
-            name=dict(required=True),
-            action=dict(required=True, choices=['add', 'remove',
-                                                'verify_chain']),
-            chain=dict(required=False, default='OS_FIREWALL_ALLOW'),
-            create_jump_rule=dict(required=False, type='bool', default=True),
-            jump_rule_chain=dict(required=False, default='INPUT'),
-            protocol=dict(required=False, choices=['tcp', 'udp']),
-            port=dict(required=False, type='str'),
-            ip_version=dict(required=False, default='ipv4',
-                            choices=['ipv4', 'ipv6']),
-        ),
-        supports_check_mode=True
-    )
-
-    action = module.params['action']
-    protocol = module.params['protocol']
-    port = module.params['port']
-
-    if action in ['add', 'remove']:
-        if not protocol:
-            error = "protocol is required when action is %s" % action
-            module.fail_json(msg=error)
-        if not port:
-            error = "port is required when action is %s" % action
-            module.fail_json(msg=error)
-
-    iptables_manager = IpTablesManager(module)
-
-    try:
-        if action == 'add':
-            iptables_manager.add_rule(port, protocol)
-        elif action == 'remove':
-            iptables_manager.remove_rule(port, protocol)
-        elif action == 'verify_chain':
-            iptables_manager.verify_chain()
-    except IpTablesError as ex:
-        module.fail_json(msg=ex.msg)
-
-    return module.exit_json(changed=iptables_manager.changed,
-                            output=iptables_manager.output)
-
-
-# pylint: disable=redefined-builtin, unused-wildcard-import, wildcard-import,  wrong-import-position
-# import module snippets
-from ansible.module_utils.basic import *  # noqa: F403,E402
-from ansible.module_utils._text import to_native  # noqa: E402
-if __name__ == '__main__':
-    main()
diff --git a/roles/os_firewall/tasks/firewall/firewalld.yml b/roles/os_firewall/tasks/firewall/firewalld.yml
index 509655b0c..2cc7af478 100644
--- a/roles/os_firewall/tasks/firewall/firewalld.yml
+++ b/roles/os_firewall/tasks/firewall/firewalld.yml
@@ -49,19 +49,3 @@
   until: pkaction.rc == 0
   retries: 6
   delay: 10
-
-- name: Add firewalld allow rules
-  firewalld:
-    port: "{{ item.port }}"
-    permanent: true
-    immediate: true
-    state: enabled
-  with_items: "{{ os_firewall_allow }}"
-
-- name: Remove firewalld allow rules
-  firewalld:
-    port: "{{ item.port }}"
-    permanent: true
-    immediate: true
-    state: disabled
-  with_items: "{{ os_firewall_deny }}"
diff --git a/roles/os_firewall/tasks/firewall/iptables.yml b/roles/os_firewall/tasks/firewall/iptables.yml
index 55f2fc471..ccb3c4713 100644
--- a/roles/os_firewall/tasks/firewall/iptables.yml
+++ b/roles/os_firewall/tasks/firewall/iptables.yml
@@ -33,19 +33,3 @@
 - name: need to pause here, otherwise the iptables service starting can sometimes cause ssh to fail
   pause: seconds=10
   when: result | changed
-
-- name: Add iptables allow rules
-  os_firewall_manage_iptables:
-    name: "{{ item.service }}"
-    action: add
-    protocol: "{{ item.port.split('/')[1] }}"
-    port: "{{ item.port.split('/')[0] }}"
-  with_items: "{{ os_firewall_allow }}"
-
-- name: Remove iptables rules
-  os_firewall_manage_iptables:
-    name: "{{ item.service }}"
-    action: remove
-    protocol: "{{ item.port.split('/')[1] }}"
-    port: "{{ item.port.split('/')[0] }}"
-  with_items: "{{ os_firewall_deny }}"
-- 
cgit v1.2.3