use original iptables_raw
This commit is contained in:
parent
2e810707d0
commit
4847473772
1
.flake8
1
.flake8
@ -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
|
||||||
|
@ -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()
|
||||||
|
Loading…
Reference in New Issue
Block a user