1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2024-09-14 20:13:21 +02:00

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
This commit is contained in:
Maxopoly 2024-03-24 18:02:48 +01:00 committed by GitHub
parent 4363f8764b
commit 23396e62dc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 104 additions and 18 deletions

View file

@ -0,0 +1,2 @@
bugfixes:
- iptables_state - fix idempotency issues when restoring incomplete iptables dumps (https://github.com/ansible-collections/community.general/issues/8029).

View file

@ -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():
if table_name not in tables_before:
# Would initialize a table, which doesn't exist yet
changed = True changed = True
else: break
tables_after = per_table_state(SAVECOMMAND, stdout) if tables_before[table_name] != table_content:
if tables_after != tables_before: # Content of some table changes
changed = True 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. "

View file

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

View file

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