From 4847473772c4d9a927fe66c583f724d66ea23316 Mon Sep 17 00:00:00 2001 From: Robert Kaussow Date: Sat, 22 Aug 2020 14:49:44 +0200 Subject: [PATCH] use original iptables_raw --- .flake8 | 1 + plugins/modules/iptables_raw.py | 465 ++++++++++++++++---------------- 2 files changed, 239 insertions(+), 227 deletions(-) diff --git a/.flake8 b/.flake8 index 88ea07a..2dbbbc0 100644 --- a/.flake8 +++ b/.flake8 @@ -14,5 +14,6 @@ exclude = .cache .eggs env* + iptables_raw.py application-import-names = ansiblelater format = ${cyan}%(path)s:%(row)d:%(col)d${reset}: ${red_bold}%(code)s${reset} %(text)s diff --git a/plugins/modules/iptables_raw.py b/plugins/modules/iptables_raw.py index 49102de..d62238f 100644 --- a/plugins/modules/iptables_raw.py +++ b/plugins/modules/iptables_raw.py @@ -1,12 +1,29 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2016, Strahinja Kustudic -# Copyright (c) 2016, Damir Markovic -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) -"""IPtables raw module.""" +""" +IPtables raw module. + +(c) 2016, Strahinja Kustudic +(c) 2016, Damir Markovic + +This file is part of Ansible + +Ansible is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Ansible is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Ansible. If not, see . +""" -ANSIBLE_METADATA = {"status": ["preview"], "supported_by": "community", "metadata_version": "1.0"} +ANSIBLE_METADATA = {'status': ['preview'], 'supported_by': 'community', 'metadata_version': '1.0'} -DOCUMENTATION = """ +DOCUMENTATION = r''' --- module: iptables_raw short_description: Manage iptables rules @@ -31,10 +48,10 @@ options: - If set to C(yes) keeps active iptables (unmanaged) rules for the target C(table) and gives them C(weight=90). This means these rules will be ordered after most of the rules, since default priority is 40, so they - shouldn"t be able to block any allow rules. If set to C(no) deletes all + shouldn't be able to block any allow rules. If set to C(no) deletes all rules which are not set by this module. - "WARNING: Be very careful when running C(keep_unmanaged=no) for the - first time, since if you don"t specify correct rules, you can block + first time, since if you don't specify correct rules, you can block yourself out of the managed host." required: false choices: ["yes", "no"] @@ -82,18 +99,18 @@ notes: Debian distributions (Debian, Ubuntu, etc): C(/etc/iptables/rules.v4) and C(/etc/iptables/rules.v6); other distributions: C(/etc/sysconfig/iptables) and C(/etc/sysconfig/ip6tables)." - - This module saves state in C(/etc/ansible-iptables) directory, so don"t + - This module saves state in C(/etc/ansible-iptables) directory, so don't modify this directory! author: - "Strahinja Kustudic (@kustodian)" - "Damir Markovic (@damirda)" -""" +''' -EXAMPLES = """ +EXAMPLES = ''' # Allow all IPv4 traffic coming in on port 80 (http) - iptables_raw: name: allow_tcp_80 - rules: "-A INPUT -p tcp -m tcp --dport 80 -j ACCEPT" + rules: '-A INPUT -p tcp -m tcp --dport 80 -j ACCEPT' # Set default rules with weight 10 and disregard all unmanaged rules - iptables_raw: @@ -113,7 +130,7 @@ EXAMPLES = """ ipversion: 6 weight: 50 name: allow_tcp_443 - rules: "-A INPUT -p tcp -m tcp --dport 443 -j ACCEPT" + rules: '-A INPUT -p tcp -m tcp --dport 443 -j ACCEPT' # Remove the above rule - iptables_raw: @@ -130,12 +147,12 @@ EXAMPLES = """ # Reset all IPv4 iptables rules in all tables and allow all traffic - iptables_raw: - name: "*" - table: "*" + name: '*' + table: '*' state: absent -""" +''' -RETURN = """ +RETURN = ''' state: description: state of the rules returned: success @@ -176,7 +193,7 @@ keep_unmanaged: returned: success type: boolean sample: True -""" +''' import fcntl import os @@ -194,8 +211,8 @@ from ansible.module_utils.basic import json def generate_diff(dump_old, dump_new): diff = dict() if dump_old != dump_new: - diff["before"] = dump_old - diff["after"] = dump_new + diff['before'] = dump_old + diff['after'] = dump_new return diff @@ -222,26 +239,26 @@ class Iptables: # Default chains for each table DEFAULT_CHAINS = { - "filter": ["INPUT", "FORWARD", "OUTPUT"], - "raw": ["PREROUTING", "OUTPUT"], - "nat": ["PREROUTING", "INPUT", "OUTPUT", "POSTROUTING"], - "mangle": ["PREROUTING", "INPUT", "FORWARD", "OUTPUT", "POSTROUTING"], - "security": ["INPUT", "FORWARD", "OUTPUT"] + 'filter': ['INPUT', 'FORWARD', 'OUTPUT'], + 'raw': ['PREROUTING', 'OUTPUT'], + 'nat': ['PREROUTING', 'INPUT', 'OUTPUT', 'POSTROUTING'], + 'mangle': ['PREROUTING', 'INPUT', 'FORWARD', 'OUTPUT', 'POSTROUTING'], + 'security': ['INPUT', 'FORWARD', 'OUTPUT'] } # List of tables TABLES = list(DEFAULT_CHAINS.copy().keys()) # Directory which will store the state file. - STATE_DIR = "/etc/ansible-iptables" + STATE_DIR = '/etc/ansible-iptables' # Key used for unmanaged rules - UNMANAGED_RULES_KEY_NAME = "$unmanaged_rules$" + UNMANAGED_RULES_KEY_NAME = '$unmanaged_rules$' # Only allow alphanumeric characters, underscore, hyphen, or a space for - # now. We don"t want to have problems while parsing comments using regular + # now. We don't want to have problems while parsing comments using regular # expressions. - RULE_NAME_ALLOWED_CHARS = "a-zA-Z0-9_ -" + RULE_NAME_ALLOWED_CHARS = 'a-zA-Z0-9_ -' module = None @@ -258,10 +275,10 @@ class Iptables: self.iptables_names_file = self._get_iptables_names_file(ipversion) # Check if we have a required iptables version. self._check_compatibility() - # Save active iptables rules for all tables, so that we don"t - # need to fetch them every time using "iptables-save" command. + # Save active iptables rules for all tables, so that we don't + # need to fetch them every time using 'iptables-save' command. self._active_rules = {} - self._refresh_active_rules(table="*") + self._refresh_active_rules(table='*') def __eq__(self, other): return ( @@ -273,86 +290,86 @@ class Iptables: return not self.__eq__(other) def _get_bins(self, ipversion): - if ipversion == "4": + if ipversion == '4': return { - "iptables": Iptables.module.get_bin_path("iptables"), - "iptables-save": Iptables.module.get_bin_path("iptables-save"), - "iptables-restore": Iptables.module.get_bin_path("iptables-restore") + 'iptables': Iptables.module.get_bin_path('iptables'), + 'iptables-save': Iptables.module.get_bin_path('iptables-save'), + 'iptables-restore': Iptables.module.get_bin_path('iptables-restore') } else: return { - "iptables": Iptables.module.get_bin_path("ip6tables"), - "iptables-save": Iptables.module.get_bin_path("ip6tables-save"), - "iptables-restore": Iptables.module.get_bin_path("ip6tables-restore") + 'iptables': Iptables.module.get_bin_path('ip6tables'), + 'iptables-save': Iptables.module.get_bin_path('ip6tables-save'), + 'iptables-restore': Iptables.module.get_bin_path('ip6tables-restore') } def _get_iptables_names_file(self, ipversion): - if ipversion == "4": - return "/proc/net/ip_tables_names" + if ipversion == '4': + return '/proc/net/ip_tables_names' else: - return "/proc/net/ip6_tables_names" + return '/proc/net/ip6_tables_names' # Return a list of active iptables tables def _get_list_of_active_tables(self): if os.path.isfile(self.iptables_names_file): - table_names = open(self.iptables_names_file, "r").read() + table_names = open(self.iptables_names_file, 'r').read() return table_names.splitlines() else: return [] # If /etc/debian_version exist, this means this is a debian based OS (Ubuntu, Mint, etc...) def _is_debian(self): - return os.path.isfile("/etc/debian_version") + return os.path.isfile('/etc/debian_version') # Get the iptables system save path. - # Supports RHEL/CentOS "/etc/sysconfig/" location. - # Supports Debian/Ubuntu/Mint, "/etc/iptables/" location. + # Supports RHEL/CentOS '/etc/sysconfig/' location. + # Supports Debian/Ubuntu/Mint, '/etc/iptables/' location. def _get_system_save_path(self, ipversion): # distro detection, path setting should be added - if ipversion == "4": + if ipversion == '4': if self._is_debian(): - return "/etc/iptables/rules.v4" + return '/etc/iptables/rules.v4' else: - return "/etc/sysconfig/iptables" + return '/etc/sysconfig/iptables' else: if self._is_debian(): - return "/etc/iptables/rules.v6" + return '/etc/iptables/rules.v6' else: - return "/etc/sysconfig/ip6tables" + return '/etc/sysconfig/ip6tables' # Return path to json state file. def _get_state_save_path(self, ipversion): - if ipversion == "4": - return self.STATE_DIR + "/iptables.json" + if ipversion == '4': + return self.STATE_DIR + '/iptables.json' else: - return self.STATE_DIR + "/ip6tables.json" + return self.STATE_DIR + '/ip6tables.json' # Checks if iptables is installed and if we have a correct version. def _check_compatibility(self): from distutils.version import StrictVersion - cmd = [self.bins["iptables"], "--version"] + cmd = [self.bins['iptables'], '--version'] rc, stdout, stderr = Iptables.module.run_command(cmd, check_rc=False) if rc == 0: - result = re.search(r"^ip6tables\s+v(\d+\.\d+)\.\d+$", stdout) + result = re.search(r'^ip6tables\s+v(\d+\.\d+)\.\d+$', stdout) if result: version = result.group(1) - # CentOS 5 ip6tables (v1.3.x) doesn"t support comments, + # CentOS 5 ip6tables (v1.3.x) doesn't support comments, # which means it cannot be used with this module. - if StrictVersion(version) < StrictVersion("1.4"): + if StrictVersion(version) < StrictVersion('1.4'): Iptables.module.fail_json( msg="This module isn't compatible with ip6tables versions older than 1.4.x" ) else: Iptables.module.fail_json( - msg="Could not fetch iptables version! Is iptables installed?" + msg='Could not fetch iptables version! Is iptables installed?' ) # Read rules from the json state file and return a dict. def _read_state_file(self): - json_str = "{}" + json_str = '{}' if os.path.isfile(self.state_save_path): - json_str = open(self.state_save_path, "r").read() - read_dict = defaultdict(lambda: dict(dump="", rules_dict={}), json.loads(json_str)) + json_str = open(self.state_save_path, 'r').read() + read_dict = defaultdict(lambda: dict(dump='', rules_dict={}), json.loads(json_str)) return read_dict # Checks if a table exists in the state_dict. @@ -366,9 +383,9 @@ class Iptables: # Acquires lock or exits after wait_for_seconds if it cannot be acquired. def acquire_lock_or_exit(self, wait_for_seconds=10): - lock_file = self.STATE_DIR + "/.iptables.lock" + lock_file = self.STATE_DIR + '/.iptables.lock' i = 0 - f = open(lock_file, "w+") + f = open(lock_file, 'w+') while i < wait_for_seconds: try: fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB) @@ -377,14 +394,14 @@ class Iptables: i += 1 time.sleep(1) Iptables.module.fail_json( - msg="Could not acquire lock to continue execution! " - "Probably another instance of this module is running." + msg='Could not acquire lock to continue execution! ' + 'Probably another instance of this module is running.' ) - # Check if a table has anything to flush (to check all tables pass table="*"). + # Check if a table has anything to flush (to check all tables pass table='*'). def table_needs_flush(self, table): needs_flush = False - if table == "*": + if table == '*': for tbl in Iptables.TABLES: # If the table exists or if it needs to be flushed that means will make changes. if self._has_table(tbl) or self._single_table_needs_flush(tbl): @@ -406,7 +423,7 @@ class Iptables: rules = self._filter_rules(active_rules, table) # Go over default policies and check if they are all ACCEPT. for line in policies.splitlines(): - if not re.search(r"\bACCEPT\b", line): + if not re.search(r'\bACCEPT\b', line): needs_flush = True break # If there is at least one rule or custom chain, that means we need flush. @@ -416,15 +433,15 @@ class Iptables: # Returns a copy of the rules dict of a passed table. def _get_table_rules_dict(self, table): - return self.state_dict[table]["rules_dict"].copy() + return self.state_dict[table]['rules_dict'].copy() # Returns saved table dump. def get_saved_table_dump(self, table): - return self.state_dict[table]["dump"] + return self.state_dict[table]['dump'] # Sets saved table dump. def _set_saved_table_dump(self, table, dump): - self.state_dict[table]["dump"] = dump + self.state_dict[table]['dump'] = dump # Updates saved table dump from the active rules. def refresh_saved_table_dump(self, table): @@ -437,13 +454,13 @@ class Iptables: # Return active rules of the passed table. def _get_active_rules(self, table, clean=True): - active_rules = "" - if table == "*": + active_rules = '' + if table == '*': all_rules = [] for tbl in Iptables.TABLES: if tbl in self._active_rules: all_rules.append(self._active_rules[tbl]) - active_rules = "\n".join(all_rules) + active_rules = '\n'.join(all_rules) else: active_rules = self._active_rules[table] if clean: @@ -451,28 +468,28 @@ class Iptables: else: return active_rules - # Refresh active rules of a table ("*" for all tables). + # Refresh active rules of a table ('*' for all tables). def _refresh_active_rules(self, table): - if table == "*": + if table == '*': for tbl in Iptables.TABLES: self._set_active_rules(tbl, self._get_system_active_rules(tbl)) else: self._set_active_rules(table, self._get_system_active_rules(table)) - # Get iptables-save dump of active rules of one or all tables (pass "*") + # Get iptables-save dump of active rules of one or all tables (pass '*') # and return it as a string. def _get_system_active_rules(self, table): active_tables = self._get_list_of_active_tables() - if table == "*": - cmd = [self.bins["iptables-save"]] + if table == '*': + cmd = [self.bins['iptables-save']] # If there are no active tables, that means there are no rules if not active_tables: - return "" + return '' else: - cmd = [self.bins["iptables-save"], "-t", table] + cmd = [self.bins['iptables-save'], '-t', table] # If the table is not active, that means it has no rules if table not in active_tables: - return "" + return '' rc, stdout, stderr = Iptables.module.run_command(cmd, check_rc=True) return stdout @@ -481,7 +498,7 @@ class Iptables: try: return shlex.split(rule, comments=True) except Exception: - msg = "Could not parse the iptables rule:\n{}".format(rule) + msg = 'Could not parse the iptables rule:\n%s' % rule Iptables.module.fail_json(msg=msg) # Removes comment lines and empty lines from rules. @@ -492,14 +509,14 @@ class Iptables: # Remove lines with comments and empty lines. if not (Iptables.is_comment(line) or Iptables.is_empty_line(line)): cleaned_rules.append(line) - return "\n".join(cleaned_rules) + return '\n'.join(cleaned_rules) # Checks if the line is a custom chain in specific iptables table. @staticmethod def is_custom_chain(line, table): default_chains = Iptables.DEFAULT_CHAINS[table] - if re.match(r"\s*(:|(-N|--new-chain)\s+)[^\s]+", line) and not re.match( - r"\s*(:|(-N|--new-chain)\s+)\b(" + "|".join(default_chains) + r")\b", line + if re.match(r'\s*(:|(-N|--new-chain)\s+)[^\s]+', line) and not re.match( + r'\s*(:|(-N|--new-chain)\s+)\b(' + '|'.join(default_chains) + r')\b', line ): return True else: @@ -510,7 +527,7 @@ class Iptables: def is_default_chain(line, table): default_chains = Iptables.DEFAULT_CHAINS[table] if re.match( - r"\s*(:|(-P|--policy)\s+)\b(" + "|".join(default_chains) + r")\b\s+(ACCEPT|DROP)", line + r'\s*(:|(-P|--policy)\s+)\b(' + '|'.join(default_chains) + r')\b\s+(ACCEPT|DROP)', line ): return True else: @@ -519,16 +536,16 @@ class Iptables: # Checks if a line is an iptables rule. @staticmethod def is_rule(line): - # We should only allow adding rules with "-A/--append", since others don"t make any sense. - if re.match(r"\s*(-A|--append)\s+[^\s]+", line): + # We should only allow adding rules with '-A/--append', since others don't make any sense. + if re.match(r'\s*(-A|--append)\s+[^\s]+', line): return True else: return False - # Checks if a line starts with "#". + # Checks if a line starts with '#'. @staticmethod def is_comment(line): - if re.match(r"\s*#", line): + if re.match(r'\s*#', line): return True else: return False @@ -536,7 +553,7 @@ class Iptables: # Checks if a line is empty. @staticmethod def is_empty_line(line): - if re.match(r"^$", line.strip()): + if re.match(r'^$', line.strip()): return True else: return False @@ -544,23 +561,23 @@ class Iptables: # Return name of custom chain from the rule. def _get_custom_chain_name(self, line, table): if Iptables.is_custom_chain(line, table): - return re.match(r"\s*(:|(-N|--new-chain)\s+)([^\s]+)", line).group(3) + return re.match(r'\s*(:|(-N|--new-chain)\s+)([^\s]+)', line).group(3) else: - return "" + return '' # Return name of default chain from the rule. def _get_default_chain_name(self, line, table): if Iptables.is_default_chain(line, table): - return re.match(r"\s*(:|(-N|--new-chain)\s+)([^\s]+)", line).group(3) + return re.match(r'\s*(:|(-N|--new-chain)\s+)([^\s]+)', line).group(3) else: - return "" + return '' # Return target of the default chain from the rule. def _get_default_chain_target(self, line, table): if Iptables.is_default_chain(line, table): - return re.match(r"\s*(:|(-N|--new-chain)\s+)([^\s]+)\s+([A-Z]+)", line).group(4) + return re.match(r'\s*(:|(-N|--new-chain)\s+)([^\s]+)\s+([A-Z]+)', line).group(4) else: - return "" + return '' # Removes duplicate custom chains from the table rules. def _remove_duplicate_custom_chains(self, rules, table): @@ -575,7 +592,7 @@ class Iptables: all_rules.append(line) else: all_rules.append(line) - return "\n".join(all_rules) + return '\n'.join(all_rules) # Returns current iptables-save dump cleaned from comments and packet/byte counters. def _clean_save_dump(self, simple_rules): @@ -584,13 +601,13 @@ class Iptables: # Ignore comments. if Iptables.is_comment(line): continue - # Reset counters for chains (begin with ":"), for easier comparing later on. - if re.match(r"\s*:", line): - cleaned_dump.append(re.sub(r"\[([0-9]+):([0-9]+)\]", "[0:0]", line)) + # Reset counters for chains (begin with ':'), for easier comparing later on. + if re.match(r'\s*:', line): + cleaned_dump.append(re.sub(r'\[([0-9]+):([0-9]+)\]', '[0:0]', line)) else: cleaned_dump.append(line) - cleaned_dump.append("\n") - return "\n".join(cleaned_dump) + cleaned_dump.append('\n') + return '\n'.join(cleaned_dump) # Returns lines with default chain policies. def _filter_default_chain_policies(self, rules, table): @@ -598,11 +615,11 @@ class Iptables: for line in rules.splitlines(): if Iptables.is_default_chain(line, table): chains.append(line) - return "\n".join(chains) + return '\n'.join(chains) # Returns lines with iptables rules from an iptables-save table dump # (removes chain policies, custom chains, comments and everything else). By - # default returns all rules, if "only_unmanged=True" returns rules which + # default returns all rules, if 'only_unmanged=True' returns rules which # are not managed by Ansible. def _filter_rules(self, rules, table, only_unmanaged=False): filtered_rules = [] @@ -610,31 +627,29 @@ class Iptables: if Iptables.is_rule(line): if only_unmanaged: tokens = self._split_rule_into_tokens(line) - # We need to check if a rule has a comment which starts with "ansible[name]" - if "--comment" in tokens: - comment_index = tokens.index("--comment") + 1 + # We need to check if a rule has a comment which starts with 'ansible[name]' + if '--comment' in tokens: + comment_index = tokens.index('--comment') + 1 if comment_index < len(tokens): # Fetch the comment comment = tokens[comment_index] - # Skip the rule if the comment starts with "ansible[name]" + # Skip the rule if the comment starts with 'ansible[name]' if not re.match( - r"ansible\[[" + Iptables.RULE_NAME_ALLOWED_CHARS + r"]+\]", comment + r'ansible\[[' + Iptables.RULE_NAME_ALLOWED_CHARS + r']+\]', comment ): filtered_rules.append(line) else: # Fail if there is no comment after the --comment parameter - msg = ( - "Iptables rule is missing a comment after " - "the '--comment' parameter:\n{}".format(line) - ) + msg = 'Iptables rule is missing a comment after ' \ + "the '--comment' parameter:\n%s" % line Iptables.module.fail_json(msg=msg) - # If it doesn"t have comment, this means it is not managed by Ansible + # If it doesn't have comment, this means it is not managed by Ansible # and we should append it. else: filtered_rules.append(line) else: filtered_rules.append(line) - return "\n".join(filtered_rules) + return '\n'.join(filtered_rules) # Same as _filter_rules(), but returns custom chains def _filter_custom_chains(self, rules, table, only_unmanaged=False): @@ -644,14 +659,14 @@ class Iptables: for line in rules.splitlines(): if Iptables.is_custom_chain(line, table): if only_unmanaged: - # The chain is not managed by this module if it"s not + # The chain is not managed by this module if it's not # in the list of managed custom chains. chain_name = self._get_custom_chain_name(line, table) if chain_name not in managed_custom_chains_list: filtered_chains.append(line) else: filtered_chains.append(line) - return "\n".join(filtered_chains) + return '\n'.join(filtered_chains) # Returns list of custom chains of a table. def _get_custom_chains_list(self, table): @@ -659,52 +674,50 @@ class Iptables: for key, value in self._get_table_rules_dict(table).items(): # Ignore UNMANAGED_RULES_KEY_NAME key, since we only want managed custom chains. if key != Iptables.UNMANAGED_RULES_KEY_NAME: - for line in value["rules"].splitlines(): + for line in value['rules'].splitlines(): if Iptables.is_custom_chain(line, table): chain_name = self._get_custom_chain_name(line, table) if chain_name not in custom_chains_list: custom_chains_list.append(chain_name) return custom_chains_list - # Prepends "ansible[name]: " to iptables rule "--comment" argument, - # or adds "ansible[name]" as a comment if there is no comment. + # Prepends 'ansible[name]: ' to iptables rule '--comment' argument, + # or adds 'ansible[name]' as a comment if there is no comment. def _prepend_ansible_comment(self, rules, name): commented_lines = [] for line in rules.splitlines(): # Extract rules only since we cannot add comments to custom chains. if Iptables.is_rule(line): tokens = self._split_rule_into_tokens(line) - if "--comment" in tokens: - # If there is a comment parameter, we need to prepand "ansible[name]: ". - comment_index = tokens.index("--comment") + 1 + if '--comment' in tokens: + # If there is a comment parameter, we need to prepand 'ansible[name]: '. + comment_index = tokens.index('--comment') + 1 if comment_index < len(tokens): # We need to remove double quotes from comments, since there # is an incompatiblity with older iptables versions - comment_text = tokens[comment_index].replace("'", "") - tokens[comment_index] = "ansible[" + name + "]: " + comment_text + comment_text = tokens[comment_index].replace('"', '') + tokens[comment_index] = 'ansible[' + name + ']: ' + comment_text else: # Fail if there is no comment after the --comment parameter - msg = ( - "Iptables rule is missing a comment after " - "the '--comment' parameter:\n{}".format(line) - ) + msg = 'Iptables rule is missing a comment after ' \ + "the '--comment' parameter:\n%s" % line Iptables.module.fail_json(msg=msg) else: - # If comment doesn"t exist, we add a comment "ansible[name]" - tokens += ["-m", "comment", "--comment", "ansible[" + name + "]"] + # If comment doesn't exist, we add a comment 'ansible[name]' + tokens += ['-m', 'comment', '--comment', 'ansible[' + name + ']'] # Escape and quote tokens in case they have spaces tokens = [self._escape_and_quote_string(x) for x in tokens] - commented_lines.append(" ".join(tokens)) - # Otherwise it"s a chain, and we should just return it. + commented_lines.append(' '.join(tokens)) + # Otherwise it's a chain, and we should just return it. else: commented_lines.append(line) - return "\n".join(commented_lines) + return '\n'.join(commented_lines) # Double quote a string if it contains a space and escape double quotes. def _escape_and_quote_string(self, s): - escaped = s.replace("'", r"\"") - if re.search(r"\s", escaped): - return "'" + escaped + "'" + escaped = s.replace('"', r'\"') + if re.search(r'\s', escaped): + return '"' + escaped + '"' else: return escaped @@ -712,17 +725,17 @@ class Iptables: def add_table_rule(self, table, name, weight, rules, prepend_ansible_comment=True): self._fail_on_bad_rules(rules, table) if prepend_ansible_comment: - self.state_dict[table]["rules_dict"][name] = { - "weight": weight, - "rules": self._prepend_ansible_comment(rules, name) + self.state_dict[table]['rules_dict'][name] = { + 'weight': weight, + 'rules': self._prepend_ansible_comment(rules, name) } else: - self.state_dict[table]["rules_dict"][name] = {"weight": weight, "rules": rules} + self.state_dict[table]['rules_dict'][name] = {'weight': weight, 'rules': rules} # Remove table rule from the state_dict. def remove_table_rule(self, table, name): - if name in self.state_dict[table]["rules_dict"]: - del self.state_dict[table]["rules_dict"][name] + if name in self.state_dict[table]['rules_dict']: + del self.state_dict[table]['rules_dict'][name] # TODO: Add sorting of rules so that diffs in check_mode look nicer and easier to follow. # Sorting would be done from top to bottom like this: @@ -732,18 +745,18 @@ class Iptables: # # Converts rules from a state_dict to an iptables-save readable format. def get_table_rules(self, table): - generated_rules = "" - # We first add a header e.g. "*filter". - generated_rules += "*" + table + "\n" + generated_rules = '' + # We first add a header e.g. '*filter'. + generated_rules += '*' + table + '\n' rules_list = [] custom_chains_list = [] default_chain_policies = [] dict_rules = self._get_table_rules_dict(table) - # Return list of rule names sorted by ("weight", "rules") tuple. + # Return list of rule names sorted by ('weight', 'rules') tuple. for rule_name in sorted( - dict_rules, key=lambda x: (dict_rules[x]["weight"], dict_rules[x]["rules"]) + dict_rules, key=lambda x: (dict_rules[x]['weight'], dict_rules[x]['rules']) ): - rules = dict_rules[rule_name]["rules"] + rules = dict_rules[rule_name]['rules'] # Fail if some of the rules are bad self._fail_on_bad_rules(rules, table) rules_list.append(self._filter_rules(rules, table)) @@ -757,16 +770,16 @@ class Iptables: # Since iptables-restore applies the last chain policy it reads, we # have to reverse the order of chain policies so that those with # the lowest weight (higher priority) are read last. - generated_rules += "\n".join(reversed(default_chain_policies)) + "\n" + generated_rules += '\n'.join(reversed(default_chain_policies)) + '\n' if custom_chains_list: # We remove duplicate custom chains so that iptables-restore - # doesn"t fail because of that. + # doesn't fail because of that. generated_rules += self._remove_duplicate_custom_chains( - "\n".join(sorted(custom_chains_list)), table - ) + "\n" + '\n'.join(sorted(custom_chains_list)), table + ) + '\n' if rules_list: - generated_rules += "\n".join(rules_list) + "\n" - generated_rules += "COMMIT\n" + generated_rules += '\n'.join(rules_list) + '\n' + generated_rules += 'COMMIT\n' return generated_rules # Sets unmanaged rules for the passed table in the state_dict. @@ -777,7 +790,7 @@ class Iptables: # Clears unmanaged rules of a table. def clear_unmanaged_rules(self, table): - self._set_unmanaged_rules(table, "") + self._set_unmanaged_rules(table, '') # Updates unmanaged rules of a table from the active rules. def refresh_unmanaged_rules(self, table): @@ -792,33 +805,31 @@ class Iptables: ) # Clean items which are empty strings unmanaged_chains_and_rules = list(filter(None, unmanaged_chains_and_rules)) - self._set_unmanaged_rules(table, "\n".join(unmanaged_chains_and_rules)) + self._set_unmanaged_rules(table, '\n'.join(unmanaged_chains_and_rules)) # Check if there are bad lines in the specified rules. def _fail_on_bad_rules(self, rules, table): for line in rules.splitlines(): tokens = self._split_rule_into_tokens(line) - if "-t" in tokens or "--table" in tokens: + if '-t' in tokens or '--table' in tokens: msg = ( "Iptables rules cannot contain '-t/--table' parameter. " "You should use the 'table' parameter of the module to set rules " - "for a specific table." + 'for a specific table.' ) Iptables.module.fail_json(msg=msg) - # Fail if the parameter --comment doesn"t have a comment after - if "--comment" in tokens and len(tokens) <= tokens.index("--comment") + 1: - msg = ( - "Iptables rule is missing a comment after " - "the '--comment' parameter:\n{}".format(line) - ) + # Fail if the parameter --comment doesn't have a comment after + if '--comment' in tokens and len(tokens) <= tokens.index('--comment') + 1: + msg = 'Iptables rule is missing a comment after ' \ + "the '--comment' parameter:\n%s" % line Iptables.module.fail_json(msg=msg) if not ( Iptables.is_rule(line) or Iptables.is_custom_chain(line, table) or Iptables.is_default_chain(line, table) or Iptables.is_comment(line) ): msg = ( - "Bad iptables rule '{}'! You can only use -A/--append, -N/--new-chain " - "and -P/--policy to specify rules.".format(line) + "Bad iptables rule '%s'! You can only use -A/--append, -N/--new-chain " + 'and -P/--policy to specify rules.' % line ) Iptables.module.fail_json(msg=msg) @@ -831,27 +842,27 @@ class Iptables: def _write_to_temp_file(self, text): fd, path = tempfile.mkstemp() Iptables.module.add_cleanup_file(path) # add file for cleanup later - tmp = os.fdopen(fd, "w") + tmp = os.fdopen(fd, 'w') tmp.write(text) tmp.close() return path # # Public and private methods which make changes on the system - # are named "system_*" and "_system_*", respectively. + # are named 'system_*' and '_system_*', respectively. # # Flush all rules in a passed table. def _system_flush_single_table_rules(self, table): # Set all default chain policies to ACCEPT. for chain in Iptables.DEFAULT_CHAINS[table]: - cmd = [self.bins["iptables"], "-t", table, "-P", chain, "ACCEPT"] + cmd = [self.bins['iptables'], '-t', table, '-P', chain, 'ACCEPT'] Iptables.module.run_command(cmd, check_rc=True) # Then flush all rules. - cmd = [self.bins["iptables"], "-t", table, "-F"] + cmd = [self.bins['iptables'], '-t', table, '-F'] Iptables.module.run_command(cmd, check_rc=True) # And delete custom chains. - cmd = [self.bins["iptables"], "-t", table, "-X"] + cmd = [self.bins['iptables'], '-t', table, '-X'] Iptables.module.run_command(cmd, check_rc=True) # Update active rules in the object. self._refresh_active_rules(table) @@ -862,7 +873,7 @@ class Iptables: if backup: Iptables.module.backup_local(self.system_save_path) # Get iptables-save dump of all tables - all_active_rules = self._get_active_rules(table="*", clean=False) + all_active_rules = self._get_active_rules(table='*', clean=False) # Move iptables-save dump of all tables to the iptables_save_path self._write_rules_to_file(all_active_rules, self.system_save_path) @@ -870,28 +881,28 @@ class Iptables: def system_apply_table_rules(self, table, test=False): dump_path = self._write_to_temp_file(self.get_table_rules(table)) if test: - cmd = [self.bins["iptables-restore"], "-t", dump_path] + cmd = [self.bins['iptables-restore'], '-t', dump_path] else: - cmd = [self.bins["iptables-restore"], dump_path] + cmd = [self.bins['iptables-restore'], dump_path] rc, stdout, stderr = Iptables.module.run_command(cmd, check_rc=False) if rc != 0: if test: - dump_contents_file = open(dump_path, "r") + dump_contents_file = open(dump_path, 'r') dump_contents = dump_contents_file.read() dump_contents_file.close() - msg = "There is a problem with the iptables rules:" \ - + "\n\nError message:\n" \ + msg = 'There is a problem with the iptables rules:' \ + + '\n\nError message:\n' \ + stderr \ - + "\nGenerated rules:\n#######\n" \ - + dump_contents + "#####" + + '\nGenerated rules:\n#######\n' \ + + dump_contents + '#####' else: - msg = "Could not load iptables rules:\n\n" + stderr + msg = 'Could not load iptables rules:\n\n' + stderr Iptables.module.fail_json(msg=msg) self._refresh_active_rules(table) - # Flush one or all tables (to flush all tables pass table="*"). + # Flush one or all tables (to flush all tables pass table='*'). def system_flush_table_rules(self, table): - if table == "*": + if table == '*': for tbl in Iptables.TABLES: self._delete_table(tbl) if self._single_table_needs_flush(tbl): @@ -905,7 +916,7 @@ class Iptables: # Saves state file and system iptables rules. def system_save(self, backup=False): self._system_save_active(backup=backup) - rules = json.dumps(self.state_dict, sort_keys=True, indent=4, separators=(",", ": ")) + rules = json.dumps(self.state_dict, sort_keys=True, indent=4, separators=(',', ': ')) self._write_rules_to_file(rules, self.state_save_path) @@ -913,32 +924,32 @@ def main(): module = AnsibleModule( argument_spec=dict( - ipversion=dict(required=False, choices=["4", "6"], type="str", default="4"), + ipversion=dict(required=False, choices=['4', '6'], type='str', default='4'), state=dict( - required=False, choices=["present", "absent"], default="present", type="str" + required=False, choices=['present', 'absent'], default='present', type='str' ), - weight=dict(required=False, type="int", default=40), - name=dict(required=True, type="str"), + weight=dict(required=False, type='int', default=40), + name=dict(required=True, type='str'), table=dict( - required=False, choices=Iptables.TABLES + ["*"], default="filter", type="str" + required=False, choices=Iptables.TABLES + ['*'], default='filter', type='str' ), - rules=dict(required=False, type="str", default=""), - backup=dict(required=False, type="bool", default=False), - keep_unmanaged=dict(required=False, type="bool", default=True), + rules=dict(required=False, type='str', default=''), + backup=dict(required=False, type='bool', default=False), + keep_unmanaged=dict(required=False, type='bool', default=True), ), supports_check_mode=True, ) check_mode = module.check_mode changed = False - ipversion = module.params["ipversion"] - state = module.params["state"] - weight = module.params["weight"] - name = module.params["name"] - table = module.params["table"] - rules = module.params["rules"] - backup = module.params["backup"] - keep_unmanaged = module.params["keep_unmanaged"] + ipversion = module.params['ipversion'] + state = module.params['state'] + weight = module.params['weight'] + name = module.params['name'] + table = module.params['table'] + rules = module.params['rules'] + backup = module.params['backup'] + keep_unmanaged = module.params['keep_unmanaged'] kw = dict( state=state, @@ -961,24 +972,24 @@ def main(): rules = Iptables.clean_up_rules(rules) # Check additional parameter requirements - if state == "present" and name == "*": + if state == 'present' and name == '*': module.fail_json(msg="Parameter 'name' can only be '*' if 'state=absent'") - if state == "present" and table == "*": + if state == 'present' and table == '*': module.fail_json(msg="Parameter 'table' can only be '*' if 'name=*' and 'state=absent'") - if state == "present" and not name: + if state == 'present' and not name: module.fail_json(msg="Parameter 'name' cannot be empty") - if state == "present" and not re.match("^[" + Iptables.RULE_NAME_ALLOWED_CHARS + "]+$", name): + if state == 'present' and not re.match('^[' + Iptables.RULE_NAME_ALLOWED_CHARS + ']+$', name): module.fail_json( msg="Parameter 'name' not valid! It can only contain alphanumeric characters, " - "underscore, hyphen, or a space, got: '{}'".format(name) + "underscore, hyphen, or a space, got: '%s'" % name ) if weight < 0 or weight > 99: - module.fail_json(msg="Parameter 'weight' can be 0-99, got: {}".format(weight)) - if state == "present" and rules == "": + module.fail_json(msg="Parameter 'weight' can be 0-99, got: %d" % weight) + if state == 'present' and rules == '': module.fail_json(msg="Parameter 'rules' cannot be empty when 'state=present'") # Flush rules of one or all tables - if state == "absent" and name == "*": + if state == 'absent' and name == '*': # Check if table(s) need to be flushed if iptables.table_needs_flush(table): changed = True @@ -988,13 +999,13 @@ def main(): # Save state and system iptables rules iptables.system_save(backup=backup) # Exit since there is nothing else to do - kw["changed"] = changed + kw['changed'] = changed module.exit_json(**kw) # Initialize new iptables object which will store new rules iptables_new = Iptables(module, ipversion) - if state == "present": + if state == 'present': iptables_new.add_table_rule(table, name, weight, rules) else: iptables_new.remove_table_rule(table, name) @@ -1018,7 +1029,7 @@ def main(): if check_mode: # Create a predicted diff for check_mode. # Diff will be created from rules generated from the state dictionary. - if hasattr(module, "_diff") and module._diff: + if hasattr(module, '_diff') and module._diff: # Update unmanaged rules in the old object so the generated diff # from the rules dictionaries is more accurate. iptables.refresh_unmanaged_rules(table) @@ -1027,13 +1038,13 @@ def main(): table_rules_new = iptables_new.get_table_rules(table) # If rules generated from dicts are not equal, we generate a diff from them. if table_rules_old != table_rules_new: - kw["diff"] = generate_diff(table_rules_old, table_rules_new) + kw['diff'] = generate_diff(table_rules_old, table_rules_new) else: # TODO: Update this comment to be better. - kw["diff"] = { - "prepared": - "System rules were not changed (e.g. rule " - "weight changed, redundant rule, etc)" + kw['diff'] = { + 'prepared': + 'System rules were not changed (e.g. rule ' + 'weight changed, redundant rule, etc)' } else: # We need to fetch active table dump before we apply new rules @@ -1050,21 +1061,21 @@ def main(): iptables_new.system_save(backup=backup) # Generate a diff. - if hasattr(module, "_diff") and module._diff: + if hasattr(module, '_diff') and module._diff: table_active_rules_new = iptables_new.get_saved_table_dump(table) if table_active_rules != table_active_rules_new: - kw["diff"] = generate_diff(table_active_rules, table_active_rules_new) + kw['diff'] = generate_diff(table_active_rules, table_active_rules_new) else: # TODO: Update this comment to be better. - kw["diff"] = { - "prepared": - "System rules were not changed (e.g. rule " - "weight changed, redundant rule, etc)" + kw['diff'] = { + 'prepared': + 'System rules were not changed (e.g. rule ' + 'weight changed, redundant rule, etc)' } - kw["changed"] = changed + kw['changed'] = changed module.exit_json(**kw) -if __name__ == "__main__": +if __name__ == '__main__': main()