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
|
import re
|
||||||
|
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
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():
|
def main():
|
||||||
module = AnsibleModule(
|
module = AnsibleModule(
|
||||||
argument_spec=dict(
|
argument_spec=dict(
|
||||||
|
@ -241,24 +267,62 @@ def main():
|
||||||
|
|
||||||
cmds = []
|
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)))
|
cmd = ' '.join(map(itemgetter(-1), filter(itemgetter(0), cmd)))
|
||||||
|
|
||||||
cmds.append(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:
|
if rc != 0 and not ignore_error:
|
||||||
module.fail_json(msg=err or out)
|
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():
|
def ufw_version():
|
||||||
"""
|
"""
|
||||||
Returns the major and minor version of ufw installed on the system.
|
Returns the major and minor version of ufw installed on the system.
|
||||||
"""
|
"""
|
||||||
rc, out, err = module.run_command("%s --version" % ufw_bin)
|
out = execute([[ufw_bin], ["--version"]])
|
||||||
if rc != 0:
|
|
||||||
module.fail_json(
|
|
||||||
msg="Failed to get ufw version.", rc=rc, out=out, err=err
|
|
||||||
)
|
|
||||||
|
|
||||||
lines = [x for x in out.split('\n') if x.strip() != '']
|
lines = [x for x in out.split('\n') if x.strip() != '']
|
||||||
if len(lines) == 0:
|
if len(lines) == 0:
|
||||||
|
@ -291,24 +355,64 @@ def main():
|
||||||
|
|
||||||
# Ensure ufw is available
|
# Ensure ufw is available
|
||||||
ufw_bin = module.get_bin_path('ufw', True)
|
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
|
# Save the pre state and rules in order to recognize changes
|
||||||
(_, pre_state, _) = module.run_command(ufw_bin + ' status verbose')
|
pre_state = execute([[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_rules = get_current_rules()
|
||||||
|
|
||||||
# Execute commands
|
changed = False
|
||||||
|
|
||||||
|
# Execute filter
|
||||||
for (command, value) in commands.items():
|
for (command, value) in commands.items():
|
||||||
|
|
||||||
cmd = [[ufw_bin], [module.check_mode, '--dry-run']]
|
cmd = [[ufw_bin], [module.check_mode, '--dry-run']]
|
||||||
|
|
||||||
if command == 'state':
|
if command == 'state':
|
||||||
states = {'enabled': 'enable', 'disabled': 'disable',
|
states = {'enabled': 'enable', 'disabled': 'disable',
|
||||||
'reloaded': 'reload', 'reset': 'reset'}
|
'reloaded': 'reload', 'reset': 'reset'}
|
||||||
|
|
||||||
|
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]]])
|
execute(cmd + [['-f'], [states[value]]])
|
||||||
|
|
||||||
elif command == 'logging':
|
elif command == 'logging':
|
||||||
|
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]])
|
execute(cmd + [[command], [value]])
|
||||||
|
|
||||||
elif command == 'default':
|
elif command == 'default':
|
||||||
|
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']]])
|
execute(cmd + [[command], [value], [params['direction']]])
|
||||||
|
|
||||||
elif command == 'rule':
|
elif command == 'rule':
|
||||||
|
@ -336,13 +440,33 @@ def main():
|
||||||
if (ufw_major == 0 and ufw_minor >= 35) or ufw_major > 0:
|
if (ufw_major == 0 and ufw_minor >= 35) or ufw_major > 0:
|
||||||
cmd.append([params['comment'], "comment '%s'" % params['comment']])
|
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
|
# Get the new state
|
||||||
(_, post_state, _) = module.run_command(ufw_bin + ' status verbose')
|
if module.check_mode:
|
||||||
(_, post_rules, _) = module.run_command("grep '^### tuple' /lib/ufw/user.rules /lib/ufw/user6.rules /etc/ufw/user.rules /etc/ufw/user6.rules")
|
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)
|
changed = (pre_state != post_state) or (pre_rules != post_rules)
|
||||||
|
|
||||||
return module.exit_json(changed=changed, commands=cmds, msg=post_state.rstrip())
|
return module.exit_json(changed=changed, commands=cmds, msg=post_state.rstrip())
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
---
|
---
|
||||||
# ############################################
|
# ############################################
|
||||||
|
- name: Make sure it is off
|
||||||
|
ufw:
|
||||||
|
state: disabled
|
||||||
- name: Enable (check mode)
|
- name: Enable (check mode)
|
||||||
ufw:
|
ufw:
|
||||||
state: enabled
|
state: enabled
|
||||||
|
@ -20,7 +23,7 @@
|
||||||
register: enable_idem_check
|
register: enable_idem_check
|
||||||
- assert:
|
- assert:
|
||||||
that:
|
that:
|
||||||
# FIXME - enable_check is changed
|
- enable_check is changed
|
||||||
- enable is changed
|
- enable is changed
|
||||||
- enable_idem is not changed
|
- enable_idem is not changed
|
||||||
- enable_idem_check is not changed
|
- enable_idem_check is not changed
|
||||||
|
@ -54,7 +57,7 @@
|
||||||
register: ipv4_allow_idem_check
|
register: ipv4_allow_idem_check
|
||||||
- assert:
|
- assert:
|
||||||
that:
|
that:
|
||||||
# FIXME - ipv4_allow_check is changed
|
- ipv4_allow_check is changed
|
||||||
- ipv4_allow is changed
|
- ipv4_allow is changed
|
||||||
- ipv4_allow_idem is not changed
|
- ipv4_allow_idem is not changed
|
||||||
- ipv4_allow_idem_check is not changed
|
- ipv4_allow_idem_check is not changed
|
||||||
|
@ -92,7 +95,7 @@
|
||||||
register: delete_ipv4_allow_idem_check
|
register: delete_ipv4_allow_idem_check
|
||||||
- assert:
|
- assert:
|
||||||
that:
|
that:
|
||||||
# FIXME - delete_ipv4_allow_check is changed
|
- delete_ipv4_allow_check is changed
|
||||||
- delete_ipv4_allow is changed
|
- delete_ipv4_allow is changed
|
||||||
- delete_ipv4_allow_idem is not changed
|
- delete_ipv4_allow_idem is not changed
|
||||||
- delete_ipv4_allow_idem_check is not changed
|
- delete_ipv4_allow_idem_check is not changed
|
||||||
|
@ -126,7 +129,7 @@
|
||||||
register: ipv6_allow_idem_check
|
register: ipv6_allow_idem_check
|
||||||
- assert:
|
- assert:
|
||||||
that:
|
that:
|
||||||
# FIXME - ipv6_allow_check is changed
|
- ipv6_allow_check is changed
|
||||||
- ipv6_allow is changed
|
- ipv6_allow is changed
|
||||||
- ipv6_allow_idem is not changed
|
- ipv6_allow_idem is not changed
|
||||||
- ipv6_allow_idem_check is not changed
|
- ipv6_allow_idem_check is not changed
|
||||||
|
@ -164,7 +167,7 @@
|
||||||
register: delete_ipv6_allow_idem_check
|
register: delete_ipv6_allow_idem_check
|
||||||
- assert:
|
- assert:
|
||||||
that:
|
that:
|
||||||
# FIXME - delete_ipv6_allow_check is changed
|
- delete_ipv6_allow_check is changed
|
||||||
- delete_ipv6_allow is changed
|
- delete_ipv6_allow is changed
|
||||||
- delete_ipv6_allow_idem is not changed
|
- delete_ipv6_allow_idem is not changed
|
||||||
- delete_ipv6_allow_idem_check is not changed
|
- delete_ipv6_allow_idem_check is not changed
|
||||||
|
@ -199,7 +202,7 @@
|
||||||
register: ipv4_allow_idem_check
|
register: ipv4_allow_idem_check
|
||||||
- assert:
|
- assert:
|
||||||
that:
|
that:
|
||||||
# FIXME - ipv4_allow_check is changed
|
- ipv4_allow_check is changed
|
||||||
- ipv4_allow is changed
|
- ipv4_allow is changed
|
||||||
- ipv4_allow_idem is not changed
|
- ipv4_allow_idem is not changed
|
||||||
- ipv4_allow_idem_check is not changed
|
- ipv4_allow_idem_check is not changed
|
||||||
|
@ -237,7 +240,7 @@
|
||||||
register: delete_ipv4_allow_idem_check
|
register: delete_ipv4_allow_idem_check
|
||||||
- assert:
|
- assert:
|
||||||
that:
|
that:
|
||||||
# FIXME - delete_ipv4_allow_check is changed
|
- delete_ipv4_allow_check is changed
|
||||||
- delete_ipv4_allow is changed
|
- delete_ipv4_allow is changed
|
||||||
- delete_ipv4_allow_idem is not changed
|
- delete_ipv4_allow_idem is not changed
|
||||||
- delete_ipv4_allow_idem_check is not changed
|
- delete_ipv4_allow_idem_check is not changed
|
||||||
|
@ -271,7 +274,7 @@
|
||||||
register: ipv6_allow_idem_check
|
register: ipv6_allow_idem_check
|
||||||
- assert:
|
- assert:
|
||||||
that:
|
that:
|
||||||
# FIXME - ipv6_allow is_check changed
|
- ipv6_allow_check is changed
|
||||||
- ipv6_allow is changed
|
- ipv6_allow is changed
|
||||||
- ipv6_allow_idem is not changed
|
- ipv6_allow_idem is not changed
|
||||||
- ipv6_allow_idem_check is not changed
|
- ipv6_allow_idem_check is not changed
|
||||||
|
@ -309,7 +312,7 @@
|
||||||
register: delete_ipv6_allow_idem_check
|
register: delete_ipv6_allow_idem_check
|
||||||
- assert:
|
- assert:
|
||||||
that:
|
that:
|
||||||
# FIXME - delete_ipv6_allow_check is changed
|
- delete_ipv6_allow_check is changed
|
||||||
- delete_ipv6_allow is changed
|
- delete_ipv6_allow is changed
|
||||||
- delete_ipv6_allow_idem is not changed
|
- delete_ipv6_allow_idem is not changed
|
||||||
- delete_ipv6_allow_idem_check is not changed
|
- delete_ipv6_allow_idem_check is not changed
|
||||||
|
@ -326,8 +329,8 @@
|
||||||
register: reload_check
|
register: reload_check
|
||||||
- assert:
|
- assert:
|
||||||
that:
|
that:
|
||||||
- reload is not changed # NOT as expected!
|
- reload is changed
|
||||||
- reload_check is not changed # NOT as expected!
|
- reload_check is changed
|
||||||
|
|
||||||
# ############################################
|
# ############################################
|
||||||
- name: Disable (check mode)
|
- name: Disable (check mode)
|
||||||
|
@ -350,7 +353,7 @@
|
||||||
register: disable_idem_check
|
register: disable_idem_check
|
||||||
- assert:
|
- assert:
|
||||||
that:
|
that:
|
||||||
# FIXME - disable_check is changed
|
- disable_check is changed
|
||||||
- disable is changed
|
- disable is changed
|
||||||
- disable_idem is not changed
|
- disable_idem is not changed
|
||||||
- disable_idem_check is not changed
|
- disable_idem_check is not changed
|
||||||
|
@ -393,7 +396,7 @@
|
||||||
register: reset_idem_check
|
register: reset_idem_check
|
||||||
- assert:
|
- assert:
|
||||||
that:
|
that:
|
||||||
- reset_check is not changed # NOT as expected!
|
- reset_check is changed
|
||||||
- reset is not changed # NOT as expected!
|
- reset is changed
|
||||||
- reset_idem is not changed
|
- reset_idem is changed
|
||||||
- reset_idem_check is not changed
|
- reset_idem_check is changed
|
||||||
|
|
|
@ -4,6 +4,9 @@
|
||||||
state: enabled
|
state: enabled
|
||||||
|
|
||||||
# ############################################
|
# ############################################
|
||||||
|
- name: Make sure logging is off
|
||||||
|
ufw:
|
||||||
|
logging: no
|
||||||
- name: Logging (check mode)
|
- name: Logging (check mode)
|
||||||
ufw:
|
ufw:
|
||||||
logging: yes
|
logging: yes
|
||||||
|
@ -17,6 +20,8 @@
|
||||||
shell: |
|
shell: |
|
||||||
ufw status verbose | grep "^Logging:"
|
ufw status verbose | grep "^Logging:"
|
||||||
register: ufw_logging
|
register: ufw_logging
|
||||||
|
environment:
|
||||||
|
LC_ALL: C
|
||||||
- name: Logging (idempotency)
|
- name: Logging (idempotency)
|
||||||
ufw:
|
ufw:
|
||||||
logging: yes
|
logging: yes
|
||||||
|
@ -26,13 +31,31 @@
|
||||||
logging: yes
|
logging: yes
|
||||||
check_mode: yes
|
check_mode: yes
|
||||||
register: logging_idem_check
|
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:
|
- assert:
|
||||||
that:
|
that:
|
||||||
- logging_check is not changed # NOT as expected!
|
- logging_check is changed
|
||||||
- logging is not changed # NOT as expected!
|
- logging is changed
|
||||||
- "ufw_logging.stdout == 'Logging: on (low)'"
|
- "ufw_logging.stdout == 'Logging: on (low)'"
|
||||||
- logging_idem is not changed
|
- logging_idem is not changed
|
||||||
- logging_idem_check 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)
|
- name: Default (check mode)
|
||||||
|
@ -50,6 +73,8 @@
|
||||||
shell: |
|
shell: |
|
||||||
ufw status verbose | grep "^Default:"
|
ufw status verbose | grep "^Default:"
|
||||||
register: ufw_defaults
|
register: ufw_defaults
|
||||||
|
environment:
|
||||||
|
LC_ALL: C
|
||||||
- name: Default (idempotency)
|
- name: Default (idempotency)
|
||||||
ufw:
|
ufw:
|
||||||
default: reject
|
default: reject
|
||||||
|
@ -76,13 +101,15 @@
|
||||||
shell: |
|
shell: |
|
||||||
ufw status verbose | grep "^Default:"
|
ufw status verbose | grep "^Default:"
|
||||||
register: ufw_defaults_change
|
register: ufw_defaults_change
|
||||||
|
environment:
|
||||||
|
LC_ALL: C
|
||||||
- assert:
|
- assert:
|
||||||
that:
|
that:
|
||||||
# FIXME - default_check is changed
|
- default_check is changed
|
||||||
- default is changed
|
- default is changed
|
||||||
- "'reject (incoming)' in ufw_defaults.stdout"
|
- "'reject (incoming)' in ufw_defaults.stdout"
|
||||||
- default_idem is not changed
|
- default_idem is not changed
|
||||||
- default_idem_check is not changed
|
- default_idem_check is not changed
|
||||||
# FIXME - default_change_check is changed
|
- default_change_check is changed
|
||||||
- default_change is changed
|
- default_change is changed
|
||||||
- "'allow (incoming)' in ufw_defaults_change.stdout"
|
- "'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