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
|
openshift_repos
|
||||||
os_env_extras
|
os_env_extras
|
||||||
os_env_extras_node
|
os_env_extras_node
|
||||||
|
os_firewall
|
||||||
pods
|
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