Enable changed var with ufw check mode (#49948)
* Enable 'changed' var with ufw check mode * Fix from comment of the PR + Unit Test * Fix on ufw module after the second review - delete rules change works in check mode - simplify execute def & use it on every call process - improved regexp - rename vars defaults to current_default_values * Add ignore error to execute() and use it in get_current_rules() * Update after third code review (introduce change in changed status) * Adjust tests and fix some problems (#1) * 'active' also appears in 'inactive'. * 'reject' is also a valid option here. * For example for reloaded, changed will be set back to False here. * Improve and adjust tests. * Fix after merging integration test * handle "disabled" on default routed * Add /var/lib/ufw/.. rules files * add unit test * Fix pep8 formatting error * Separate ipv6 and ipv4 rules process from checkmode * fix non-ascii error on ci * Some change after review * Add unit test with sub network mask * rename is_match function by is_starting * add changelog fragment
This commit is contained in:
parent
708f0b07ba
commit
b99de25f32
5 changed files with 498 additions and 41 deletions
|
@ -0,0 +1,2 @@
|
|||
minor_changes:
|
||||
- ufw - enable "changed" status while check mode is enabled
|
|
@ -207,11 +207,37 @@ EXAMPLES = '''
|
|||
'''
|
||||
|
||||
import re
|
||||
|
||||
from operator import itemgetter
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
|
||||
def compile_ipv4_regexp():
|
||||
r = r"((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}"
|
||||
r += r"(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])"
|
||||
return re.compile(r)
|
||||
|
||||
|
||||
def compile_ipv6_regexp():
|
||||
"""
|
||||
validation pattern provided by :
|
||||
https://stackoverflow.com/questions/53497/regular-expression-that-matches-
|
||||
valid-ipv6-addresses#answer-17871737
|
||||
"""
|
||||
r = r"(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:"
|
||||
r += r"|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}"
|
||||
r += r"(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4})"
|
||||
r += r"{1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]"
|
||||
r += r"{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]"
|
||||
r += r"{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4})"
|
||||
r += r"{0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]"
|
||||
r += r"|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}"
|
||||
r += r"[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}"
|
||||
r += r"[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))"
|
||||
return re.compile(r)
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
|
@ -241,24 +267,62 @@ def main():
|
|||
|
||||
cmds = []
|
||||
|
||||
def execute(cmd):
|
||||
ipv4_regexp = compile_ipv4_regexp()
|
||||
ipv6_regexp = compile_ipv6_regexp()
|
||||
|
||||
def filter_line_that_not_start_with(pattern, content):
|
||||
return ''.join([line for line in content.splitlines(True) if line.startswith(pattern)])
|
||||
|
||||
def filter_line_that_contains(pattern, content):
|
||||
return [line for line in content.splitlines(True) if pattern in line]
|
||||
|
||||
def filter_line_that_not_contains(pattern, content):
|
||||
return ''.join([line for line in content.splitlines(True) if not line.contains(pattern)])
|
||||
|
||||
def filter_line_that_match_func(match_func, content):
|
||||
return ''.join([line for line in content.splitlines(True) if match_func(line) is not None])
|
||||
|
||||
def filter_line_that_contains_ipv4(content):
|
||||
return filter_line_that_match_func(ipv4_regexp.search, content)
|
||||
|
||||
def filter_line_that_contains_ipv6(content):
|
||||
return filter_line_that_match_func(ipv6_regexp.search, content)
|
||||
|
||||
def is_starting_by_ipv4(ip):
|
||||
return ipv4_regexp.match(ip) is not None
|
||||
|
||||
def is_starting_by_ipv6(ip):
|
||||
return ipv6_regexp.match(ip) is not None
|
||||
|
||||
def execute(cmd, ignore_error=False):
|
||||
cmd = ' '.join(map(itemgetter(-1), filter(itemgetter(0), cmd)))
|
||||
|
||||
cmds.append(cmd)
|
||||
(rc, out, err) = module.run_command(cmd)
|
||||
(rc, out, err) = module.run_command(cmd, environ_update={"LANG": "C"})
|
||||
|
||||
if rc != 0:
|
||||
module.fail_json(msg=err or out)
|
||||
if rc != 0 and not ignore_error:
|
||||
module.fail_json(msg=err or out, commands=cmds)
|
||||
|
||||
return out
|
||||
|
||||
def get_current_rules():
|
||||
user_rules_files = ["/lib/ufw/user.rules",
|
||||
"/lib/ufw/user6.rules",
|
||||
"/etc/ufw/user.rules",
|
||||
"/etc/ufw/user6.rules",
|
||||
"/var/lib/ufw/user.rules",
|
||||
"/var/lib/ufw/user6.rules"]
|
||||
|
||||
cmd = [[grep_bin], ["-h"], ["'^### tuple'"]]
|
||||
|
||||
cmd.extend([[f] for f in user_rules_files])
|
||||
return execute(cmd, ignore_error=True)
|
||||
|
||||
def ufw_version():
|
||||
"""
|
||||
Returns the major and minor version of ufw installed on the system.
|
||||
"""
|
||||
rc, out, err = module.run_command("%s --version" % ufw_bin)
|
||||
if rc != 0:
|
||||
module.fail_json(
|
||||
msg="Failed to get ufw version.", rc=rc, out=out, err=err
|
||||
)
|
||||
out = execute([[ufw_bin], ["--version"]])
|
||||
|
||||
lines = [x for x in out.split('\n') if x.strip() != '']
|
||||
if len(lines) == 0:
|
||||
|
@ -291,25 +355,65 @@ def main():
|
|||
|
||||
# Ensure ufw is available
|
||||
ufw_bin = module.get_bin_path('ufw', True)
|
||||
grep_bin = module.get_bin_path('grep', True)
|
||||
|
||||
# Save the pre state and rules in order to recognize changes
|
||||
(_, pre_state, _) = module.run_command(ufw_bin + ' status verbose')
|
||||
(_, pre_rules, _) = module.run_command("grep '^### tuple' /lib/ufw/user.rules /lib/ufw/user6.rules /etc/ufw/user.rules /etc/ufw/user6.rules")
|
||||
pre_state = execute([[ufw_bin], ['status verbose']])
|
||||
pre_rules = get_current_rules()
|
||||
|
||||
# Execute commands
|
||||
changed = False
|
||||
|
||||
# Execute filter
|
||||
for (command, value) in commands.items():
|
||||
|
||||
cmd = [[ufw_bin], [module.check_mode, '--dry-run']]
|
||||
|
||||
if command == 'state':
|
||||
states = {'enabled': 'enable', 'disabled': 'disable',
|
||||
'reloaded': 'reload', 'reset': 'reset'}
|
||||
execute(cmd + [['-f'], [states[value]]])
|
||||
|
||||
if value in ['reloaded', 'reset']:
|
||||
changed = True
|
||||
|
||||
if module.check_mode:
|
||||
# "active" would also match "inactive", hence the space
|
||||
ufw_enabled = pre_state.find(" active") != -1
|
||||
if (value == 'disabled' and ufw_enabled) or (value == 'enabled' and not ufw_enabled):
|
||||
changed = True
|
||||
else:
|
||||
execute(cmd + [['-f'], [states[value]]])
|
||||
|
||||
elif command == 'logging':
|
||||
execute(cmd + [[command], [value]])
|
||||
extract = re.search(r'Logging: (on|off) \(([a-z]+)\)', pre_state)
|
||||
if extract:
|
||||
current_level = extract.group(2)
|
||||
current_on_off_value = extract.group(1)
|
||||
if value != "off":
|
||||
if value != "on" and (value != current_level or current_on_off_value == "off"):
|
||||
changed = True
|
||||
elif current_on_off_value != "off":
|
||||
changed = True
|
||||
else:
|
||||
changed = True
|
||||
|
||||
if not module.check_mode:
|
||||
execute(cmd + [[command], [value]])
|
||||
|
||||
elif command == 'default':
|
||||
execute(cmd + [[command], [value], [params['direction']]])
|
||||
if module.check_mode:
|
||||
regexp = r'Default: (deny|allow|reject) \(incoming\), (deny|allow|reject) \(outgoing\), (deny|allow|reject|disabled) \(routed\)'
|
||||
extract = re.search(regexp, pre_state)
|
||||
if extract is not None:
|
||||
current_default_values = {}
|
||||
current_default_values["incoming"] = extract.group(1)
|
||||
current_default_values["outgoing"] = extract.group(2)
|
||||
current_default_values["routed"] = extract.group(3)
|
||||
if current_default_values[params['direction']] != value:
|
||||
changed = True
|
||||
else:
|
||||
changed = True
|
||||
else:
|
||||
execute(cmd + [[command], [value], [params['direction']]])
|
||||
|
||||
elif command == 'rule':
|
||||
# Rules are constructed according to the long format
|
||||
|
@ -336,14 +440,34 @@ def main():
|
|||
if (ufw_major == 0 and ufw_minor >= 35) or ufw_major > 0:
|
||||
cmd.append([params['comment'], "comment '%s'" % params['comment']])
|
||||
|
||||
execute(cmd)
|
||||
rules_dry = execute(cmd)
|
||||
|
||||
if module.check_mode:
|
||||
|
||||
nb_skipping_line = len(filter_line_that_contains("Skipping", rules_dry))
|
||||
|
||||
if not (nb_skipping_line > 0 and nb_skipping_line == len(rules_dry.splitlines(True))):
|
||||
|
||||
rules_dry = filter_line_that_not_start_with("### tuple", rules_dry)
|
||||
# ufw dry-run doesn't send all rules so have to compare ipv4 or ipv6 rules
|
||||
if is_starting_by_ipv4(params['from_ip']) or is_starting_by_ipv4(params['to_ip']):
|
||||
if filter_line_that_contains_ipv4(pre_rules) != filter_line_that_contains_ipv4(rules_dry):
|
||||
changed = True
|
||||
elif is_starting_by_ipv6(params['from_ip']) or is_starting_by_ipv6(params['to_ip']):
|
||||
if filter_line_that_contains_ipv6(pre_rules) != filter_line_that_contains_ipv6(rules_dry):
|
||||
changed = True
|
||||
elif pre_rules != rules_dry:
|
||||
changed = True
|
||||
|
||||
# Get the new state
|
||||
(_, post_state, _) = module.run_command(ufw_bin + ' status verbose')
|
||||
(_, post_rules, _) = module.run_command("grep '^### tuple' /lib/ufw/user.rules /lib/ufw/user6.rules /etc/ufw/user.rules /etc/ufw/user6.rules")
|
||||
changed = (pre_state != post_state) or (pre_rules != post_rules)
|
||||
|
||||
return module.exit_json(changed=changed, commands=cmds, msg=post_state.rstrip())
|
||||
if module.check_mode:
|
||||
return module.exit_json(changed=changed, commands=cmds)
|
||||
else:
|
||||
post_state = execute([[ufw_bin], ['status'], ['verbose']])
|
||||
if not changed:
|
||||
post_rules = get_current_rules()
|
||||
changed = (pre_state != post_state) or (pre_rules != post_rules)
|
||||
return module.exit_json(changed=changed, commands=cmds, msg=post_state.rstrip())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
---
|
||||
# ############################################
|
||||
- name: Make sure it is off
|
||||
ufw:
|
||||
state: disabled
|
||||
- name: Enable (check mode)
|
||||
ufw:
|
||||
state: enabled
|
||||
|
@ -20,7 +23,7 @@
|
|||
register: enable_idem_check
|
||||
- assert:
|
||||
that:
|
||||
# FIXME - enable_check is changed
|
||||
- enable_check is changed
|
||||
- enable is changed
|
||||
- enable_idem is not changed
|
||||
- enable_idem_check is not changed
|
||||
|
@ -54,7 +57,7 @@
|
|||
register: ipv4_allow_idem_check
|
||||
- assert:
|
||||
that:
|
||||
# FIXME - ipv4_allow_check is changed
|
||||
- ipv4_allow_check is changed
|
||||
- ipv4_allow is changed
|
||||
- ipv4_allow_idem is not changed
|
||||
- ipv4_allow_idem_check is not changed
|
||||
|
@ -92,7 +95,7 @@
|
|||
register: delete_ipv4_allow_idem_check
|
||||
- assert:
|
||||
that:
|
||||
# FIXME - delete_ipv4_allow_check is changed
|
||||
- delete_ipv4_allow_check is changed
|
||||
- delete_ipv4_allow is changed
|
||||
- delete_ipv4_allow_idem is not changed
|
||||
- delete_ipv4_allow_idem_check is not changed
|
||||
|
@ -126,7 +129,7 @@
|
|||
register: ipv6_allow_idem_check
|
||||
- assert:
|
||||
that:
|
||||
# FIXME - ipv6_allow_check is changed
|
||||
- ipv6_allow_check is changed
|
||||
- ipv6_allow is changed
|
||||
- ipv6_allow_idem is not changed
|
||||
- ipv6_allow_idem_check is not changed
|
||||
|
@ -164,7 +167,7 @@
|
|||
register: delete_ipv6_allow_idem_check
|
||||
- assert:
|
||||
that:
|
||||
# FIXME - delete_ipv6_allow_check is changed
|
||||
- delete_ipv6_allow_check is changed
|
||||
- delete_ipv6_allow is changed
|
||||
- delete_ipv6_allow_idem is not changed
|
||||
- delete_ipv6_allow_idem_check is not changed
|
||||
|
@ -199,7 +202,7 @@
|
|||
register: ipv4_allow_idem_check
|
||||
- assert:
|
||||
that:
|
||||
# FIXME - ipv4_allow_check is changed
|
||||
- ipv4_allow_check is changed
|
||||
- ipv4_allow is changed
|
||||
- ipv4_allow_idem is not changed
|
||||
- ipv4_allow_idem_check is not changed
|
||||
|
@ -237,7 +240,7 @@
|
|||
register: delete_ipv4_allow_idem_check
|
||||
- assert:
|
||||
that:
|
||||
# FIXME - delete_ipv4_allow_check is changed
|
||||
- delete_ipv4_allow_check is changed
|
||||
- delete_ipv4_allow is changed
|
||||
- delete_ipv4_allow_idem is not changed
|
||||
- delete_ipv4_allow_idem_check is not changed
|
||||
|
@ -271,7 +274,7 @@
|
|||
register: ipv6_allow_idem_check
|
||||
- assert:
|
||||
that:
|
||||
# FIXME - ipv6_allow is_check changed
|
||||
- ipv6_allow_check is changed
|
||||
- ipv6_allow is changed
|
||||
- ipv6_allow_idem is not changed
|
||||
- ipv6_allow_idem_check is not changed
|
||||
|
@ -309,7 +312,7 @@
|
|||
register: delete_ipv6_allow_idem_check
|
||||
- assert:
|
||||
that:
|
||||
# FIXME - delete_ipv6_allow_check is changed
|
||||
- delete_ipv6_allow_check is changed
|
||||
- delete_ipv6_allow is changed
|
||||
- delete_ipv6_allow_idem is not changed
|
||||
- delete_ipv6_allow_idem_check is not changed
|
||||
|
@ -326,8 +329,8 @@
|
|||
register: reload_check
|
||||
- assert:
|
||||
that:
|
||||
- reload is not changed # NOT as expected!
|
||||
- reload_check is not changed # NOT as expected!
|
||||
- reload is changed
|
||||
- reload_check is changed
|
||||
|
||||
# ############################################
|
||||
- name: Disable (check mode)
|
||||
|
@ -350,7 +353,7 @@
|
|||
register: disable_idem_check
|
||||
- assert:
|
||||
that:
|
||||
# FIXME - disable_check is changed
|
||||
- disable_check is changed
|
||||
- disable is changed
|
||||
- disable_idem is not changed
|
||||
- disable_idem_check is not changed
|
||||
|
@ -393,7 +396,7 @@
|
|||
register: reset_idem_check
|
||||
- assert:
|
||||
that:
|
||||
- reset_check is not changed # NOT as expected!
|
||||
- reset is not changed # NOT as expected!
|
||||
- reset_idem is not changed
|
||||
- reset_idem_check is not changed
|
||||
- reset_check is changed
|
||||
- reset is changed
|
||||
- reset_idem is changed
|
||||
- reset_idem_check is changed
|
||||
|
|
|
@ -4,6 +4,9 @@
|
|||
state: enabled
|
||||
|
||||
# ############################################
|
||||
- name: Make sure logging is off
|
||||
ufw:
|
||||
logging: no
|
||||
- name: Logging (check mode)
|
||||
ufw:
|
||||
logging: yes
|
||||
|
@ -17,6 +20,8 @@
|
|||
shell: |
|
||||
ufw status verbose | grep "^Logging:"
|
||||
register: ufw_logging
|
||||
environment:
|
||||
LC_ALL: C
|
||||
- name: Logging (idempotency)
|
||||
ufw:
|
||||
logging: yes
|
||||
|
@ -26,13 +31,31 @@
|
|||
logging: yes
|
||||
check_mode: yes
|
||||
register: logging_idem_check
|
||||
- name: Logging (change, check mode)
|
||||
ufw:
|
||||
logging: full
|
||||
check_mode: yes
|
||||
register: logging_change_check
|
||||
- name: Logging (change)
|
||||
ufw:
|
||||
logging: full
|
||||
register: logging_change
|
||||
- name: Get logging
|
||||
shell: |
|
||||
ufw status verbose | grep "^Logging:"
|
||||
register: ufw_logging_change
|
||||
environment:
|
||||
LC_ALL: C
|
||||
- assert:
|
||||
that:
|
||||
- logging_check is not changed # NOT as expected!
|
||||
- logging is not changed # NOT as expected!
|
||||
- logging_check is changed
|
||||
- logging is changed
|
||||
- "ufw_logging.stdout == 'Logging: on (low)'"
|
||||
- logging_idem is not changed
|
||||
- logging_idem_check is not changed
|
||||
- "ufw_logging_change.stdout == 'Logging: on (full)'"
|
||||
- logging_change is changed
|
||||
- logging_change_check is changed
|
||||
|
||||
# ############################################
|
||||
- name: Default (check mode)
|
||||
|
@ -50,6 +73,8 @@
|
|||
shell: |
|
||||
ufw status verbose | grep "^Default:"
|
||||
register: ufw_defaults
|
||||
environment:
|
||||
LC_ALL: C
|
||||
- name: Default (idempotency)
|
||||
ufw:
|
||||
default: reject
|
||||
|
@ -76,13 +101,15 @@
|
|||
shell: |
|
||||
ufw status verbose | grep "^Default:"
|
||||
register: ufw_defaults_change
|
||||
environment:
|
||||
LC_ALL: C
|
||||
- assert:
|
||||
that:
|
||||
# FIXME - default_check is changed
|
||||
- default_check is changed
|
||||
- default is changed
|
||||
- "'reject (incoming)' in ufw_defaults.stdout"
|
||||
- default_idem is not changed
|
||||
- default_idem_check is not changed
|
||||
# FIXME - default_change_check is changed
|
||||
- default_change_check is changed
|
||||
- default_change is changed
|
||||
- "'allow (incoming)' in ufw_defaults_change.stdout"
|
||||
|
|
301
test/units/modules/system/test_ufw.py
Normal file
301
test/units/modules/system/test_ufw.py
Normal file
|
@ -0,0 +1,301 @@
|
|||
|
||||
from units.compat import unittest
|
||||
from units.compat.mock import patch
|
||||
from ansible.module_utils import basic
|
||||
from ansible.module_utils._text import to_bytes
|
||||
import ansible.modules.system.ufw as module
|
||||
|
||||
import json
|
||||
|
||||
|
||||
# mock ufw messages
|
||||
|
||||
ufw_version_35 = """ufw 0.35\nCopyright 2008-2015 Canonical Ltd.\n"""
|
||||
|
||||
ufw_verbose_header = """Status: active
|
||||
Logging: on (low)
|
||||
Default: deny (incoming), allow (outgoing), deny (routed)
|
||||
New profiles: skip
|
||||
|
||||
To Action From
|
||||
-- ------ ----"""
|
||||
|
||||
|
||||
ufw_status_verbose_with_port_7000 = ufw_verbose_header + """
|
||||
7000/tcp ALLOW IN Anywhere
|
||||
7000/tcp (v6) ALLOW IN Anywhere (v6)
|
||||
"""
|
||||
|
||||
user_rules_with_port_7000 = """### tuple ### allow tcp 7000 0.0.0.0/0 any 0.0.0.0/0 in
|
||||
### tuple ### allow tcp 7000 ::/0 any ::/0 in
|
||||
"""
|
||||
|
||||
user_rules_with_ipv6 = """### tuple ### allow udp 5353 0.0.0.0/0 any 224.0.0.251 in
|
||||
### tuple ### allow udp 5353 ::/0 any ff02::fb in
|
||||
"""
|
||||
|
||||
ufw_status_verbose_with_ipv6 = ufw_verbose_header + """
|
||||
5353/udp ALLOW IN 224.0.0.251
|
||||
5353/udp ALLOW IN ff02::fb
|
||||
"""
|
||||
|
||||
ufw_status_verbose_nothing = ufw_verbose_header
|
||||
|
||||
skippg_adding_existing_rules = "Skipping adding existing rule\nSkipping adding existing rule (v6)\n"
|
||||
|
||||
grep_config_cli = "grep -h '^### tuple' /lib/ufw/user.rules /lib/ufw/user6.rules /etc/ufw/user.rules /etc/ufw/user6.rules "
|
||||
grep_config_cli += "/var/lib/ufw/user.rules /var/lib/ufw/user6.rules"
|
||||
|
||||
dry_mode_cmd_with_port_700 = {
|
||||
"ufw status verbose": ufw_status_verbose_with_port_7000,
|
||||
"ufw --version": ufw_version_35,
|
||||
"ufw --dry-run allow from any to any port 7000 proto tcp": skippg_adding_existing_rules,
|
||||
"ufw --dry-run delete allow from any to any port 7000 proto tcp": "",
|
||||
"ufw --dry-run delete allow from any to any port 7001 proto tcp": user_rules_with_port_7000,
|
||||
grep_config_cli: user_rules_with_port_7000
|
||||
}
|
||||
|
||||
# setup configuration :
|
||||
# ufw reset
|
||||
# ufw enable
|
||||
# ufw allow proto udp to any port 5353 from 224.0.0.251
|
||||
# ufw allow proto udp to any port 5353 from ff02::fb
|
||||
dry_mode_cmd_with_ipv6 = {
|
||||
"ufw status verbose": ufw_status_verbose_with_ipv6,
|
||||
"ufw --version": ufw_version_35,
|
||||
# CONTENT of the command sudo ufw --dry-run delete allow in from ff02::fb port 5353 proto udp | grep -E "^### tupple"
|
||||
"ufw --dry-run delete allow from ff02::fb to any port 5353 proto udp": "### tuple ### allow udp any ::/0 5353 ff02::fb in",
|
||||
grep_config_cli: user_rules_with_ipv6,
|
||||
"ufw --dry-run allow from ff02::fb to any port 5353 proto udp": skippg_adding_existing_rules,
|
||||
"ufw --dry-run allow from 224.0.0.252 to any port 5353 proto udp": """### tuple ### allow udp 5353 0.0.0.0/0 any 224.0.0.251 in
|
||||
### tuple ### allow udp 5353 0.0.0.0/0 any 224.0.0.252 in
|
||||
""",
|
||||
"ufw --dry-run allow from 10.0.0.0/24 to any port 1577 proto udp": "### tuple ### allow udp 1577 0.0.0.0/0 any 10.0.0.0/24 in"
|
||||
}
|
||||
|
||||
dry_mode_cmd_nothing = {
|
||||
"ufw status verbose": ufw_status_verbose_nothing,
|
||||
"ufw --version": ufw_version_35,
|
||||
grep_config_cli: "",
|
||||
"ufw --dry-run allow from any to :: port 23": "### tuple ### allow any 23 :: any ::/0 in"
|
||||
}
|
||||
|
||||
|
||||
def do_nothing_func_nothing(*args, **kwarg):
|
||||
return 0, dry_mode_cmd_nothing[args[0]], ""
|
||||
|
||||
|
||||
def do_nothing_func_ipv6(*args, **kwarg):
|
||||
return 0, dry_mode_cmd_with_ipv6[args[0]], ""
|
||||
|
||||
|
||||
def do_nothing_func_port_7000(*args, **kwarg):
|
||||
return 0, dry_mode_cmd_with_port_700[args[0]], ""
|
||||
|
||||
|
||||
def set_module_args(args):
|
||||
args = json.dumps({'ANSIBLE_MODULE_ARGS': args})
|
||||
"""prepare arguments so that they will be picked up during module creation"""
|
||||
basic._ANSIBLE_ARGS = to_bytes(args)
|
||||
|
||||
|
||||
class AnsibleExitJson(Exception):
|
||||
"""Exception class to be raised by module.exit_json and caught by the test case"""
|
||||
pass
|
||||
|
||||
|
||||
class AnsibleFailJson(Exception):
|
||||
"""Exception class to be raised by module.fail_json and caught by the test case"""
|
||||
pass
|
||||
|
||||
|
||||
def exit_json(*args, **kwargs):
|
||||
"""function to patch over exit_json; package return data into an exception"""
|
||||
if 'changed' not in kwargs:
|
||||
kwargs['changed'] = False
|
||||
raise AnsibleExitJson(kwargs)
|
||||
|
||||
|
||||
def fail_json(*args, **kwargs):
|
||||
"""function to patch over fail_json; package return data into an exception"""
|
||||
kwargs['failed'] = True
|
||||
raise AnsibleFailJson(kwargs)
|
||||
|
||||
|
||||
def get_bin_path(self, arg, required=False):
|
||||
"""Mock AnsibleModule.get_bin_path"""
|
||||
return arg
|
||||
|
||||
|
||||
class TestUFW(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.mock_module_helper = patch.multiple(basic.AnsibleModule,
|
||||
exit_json=exit_json,
|
||||
fail_json=fail_json,
|
||||
get_bin_path=get_bin_path)
|
||||
self.mock_module_helper.start()
|
||||
self.addCleanup(self.mock_module_helper.stop)
|
||||
|
||||
def test_filter_line_that_contains_ipv4(self):
|
||||
reg = module.compile_ipv4_regexp()
|
||||
|
||||
self.assertTrue(reg.search("### tuple ### allow udp 5353 ::/0 any ff02::fb in") is None)
|
||||
self.assertTrue(reg.search("### tuple ### allow udp 5353 0.0.0.0/0 any 224.0.0.251 in") is not None)
|
||||
|
||||
self.assertTrue(reg.match("ff02::fb") is None)
|
||||
self.assertTrue(reg.match("224.0.0.251") is not None)
|
||||
self.assertTrue(reg.match("10.0.0.0/8") is not None)
|
||||
self.assertTrue(reg.match("somethingElse") is None)
|
||||
self.assertTrue(reg.match("::") is None)
|
||||
self.assertTrue(reg.match("any") is None)
|
||||
|
||||
def test_filter_line_that_contains_ipv6(self):
|
||||
reg = module.compile_ipv6_regexp()
|
||||
self.assertTrue(reg.search("### tuple ### allow udp 5353 ::/0 any ff02::fb in") is not None)
|
||||
self.assertTrue(reg.search("### tuple ### allow udp 5353 0.0.0.0/0 any 224.0.0.251 in") is None)
|
||||
self.assertTrue(reg.search("### tuple ### allow any 23 :: any ::/0 in") is not None)
|
||||
self.assertTrue(reg.match("ff02::fb") is not None)
|
||||
self.assertTrue(reg.match("224.0.0.251") is None)
|
||||
self.assertTrue(reg.match("::") is not None)
|
||||
|
||||
def test_check_mode_add_rules(self):
|
||||
set_module_args({
|
||||
'rule': 'allow',
|
||||
'proto': 'tcp',
|
||||
'port': '7000',
|
||||
'_ansible_check_mode': True
|
||||
})
|
||||
result = self.__getResult(do_nothing_func_port_7000)
|
||||
self.assertFalse(result.exception.args[0]['changed'])
|
||||
|
||||
def test_check_mode_delete_existing_rules(self):
|
||||
|
||||
set_module_args({
|
||||
'rule': 'allow',
|
||||
'proto': 'tcp',
|
||||
'port': '7000',
|
||||
'delete': 'yes',
|
||||
'_ansible_check_mode': True,
|
||||
})
|
||||
|
||||
self.assertTrue(self.__getResult(do_nothing_func_port_7000).exception.args[0]['changed'])
|
||||
|
||||
def test_check_mode_delete_not_existing_rules(self):
|
||||
|
||||
set_module_args({
|
||||
'rule': 'allow',
|
||||
'proto': 'tcp',
|
||||
'port': '7001',
|
||||
'delete': 'yes',
|
||||
'_ansible_check_mode': True,
|
||||
})
|
||||
|
||||
self.assertFalse(self.__getResult(do_nothing_func_port_7000).exception.args[0]['changed'])
|
||||
|
||||
def test_enable_mode(self):
|
||||
set_module_args({
|
||||
'state': 'enabled',
|
||||
'_ansible_check_mode': True
|
||||
})
|
||||
|
||||
self.assertFalse(self.__getResult(do_nothing_func_port_7000).exception.args[0]['changed'])
|
||||
|
||||
def test_disable_mode(self):
|
||||
set_module_args({
|
||||
'state': 'disabled',
|
||||
'_ansible_check_mode': True
|
||||
})
|
||||
|
||||
self.assertTrue(self.__getResult(do_nothing_func_port_7000).exception.args[0]['changed'])
|
||||
|
||||
def test_logging_off(self):
|
||||
set_module_args({
|
||||
'logging': 'off',
|
||||
'_ansible_check_mode': True
|
||||
})
|
||||
|
||||
self.assertTrue(self.__getResult(do_nothing_func_port_7000).exception.args[0]['changed'])
|
||||
|
||||
def test_logging_on(self):
|
||||
set_module_args({
|
||||
'logging': 'on',
|
||||
'_ansible_check_mode': True
|
||||
})
|
||||
|
||||
self.assertFalse(self.__getResult(do_nothing_func_port_7000).exception.args[0]['changed'])
|
||||
|
||||
def test_default_changed(self):
|
||||
set_module_args({
|
||||
'default': 'allow',
|
||||
"direction": "incoming",
|
||||
'_ansible_check_mode': True
|
||||
})
|
||||
self.assertTrue(self.__getResult(do_nothing_func_port_7000).exception.args[0]['changed'])
|
||||
|
||||
def test_default_not_changed(self):
|
||||
set_module_args({
|
||||
'default': 'deny',
|
||||
"direction": "incoming",
|
||||
'_ansible_check_mode': True
|
||||
})
|
||||
self.assertFalse(self.__getResult(do_nothing_func_port_7000).exception.args[0]['changed'])
|
||||
|
||||
def test_ipv6_remove(self):
|
||||
set_module_args({
|
||||
'rule': 'allow',
|
||||
'proto': 'udp',
|
||||
'port': '5353',
|
||||
'from': 'ff02::fb',
|
||||
'delete': 'yes',
|
||||
'_ansible_check_mode': True,
|
||||
})
|
||||
self.assertTrue(self.__getResult(do_nothing_func_ipv6).exception.args[0]['changed'])
|
||||
|
||||
def test_ipv6_add_existing(self):
|
||||
set_module_args({
|
||||
'rule': 'allow',
|
||||
'proto': 'udp',
|
||||
'port': '5353',
|
||||
'from': 'ff02::fb',
|
||||
'_ansible_check_mode': True,
|
||||
})
|
||||
self.assertFalse(self.__getResult(do_nothing_func_ipv6).exception.args[0]['changed'])
|
||||
|
||||
def test_add_not_existing_ipv4_submask(self):
|
||||
set_module_args({
|
||||
'rule': 'allow',
|
||||
'proto': 'udp',
|
||||
'port': '1577',
|
||||
'from': '10.0.0.0/24',
|
||||
'_ansible_check_mode': True,
|
||||
})
|
||||
self.assertTrue(self.__getResult(do_nothing_func_ipv6).exception.args[0]['changed'])
|
||||
|
||||
def test_ipv4_add_with_existing_ipv6(self):
|
||||
set_module_args({
|
||||
'rule': 'allow',
|
||||
'proto': 'udp',
|
||||
'port': '5353',
|
||||
'from': '224.0.0.252',
|
||||
'_ansible_check_mode': True,
|
||||
})
|
||||
self.assertTrue(self.__getResult(do_nothing_func_ipv6).exception.args[0]['changed'])
|
||||
|
||||
def test_ipv6_add_from_nothing(self):
|
||||
set_module_args({
|
||||
'rule': 'allow',
|
||||
'port': '23',
|
||||
'to': '::',
|
||||
'_ansible_check_mode': True,
|
||||
})
|
||||
result = self.__getResult(do_nothing_func_nothing).exception.args[0]
|
||||
print(result)
|
||||
self.assertTrue(result['changed'])
|
||||
|
||||
def __getResult(self, cmd_fun):
|
||||
with patch.object(basic.AnsibleModule, 'run_command') as mock_run_command:
|
||||
mock_run_command.side_effect = cmd_fun
|
||||
with self.assertRaises(AnsibleExitJson) as result:
|
||||
module.main()
|
||||
return result
|
Loading…
Reference in a new issue