import os_firewall role from openshift-ansible

This commit is contained in:
Adam Miller 2015-08-05 16:35:47 +00:00
parent 91b1a135ae
commit 9c028cb242
8 changed files with 506 additions and 0 deletions

View file

@ -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
) )

View 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

View file

@ -0,0 +1,2 @@
---
os_firewall_use_firewalld: True

View 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()

View 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: []

View 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

View 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

View file

@ -0,0 +1,6 @@
---
- include: firewall/firewalld.yml
when: os_firewall_use_firewalld
- include: firewall/iptables.yml
when: not os_firewall_use_firewalld