import os_firewall role from openshift-ansible
This commit is contained in:
parent
91b1a135ae
commit
9c028cb242
8 changed files with 506 additions and 0 deletions
|
@ -32,6 +32,7 @@ To re-import/update the OpenShift Ansible roles:
|
|||
openshift_repos
|
||||
os_env_extras
|
||||
os_env_extras_node
|
||||
os_firewall
|
||||
pods
|
||||
)
|
||||
|
||||
|
|
66
roles/os_firewall/README.md
Normal file
66
roles/os_firewall/README.md
Normal file
|
@ -0,0 +1,66 @@
|
|||
OS Firewall
|
||||
===========
|
||||
|
||||
OS Firewall manages firewalld and iptables firewall settings for a minimal use
|
||||
case (Adding/Removing rules based on protocol and port number).
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
None.
|
||||
|
||||
Role Variables
|
||||
--------------
|
||||
|
||||
| Name | Default | |
|
||||
|---------------------------|---------|----------------------------------------|
|
||||
| os_firewall_use_firewalld | True | If false, use iptables |
|
||||
| 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
|
||||
roles:
|
||||
- os_firewall
|
||||
```
|
||||
|
||||
Use firewalld and open tcp port 443 and close previously open tcp port 80:
|
||||
```
|
||||
---
|
||||
- hosts: servers
|
||||
vars:
|
||||
os_firewall_allow:
|
||||
- service: https
|
||||
port: 443/tcp
|
||||
os_firewall_deny:
|
||||
- service: httpd
|
||||
port: 80/tcp
|
||||
roles:
|
||||
- os_firewall
|
||||
```
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
Apache License, Version 2.0
|
||||
|
||||
Author Information
|
||||
------------------
|
||||
Jason DeTiberus - jdetiber@redhat.com
|
2
roles/os_firewall/defaults/main.yml
Normal file
2
roles/os_firewall/defaults/main.yml
Normal file
|
@ -0,0 +1,2 @@
|
|||
---
|
||||
os_firewall_use_firewalld: True
|
273
roles/os_firewall/library/os_firewall_manage_iptables.py
Executable file
273
roles/os_firewall/library/os_firewall_manage_iptables.py
Executable file
|
@ -0,0 +1,273 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: expandtab:tabstop=4:shiftwidth=4
|
||||
# pylint: disable=fixme, missing-docstring
|
||||
from subprocess import call, check_output
|
||||
|
||||
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):
|
||||
pass
|
||||
|
||||
|
||||
class IpTablesSaveError(IpTablesError):
|
||||
pass
|
||||
|
||||
|
||||
class IpTablesCreateChainError(IpTablesError):
|
||||
def __init__(self, chain, msg, cmd, exit_code, output): # pylint: disable=too-many-arguments, line-too-long
|
||||
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
|
||||
super(IpTablesCreateJumpRuleError, self).__init__(msg, cmd, exit_code,
|
||||
output)
|
||||
self.chain = chain
|
||||
|
||||
|
||||
# TODO: impliment rollbacks for any events that where 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(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(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(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 call(check_cmd) == 0 else False
|
||||
|
||||
def gen_rule(self, port, proto):
|
||||
return [self.chain, '-p', proto, '-m', 'state', '--state', 'NEW',
|
||||
'-m', proto, '--dport', str(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 = check_output(cmd, stderr=subprocess.STDOUT)
|
||||
|
||||
# break the input rules into rows and columns
|
||||
input_rules = [s.split() for s in 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 rule, then
|
||||
# we can add 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 == 'REJECT'):
|
||||
# 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 = 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(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 call(cmd) == 0 else False
|
||||
|
||||
def chain_exists(self):
|
||||
cmd = self.cmd + ['-L', self.chain]
|
||||
return True if call(cmd) == 0 else False
|
||||
|
||||
def gen_cmd(self):
|
||||
cmd = 'iptables' if self.ip_version == 'ipv4' else 'ip6tables'
|
||||
return ["/usr/sbin/%s" % cmd]
|
||||
|
||||
def gen_save_cmd(self): # pylint: disable=no-self-use
|
||||
return ['/usr/libexec/iptables/iptables.init', 'save']
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
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='int'),
|
||||
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
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
if __name__ == '__main__':
|
||||
main()
|
14
roles/os_firewall/meta/main.yml
Normal file
14
roles/os_firewall/meta/main.yml
Normal file
|
@ -0,0 +1,14 @@
|
|||
---
|
||||
galaxy_info:
|
||||
author: Jason DeTiberus
|
||||
description: os_firewall
|
||||
company: Red Hat, Inc.
|
||||
license: Apache License, Version 2.0
|
||||
min_ansible_version: 1.7
|
||||
platforms:
|
||||
- name: EL
|
||||
versions:
|
||||
- 7
|
||||
categories:
|
||||
- system
|
||||
dependencies: []
|
81
roles/os_firewall/tasks/firewall/firewalld.yml
Normal file
81
roles/os_firewall/tasks/firewall/firewalld.yml
Normal file
|
@ -0,0 +1,81 @@
|
|||
---
|
||||
- name: Install firewalld packages
|
||||
yum:
|
||||
name: firewalld
|
||||
state: present
|
||||
register: install_result
|
||||
|
||||
- name: Check if iptables-services is installed
|
||||
command: rpm -q iptables-services
|
||||
register: pkg_check
|
||||
failed_when: pkg_check.rc > 1
|
||||
changed_when: no
|
||||
|
||||
- name: Ensure iptables services are not enabled
|
||||
service:
|
||||
name: "{{ item }}"
|
||||
state: stopped
|
||||
enabled: no
|
||||
with_items:
|
||||
- iptables
|
||||
- ip6tables
|
||||
when: pkg_check.rc == 0
|
||||
|
||||
- name: Reload systemd units
|
||||
command: systemctl daemon-reload
|
||||
when: install_result | changed
|
||||
|
||||
- name: Start and enable firewalld service
|
||||
service:
|
||||
name: firewalld
|
||||
state: started
|
||||
enabled: yes
|
||||
register: result
|
||||
|
||||
- name: need to pause here, otherwise the firewalld service starting can sometimes cause ssh to fail
|
||||
pause: seconds=10
|
||||
when: result | changed
|
||||
|
||||
- name: Mask iptables services
|
||||
command: systemctl mask "{{ item }}"
|
||||
register: result
|
||||
changed_when: "'iptables' in result.stdout"
|
||||
with_items:
|
||||
- iptables
|
||||
- ip6tables
|
||||
when: pkg_check.rc == 0
|
||||
ignore_errors: yes
|
||||
|
||||
# TODO: Ansible 1.9 will eliminate the need for separate firewalld tasks for
|
||||
# enabling rules and making them permanent with the immediate flag
|
||||
- name: Add firewalld allow rules
|
||||
firewalld:
|
||||
port: "{{ item.port }}"
|
||||
permanent: false
|
||||
state: enabled
|
||||
with_items: os_firewall_allow
|
||||
when: os_firewall_allow is defined
|
||||
|
||||
- name: Persist firewalld allow rules
|
||||
firewalld:
|
||||
port: "{{ item.port }}"
|
||||
permanent: true
|
||||
state: enabled
|
||||
with_items: os_firewall_allow
|
||||
when: os_firewall_allow is defined
|
||||
|
||||
- name: Remove firewalld allow rules
|
||||
firewalld:
|
||||
port: "{{ item.port }}"
|
||||
permanent: false
|
||||
state: disabled
|
||||
with_items: os_firewall_deny
|
||||
when: os_firewall_deny is defined
|
||||
|
||||
- name: Persist removal of firewalld allow rules
|
||||
firewalld:
|
||||
port: "{{ item.port }}"
|
||||
permanent: true
|
||||
state: disabled
|
||||
with_items: os_firewall_deny
|
||||
when: os_firewall_deny is defined
|
63
roles/os_firewall/tasks/firewall/iptables.yml
Normal file
63
roles/os_firewall/tasks/firewall/iptables.yml
Normal file
|
@ -0,0 +1,63 @@
|
|||
---
|
||||
- name: Install iptables packages
|
||||
yum:
|
||||
name: "{{ item }}"
|
||||
state: present
|
||||
with_items:
|
||||
- iptables
|
||||
- iptables-services
|
||||
register: install_result
|
||||
|
||||
- name: Check if firewalld is installed
|
||||
command: rpm -q firewalld
|
||||
register: pkg_check
|
||||
failed_when: pkg_check.rc > 1
|
||||
changed_when: no
|
||||
|
||||
- name: Ensure firewalld service is not enabled
|
||||
service:
|
||||
name: firewalld
|
||||
state: stopped
|
||||
enabled: no
|
||||
when: pkg_check.rc == 0
|
||||
|
||||
- name: Reload systemd units
|
||||
command: systemctl daemon-reload
|
||||
when: install_result | changed
|
||||
|
||||
- name: Start and enable iptables service
|
||||
service:
|
||||
name: iptables
|
||||
state: started
|
||||
enabled: yes
|
||||
register: result
|
||||
|
||||
- name: need to pause here, otherwise the iptables service starting can sometimes cause ssh to fail
|
||||
pause: seconds=10
|
||||
when: result | changed
|
||||
|
||||
# TODO: submit PR upstream to add mask/unmask to service module
|
||||
- name: Mask firewalld service
|
||||
command: systemctl mask firewalld
|
||||
register: result
|
||||
changed_when: "'firewalld' in result.stdout"
|
||||
when: pkg_check.rc == 0
|
||||
ignore_errors: yes
|
||||
|
||||
- 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
|
||||
when: os_firewall_allow is defined
|
||||
|
||||
- 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
|
||||
when: os_firewall_deny is defined
|
6
roles/os_firewall/tasks/main.yml
Normal file
6
roles/os_firewall/tasks/main.yml
Normal file
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
- include: firewall/firewalld.yml
|
||||
when: os_firewall_use_firewalld
|
||||
|
||||
- include: firewall/iptables.yml
|
||||
when: not os_firewall_use_firewalld
|
Loading…
Add table
Add a link
Reference in a new issue