use original iptables_raw
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/tag Build is passing Details

This commit is contained in:
Robert Kaussow 2020-08-22 14:49:44 +02:00
parent 2e810707d0
commit 4847473772
Signed by: xoxys
GPG Key ID: 65362AE74AF98B61
2 changed files with 239 additions and 227 deletions

View File

@ -14,5 +14,6 @@ exclude =
.cache .cache
.eggs .eggs
env* env*
iptables_raw.py
application-import-names = ansiblelater application-import-names = ansiblelater
format = ${cyan}%(path)s:%(row)d:%(col)d${reset}: ${red_bold}%(code)s${reset} %(text)s format = ${cyan}%(path)s:%(row)d:%(col)d${reset}: ${red_bold}%(code)s${reset} %(text)s

View File

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