mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
[PR #8029/23396e62 backport][stable-7] Fix check mode in iptables_state for incomplete iptables-save files along with integration tests (#8136)
Fix check mode in iptables_state for incomplete iptables-save files along with integration tests (#8029)
* Implement integration test to reproduce #7463
* Make new iptables_state checks async
* Add missing commit to iptable_state integration test
* Remove async when using checkmode in iptables_state integration tests
* Do per table comparison in check mode for iptables_state
* Calculate changes of iptables state per table based on result
* Output target iptables state in checkmode
* Refactor calculation of invidual table states in iptables_state
* Add missing return for table calculation
* Add missing arg to regex check
* Remove leftover debug output for target iptable state
* Parse per table state from raw state string
* Join restored state for extration of table specific rules
* Switch arguments for joining restored iptable state
* Output final ip table state
* Compare content of tables
* Complete iptables partial tables test cases
* Correct order of test iptables data
* Update docu for iptables tables_after
* Add changelog fragment
* Appease the linting gods for iptables_state
* Adjust spelling and remove tables_after from return values
(cherry picked from commit 23396e62dc
)
Co-authored-by: Maxopoly <max@dermax.org>
This commit is contained in:
parent
e043274ced
commit
c3df54689c
4 changed files with 104 additions and 18 deletions
|
@ -0,0 +1,2 @@
|
||||||
|
bugfixes:
|
||||||
|
- iptables_state - fix idempotency issues when restoring incomplete iptables dumps (https://github.com/ansible-collections/community.general/issues/8029).
|
|
@ -207,7 +207,9 @@ saved:
|
||||||
"# Completed"
|
"# Completed"
|
||||||
]
|
]
|
||||||
tables:
|
tables:
|
||||||
description: The iptables we have interest for when module starts.
|
description:
|
||||||
|
- The iptables on the system before the module has run, separated by table.
|
||||||
|
- If the option O(table) is used, only this table is included.
|
||||||
type: dict
|
type: dict
|
||||||
contains:
|
contains:
|
||||||
table:
|
table:
|
||||||
|
@ -346,20 +348,27 @@ def filter_and_format_state(string):
|
||||||
return lines
|
return lines
|
||||||
|
|
||||||
|
|
||||||
def per_table_state(command, state):
|
def parse_per_table_state(all_states_dump):
|
||||||
'''
|
'''
|
||||||
Convert raw iptables-save output into usable datastructure, for reliable
|
Convert raw iptables-save output into usable datastructure, for reliable
|
||||||
comparisons between initial and final states.
|
comparisons between initial and final states.
|
||||||
'''
|
'''
|
||||||
|
lines = filter_and_format_state(all_states_dump)
|
||||||
tables = dict()
|
tables = dict()
|
||||||
for t in TABLES:
|
current_table = ''
|
||||||
COMMAND = list(command)
|
current_list = list()
|
||||||
if '*%s' % t in state.splitlines():
|
for line in lines:
|
||||||
COMMAND.extend(['--table', t])
|
if re.match(r'^[*](filter|mangle|nat|raw|security)$', line):
|
||||||
dummy, out, dummy = module.run_command(COMMAND, check_rc=True)
|
current_table = line[1:]
|
||||||
out = re.sub(r'(^|\n)(# Generated|# Completed|[*]%s|COMMIT)[^\n]*' % t, r'', out)
|
continue
|
||||||
out = re.sub(r' *\[[0-9]+:[0-9]+\] *', r'', out)
|
if line == 'COMMIT':
|
||||||
tables[t] = [tt for tt in out.splitlines() if tt != '']
|
tables[current_table] = current_list
|
||||||
|
current_table = ''
|
||||||
|
current_list = list()
|
||||||
|
continue
|
||||||
|
if line.startswith('# '):
|
||||||
|
continue
|
||||||
|
current_list.append(line)
|
||||||
return tables
|
return tables
|
||||||
|
|
||||||
|
|
||||||
|
@ -486,7 +495,7 @@ def main():
|
||||||
# Depending on the value of 'table', initref_state may differ from
|
# Depending on the value of 'table', initref_state may differ from
|
||||||
# initial_state.
|
# initial_state.
|
||||||
(rc, stdout, stderr) = module.run_command(SAVECOMMAND, check_rc=True)
|
(rc, stdout, stderr) = module.run_command(SAVECOMMAND, check_rc=True)
|
||||||
tables_before = per_table_state(SAVECOMMAND, stdout)
|
tables_before = parse_per_table_state(stdout)
|
||||||
initref_state = filter_and_format_state(stdout)
|
initref_state = filter_and_format_state(stdout)
|
||||||
|
|
||||||
if state == 'saved':
|
if state == 'saved':
|
||||||
|
@ -583,14 +592,17 @@ def main():
|
||||||
|
|
||||||
(rc, stdout, stderr) = module.run_command(SAVECOMMAND, check_rc=True)
|
(rc, stdout, stderr) = module.run_command(SAVECOMMAND, check_rc=True)
|
||||||
restored_state = filter_and_format_state(stdout)
|
restored_state = filter_and_format_state(stdout)
|
||||||
|
tables_after = parse_per_table_state('\n'.join(restored_state))
|
||||||
if restored_state not in (initref_state, initial_state):
|
if restored_state not in (initref_state, initial_state):
|
||||||
if module.check_mode:
|
for table_name, table_content in tables_after.items():
|
||||||
changed = True
|
if table_name not in tables_before:
|
||||||
else:
|
# Would initialize a table, which doesn't exist yet
|
||||||
tables_after = per_table_state(SAVECOMMAND, stdout)
|
|
||||||
if tables_after != tables_before:
|
|
||||||
changed = True
|
changed = True
|
||||||
|
break
|
||||||
|
if tables_before[table_name] != table_content:
|
||||||
|
# Content of some table changes
|
||||||
|
changed = True
|
||||||
|
break
|
||||||
|
|
||||||
if _back is None or module.check_mode:
|
if _back is None or module.check_mode:
|
||||||
module.exit_json(
|
module.exit_json(
|
||||||
|
@ -633,7 +645,7 @@ def main():
|
||||||
os.remove(b_back)
|
os.remove(b_back)
|
||||||
|
|
||||||
(rc, stdout, stderr) = module.run_command(SAVECOMMAND, check_rc=True)
|
(rc, stdout, stderr) = module.run_command(SAVECOMMAND, check_rc=True)
|
||||||
tables_rollback = per_table_state(SAVECOMMAND, stdout)
|
tables_rollback = parse_per_table_state(stdout)
|
||||||
|
|
||||||
msg = (
|
msg = (
|
||||||
"Failed to confirm state restored from %s after %ss. "
|
"Failed to confirm state restored from %s after %ss. "
|
||||||
|
|
|
@ -29,6 +29,12 @@
|
||||||
when:
|
when:
|
||||||
- xtables_lock is undefined
|
- xtables_lock is undefined
|
||||||
|
|
||||||
|
- name: include tasks to test partial restore files
|
||||||
|
include_tasks: tests/02-partial-restore.yml
|
||||||
|
when:
|
||||||
|
- xtables_lock is undefined
|
||||||
|
|
||||||
|
|
||||||
- name: include tasks to test rollbacks
|
- name: include tasks to test rollbacks
|
||||||
include_tasks: tests/10-rollback.yml
|
include_tasks: tests/10-rollback.yml
|
||||||
when:
|
when:
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
---
|
||||||
|
# Copyright (c) Ansible Project
|
||||||
|
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
- name: "Create initial rule set to use"
|
||||||
|
copy:
|
||||||
|
dest: "{{ iptables_tests }}"
|
||||||
|
content: |
|
||||||
|
*filter
|
||||||
|
:INPUT ACCEPT [0:0]
|
||||||
|
:FORWARD ACCEPT [0:0]
|
||||||
|
:OUTPUT ACCEPT [0:0]
|
||||||
|
-A INPUT -m state --state NEW,ESTABLISHED -j ACCEPT
|
||||||
|
COMMIT
|
||||||
|
*nat
|
||||||
|
:PREROUTING ACCEPT [151:17304]
|
||||||
|
:INPUT ACCEPT [151:17304]
|
||||||
|
:OUTPUT ACCEPT [151:17304]
|
||||||
|
:POSTROUTING ACCEPT [151:17304]
|
||||||
|
-A POSTROUTING -o eth0 -j MASQUERADE
|
||||||
|
COMMIT
|
||||||
|
|
||||||
|
- name: "Restore initial state"
|
||||||
|
iptables_state:
|
||||||
|
path: "{{ iptables_tests }}"
|
||||||
|
state: restored
|
||||||
|
async: "{{ ansible_timeout }}"
|
||||||
|
poll: 0
|
||||||
|
|
||||||
|
- name: "Create partial ruleset only specifying input"
|
||||||
|
copy:
|
||||||
|
dest: "{{ iptables_tests }}"
|
||||||
|
content: |
|
||||||
|
*filter
|
||||||
|
:INPUT ACCEPT [0:0]
|
||||||
|
:FORWARD ACCEPT [0:0]
|
||||||
|
:OUTPUT ACCEPT [0:0]
|
||||||
|
-A INPUT -m state --state NEW,ESTABLISHED -j ACCEPT
|
||||||
|
COMMIT
|
||||||
|
|
||||||
|
- name: "Check restoring partial state"
|
||||||
|
iptables_state:
|
||||||
|
path: "{{ iptables_tests }}"
|
||||||
|
state: restored
|
||||||
|
check_mode: true
|
||||||
|
register: iptables_state
|
||||||
|
|
||||||
|
|
||||||
|
- name: "assert that no changes are detected in check mode"
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- iptables_state is not changed
|
||||||
|
|
||||||
|
- name: "Restore partial state"
|
||||||
|
iptables_state:
|
||||||
|
path: "{{ iptables_tests }}"
|
||||||
|
state: restored
|
||||||
|
register: iptables_state
|
||||||
|
async: "{{ ansible_timeout }}"
|
||||||
|
poll: 0
|
||||||
|
|
||||||
|
- name: "assert that no changes are made"
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- iptables_state is not changed
|
Loading…
Reference in a new issue