Refactor from the scratch
Customer-Found
Prod-Related: CEEMCP-19
Prod-Related: PROD-22620
Change-Id: Ib14838becc409c2f735d93b5fa8a8ead6ea1a5ec
diff --git a/.kitchen.yml b/.kitchen.yml
index 497eecc..89e569c 100644
--- a/.kitchen.yml
+++ b/.kitchen.yml
@@ -36,8 +36,7 @@
suites:
- - name: iptables_server
+ - name: iptables
provisioner:
pillars-from-files:
- iptables.sls: tests/pillar/iptables_server.sls
-# vim: ft=yaml sw=2 ts=2 sts=2 tw=125
+ iptables.sls: tests/pillar/iptables.sls
diff --git a/.travis.yml b/.travis.yml
index 533ccac..d5b5acc 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -20,11 +20,11 @@
- bundle install
env:
- - PLATFORM=epcim/salt:saltstack-ubuntu-xenial-salt-2016.3 SUITE=iptables-server
- - PLATFORM=epcim/salt:saltstack-ubuntu-xenial-salt-2017.7 SUITE=iptables-server
- - PLATFORM=epcim/salt:saltstack-ubuntu-xenial-salt-2018.3 SUITE=iptables-server
-# - PLATFORM=epcim/salt:saltstack-ubuntu-bionic-salt-2017.7 SUITE=iptables-server
-# - PLATFORM=epcim/salt:saltstack-ubuntu-bionic-salt-2018.3 SUITE=iptables-server
+ - PLATFORM=epcim/salt:saltstack-ubuntu-xenial-salt-2016.3 SUITE=iptables
+ - PLATFORM=epcim/salt:saltstack-ubuntu-xenial-salt-2017.7 SUITE=iptables
+ - PLATFORM=epcim/salt:saltstack-ubuntu-xenial-salt-2018.3 SUITE=iptables
+# - PLATFORM=epcim/salt:saltstack-ubuntu-bionic-salt-2017.7 SUITE=iptables
+# - PLATFORM=epcim/salt:saltstack-ubuntu-bionic-salt-2018.3 SUITE=iptables
before_script:
- set -o pipefail
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index fa0bbc5..c14ec9b 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -1,7 +1,6 @@
+iptables-salt-formula
+=====================
-iptables-formula
-================
+1.0.0
-0.0.1 (2015-08-03)
-
-- Initial formula setup
\ No newline at end of file
+- Initial setup
diff --git a/LICENSE b/LICENSE
index 6f2b42f..cd00389 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2014-2015 tcp cloud a.s.
+Copyright (c) 2018 Mirantis a.s.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -10,4 +10,4 @@
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
-limitations under the License.
\ No newline at end of file
+limitations under the License.
diff --git a/README.rst b/README.rst
index c079424..a59e5e8 100644
--- a/README.rst
+++ b/README.rst
@@ -1,7 +1,6 @@
-
-================
-iptables formula
-================
+=====================
+iptables salt formula
+=====================
Iptables is used to set up, maintain, and inspect the tables of IPv4 packet
filter rules in the Linux kernel. Several different tables may be defined.
@@ -11,154 +10,76 @@
called a `target`, which may be a jump to a user-defined chain in the same
table.
+This version of a formula guarantees that manually added rules or rules which
+has been added in runtime would be removed.
+
+In order to ensure architecture, proper epoch value should be specified.
+Refer to an example.
+
Sample pillars
==============
-Most common rules - allow traffic on localhost, accept related,established and
-ping
-
.. code-block:: yaml
parameters:
iptables:
+ schema:
+ epoch: 1
service:
- enabled: True
- chain:
- INPUT:
- rules:
- - in_interface: lo
- jump: ACCEPT
- - connection_state: RELATED,ESTABLISHED
- match: state
- jump: ACCEPT
- - protocol: icmp
- jump: ACCEPT
+ v4:
+ enabled: true
+ persistent_config: /etc/iptables.v4
+ modules:
+ - nf_conntrack_ftp
+ - nf_conntrack_pptp
+ v6:
+ enabled: false
+ persistent_config: /etc/iptables.v6
+ modules:
+ - nf_conntrack_ipv6
+ defaults:
+ v4:
+ metadata_rules: false
+ policy: ACCEPT
+ ruleset:
+ action: ACCEPT
+ params: ""
+ rule: ""
+ v6:
+ metadata_rules: false
+ policy: DROP
+ ruleset:
+ action: ACCEPT
+ params: ""
+ rule: ""
+ tables:
+ v4:
+ filter:
+ chains:
+ INPUT:
+ ruleset:
+ 5:
+ action: log_drop
+ 10:
+ rule: -s 192.168.0.0/24 -p tcp
+ log_drop:
+ policy: DROP
+ ruleset:
+ 10:
+ action: LOG
+ comment: "Log my packets"
+ nat:
+ chains:
+ OUTPUT:
+ PREROUTING:
+ POSTROUTING:
+ policy: ACCEPT
+ ruleset:
+ 10:
+ rule: -s 192.168.0.0/24 -p tcp -o lo
+ action: SNAT
+ params: --to-source=127.0.0.1
-Accept connections on port 22
-
-.. code-block:: yaml
-
- parameters:
- iptables:
- service:
- chain:
- INPUT:
- rules:
- - destination_port: 22
- protocol: tcp
- jump: ACCEPT
-
-Set drop policy on INPUT chain:
-
-.. code-block:: yaml
-
- parameters:
- iptables:
- service:
- chain:
- INPUT:
- policy: DROP
-
-Redirect privileged port 443 to 8081
-
-.. code-block:: yaml
-
- parameters:
- iptables:
- service:
- chain:
- PREROUTING:
- filter: nat
- destination_port: 443
- to_port: 8081
- protocol: tcp
- jump: REDIRECT
-
-Allow access from local network
-
-.. code-block:: yaml
-
- parameters:
- iptables:
- service:
- chain:
- INPUT:
- rules:
- - protocol: tcp
- destination_port: 22
- source_network: 192.168.1.0/24
- jump: ACCEPT
- comment: Blah
-
-Support logging with custom prefix and log level
-
-.. code-block:: yaml
-
- parameters:
- iptables:
- service:
- chain:
- POSTROUTING:
- rules:
- - table: nat
- protocol: tcp
- match: multiport
- destination_ports:
- - 21
- - 80
- - 443
- - 2220
- source_network: '10.20.30.0/24'
- log_level: 7
- log_prefix: 'iptables-logging: '
- jump: LOG
-
-
-IPv6 is supported as well
-
-.. code-block:: yaml
-
- parameters:
- iptables:
- service:
- enabled: True
- ipv6: True
- chain:
- INPUT:
- rules:
- - protocol: tcp
- family: ipv6
- destination_port: 22
- source_network: 2001:DB8::/32
- jump: ACCEPT
-
-
-You may set policy for chain in specific table
-If 'table' key is omitted, 'filter' table is assumed
-
-.. code-block:: yaml
-
- parameters:
- iptables:
- service:
- enabled: true
- chain:
- OUTPUT:
- policy: ACCEPT
-
-Specify policy directly
-
-.. code-block:: yaml
-
- parameters:
- iptables:
- service:
- enabled: true
- chain:
- FORWARD:
- policy:
- - table: mangle
- policy: DROP
Read more
=========
@@ -181,21 +102,9 @@
https://github.com/salt-formulas/salt-formula-iptables/issues
-For feature requests, bug reports or blueprints affecting entire ecosystem,
-use Launchpad salt-formulas project:
-
- https://launchpad.net/salt-formulas
-
-You can also join salt-formulas-users team and subscribe to mailing list:
-
- https://launchpad.net/~salt-formulas-users
Developers wishing to work on the salt-formulas projects should always base
their work on master branch and submit pull request against specific formula.
https://github.com/salt-formulas/salt-formula-iptables
-Any questions or feedback is always welcome so feel free to join our IRC
-channel:
-
- #salt-formulas @ irc.freenode.net
diff --git a/VERSION b/VERSION
index 3b04cfb..3eefcb9 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.2
+1.0.0
diff --git a/_modules/iptables_extra.py b/_modules/iptables_extra.py
new file mode 100644
index 0000000..90d421e
--- /dev/null
+++ b/_modules/iptables_extra.py
@@ -0,0 +1,231 @@
+# -*- coding: utf-8 -*-
+from os import chmod,remove
+from time import time
+from subprocess import Popen,PIPE
+
+def get_tables(family="ipv4"):
+
+ ''' List iptables tables
+
+ :param family: iptables ip family version. type: str
+
+ '''
+
+ if family == "ipv4":
+ cmd = 'iptables-save'
+ elif family == "ipv6":
+ cmd = 'ip6tables-save'
+ else:
+ return "Invalid ip family specified. Use either ipv4 or ipv6"
+
+ tables = []
+ try:
+ tables_list = Popen(cmd, shell=True, stdout=PIPE)
+ while True:
+ line = tables_list.stdout.readline()
+ if line != '':
+ if line[0] == "*":
+ tables.append(line.rstrip()[1:])
+ else:
+ break
+ except:
+ return "Error getting list of tables"
+
+ return tables
+
+def get_chains(family="ipv4", table="filter"):
+
+ ''' List iptables chains
+
+ :param family: iptables ip family version. type: str
+ :param table: Lookup chains for this table. type: str
+
+ '''
+
+ if family == "ipv4":
+ cmd = 'iptables-save'
+ elif family == "ipv6":
+ cmd = 'ip6tables-save'
+ else:
+ return "Invalid ip family specified. Use either ipv4 or ipv6"
+
+ cmd += ' -t ' + table
+
+ chains = []
+ try:
+ chains_list = Popen(cmd, shell=True, stdout=PIPE)
+ while True:
+ line = chains_list.stdout.readline()
+ if line != '':
+ if line[0] == ":":
+ chains.append(line.rstrip()[1:].split(' ')[0])
+ else:
+ break
+ except:
+ return "Error getting list of chains"
+
+ return chains
+
+def get_structure(family="ipv4"):
+
+ ''' Get structure of all chains in all tables
+
+ :param family: iptables ip family version. type: str
+
+ '''
+
+ if family == "ipv4":
+ cmd = 'iptables-save'
+ elif family == "ipv6":
+ cmd = 'ip6tables-save'
+ else:
+ return "Invalid ip family specified. Use either ipv4 or ipv6"
+
+ tables = []
+ tables_list = Popen(cmd, shell=True, stdout=PIPE)
+ while True:
+ line = tables_list.stdout.readline()
+ if line != '':
+ line = line.rstrip().lstrip()
+ if line[0] == "*":
+ elem = {}
+ table_name = line[1:].split(' ')[0]
+ elem[table_name] = []
+ if line[0] == ":":
+ elem[table_name].append(line[1:].split(' ')[0])
+ if line == "COMMIT":
+ tables.append(elem)
+ else:
+ break
+
+ return tables
+
+def run_script(script):
+
+ ''' Execute local script
+
+ :param script: script to be executed, storad localy: str
+
+ '''
+
+ chmod(script, 0o700)
+ process = Popen([script],stdout=PIPE,stderr=PIPE)
+ process.wait()
+ code = process.returncode
+ remove(script)
+ return code
+
+
+def flush_all(family="ipv4"):
+
+ ''' Flush all chains in all tables
+
+ :param family: iptables ip family version. type: str
+
+ '''
+
+ if family == "ipv4":
+ cmd = 'iptables'
+ rmmod = 'iptable_'
+ elif family == "ipv6":
+ cmd = 'ip6tables'
+ rmmod = 'ip6table_'
+ else:
+ return "Invalid ip family specified. Use either ipv4 or ipv6"
+
+ tables = get_structure(family)
+
+ f_name = '/tmp/' + cmd + '-flush-' + str(time()).split('.')[0] + '.sh'
+
+ with open(f_name, 'w') as f:
+ f.write('#!/bin/sh\n')
+ for table in tables:
+ for var in enumerate(table):
+ t_name = var[1]
+ for chain in table[t_name]:
+ f.write(cmd + ' -t ' + t_name + " -F " + chain + '\n')
+ if chain not in ['INPUT','FORWARD','OUTPUT','PREROUTING','POSTROUTING']:
+ f.write(cmd + ' -t ' + t_name + " -X " + chain + '\n')
+ f.write('rmmod ' + rmmod + t_name + '\n')
+
+ return run_script(f_name)
+
+def set_policy_all(family="ipv4", policy="ACCEPT"):
+
+ ''' Set policy for all chains in all tables
+
+ :param family: iptables ip family version. type: str
+ :param policy: iptables chain policy. type: str
+
+ '''
+
+ if family == "ipv4":
+ cmd = 'iptables'
+ elif family == "ipv6":
+ cmd = 'ip6tables'
+ else:
+ return "Invalid ip family specified. Use either ipv4 or ipv6"
+
+ tables = get_structure(family)
+
+ f_name = '/tmp/' + cmd + '-policy-' + str(time()).split('.')[0] + '.sh'
+
+ with open(f_name, 'w') as f:
+ f.write('#!/bin/sh\n')
+ for table in tables:
+ for var in enumerate(table):
+ t_name = var[1]
+ for chain in table[t_name]:
+ f.write(cmd + ' -t ' + t_name + " -P " + chain + ' ' + policy + '\n')
+
+ return run_script(f_name)
+
+def remove_stale_tables(config_file, family="ipv4"):
+
+ ''' Remove tables which are not in config file
+ to prevet flushing all the tables
+
+ :param family: iptables ip family version. type: str
+ :param config_file: iptables rules persistent config file. type: str
+
+ '''
+
+ if family == "ipv4":
+ cmd = 'iptables'
+ rmmod = 'iptable_'
+ elif family == "ipv6":
+ cmd = 'ip6tables'
+ rmmod = 'ip6table_'
+ else:
+ return "Invalid ip family specified. Use either ipv4 or ipv6"
+
+ runtime_tables = get_tables(family)
+
+ config_tables = []
+ for line in open(config_file, 'r'):
+ if line != '':
+ if line[0] == "*":
+ config_tables.append(line.rstrip()[1:])
+
+ runtime_tables.sort()
+ config_tables.sort()
+ diff = list(set(runtime_tables) - set(config_tables))
+
+ if diff != []:
+ tables = get_structure(family)
+ f_name = '/tmp/' + cmd + '-flush-' + str(time()).split('.')[0] + '.sh'
+ with open(f_name, 'w') as f:
+ f.write('#!/bin/sh\n')
+ for table in tables:
+ for var in enumerate(table):
+ t_name = var[1]
+ if t_name in diff:
+ for chain in table[t_name]:
+ f.write(cmd + ' -t ' + t_name + " -F " + chain + '\n')
+ if chain not in ['INPUT','FORWARD','OUTPUT','PREROUTING','POSTROUTING']:
+ f.write(cmd + ' -t ' + t_name + " -X " + chain + '\n')
+ f.write('rmmod ' + rmmod + t_name + '\n')
+
+ return run_script(f_name)
+ else:
+ return
diff --git a/debian/changelog b/debian/changelog
index 02b2933..18b8056 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,11 +1,5 @@
-salt-formula-iptables (0.2) trusty; urgency=medium
-
- * First public release
-
- -- Filip Pytloun <filip.pytloun@tcpcloud.eu> Tue, 06 Oct 2015 16:38:43 +0200
-
-salt-formula-iptables (0.1) trusty; urgency=medium
+salt-formula-iptables (1.0) xenial; urgency=medium
* Initial release
- -- Jan Kaufman <jan.kaufman@tcpcloud.eu> Thu, 13 Aug 2015 23:23:41 +0200
+ -- Dzmitry Stremkouski <dstremkouski@mirantis.com> Thu, 30 Aug 2018 16:20:23 +0100
diff --git a/debian/control b/debian/control
index bd27c49..c0d03f0 100644
--- a/debian/control
+++ b/debian/control
@@ -1,15 +1,15 @@
Source: salt-formula-iptables
-Maintainer: Jan Kaufman <jan.kaufman@tcpcloud.eu>
+Maintainer: Dzmitry Stremkouski <dstremkouski@mirantis.com>
Section: admin
-Priority: optional
+Priority: extra
Build-Depends: salt-master, python, python-yaml, debhelper (>= 9)
Standards-Version: 3.9.6
-Homepage: http://www.tcpcloud.eu
-Vcs-Browser: https://github.com/tcpcloud/salt-formula-iptables
-Vcs-Git: https://github.com/tcpcloud/salt-formula-iptables.git
+Homepage: https://mirantis.com
+Vcs-Browser: https://github.com/Mirantis/salt-formula-iptables
+Vcs-Git: https://github.com/Mirantis/salt-formula-iptables.git
Package: salt-formula-iptables
Architecture: all
Depends: ${misc:Depends}
Description: iptables salt formula
- Configure iptables rules.
+ Manages iptables rules.
diff --git a/debian/copyright b/debian/copyright
index 8f82be0..5332ddf 100644
--- a/debian/copyright
+++ b/debian/copyright
@@ -1,10 +1,10 @@
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: salt-formula-iptables
-Upstream-Contact: Ales Komarek <ales.komarek@tcpcloud.eu>
-Source: https://github.com/tcpcloud/salt-formula-iptables
+Upstream-Contact: Dzmitry Stremkouski <dstremkouski@mirantis.com>
+Source: https://github.com/Mirantis/salt-formula-iptables
Files: *
-Copyright: 2014-2015 tcp cloud a.s.
+Copyright: 2018 Mirantis a.s.
License: Apache-2.0
Copyright (C) 2014-2015 tcp cloud a.s.
.
diff --git a/iptables/_rule.sls b/iptables/_rule.sls
deleted file mode 100644
index e967fae..0000000
--- a/iptables/_rule.sls
+++ /dev/null
@@ -1,94 +0,0 @@
-{%- set table = rule.get('table', 'filter') %}
-iptables_{{ table }}_{{ chain_name }}_{{ rule_name }}:
- {%- if rule.position is defined %}
- iptables.insert:
- - position: {{ rule.position }}
- {%- else %}
- iptables.append:
- - require:
- {%- if loop.index != 1 %}
- - iptables: iptables_{{ table }}_{{ chain_name }}_{% if service_name is defined %}{{ service_name }}_{% endif %}{{ loop.index - 1 }}
- {%- else %}
- {%- for chain in chains %}
- - iptables: iptables_{{ table }}_{{ chain }}
- {%- endfor %}
- {%- endif %}
- {%- endif %}
- - table: {{ table }}
- - chain: {{ chain_name }}
- {%- if rule.family is defined %}
- - family: {{ rule.family }}
- {%- endif %}
- {%- if rule.jump is defined %}
- - jump: {{ rule.jump }}
- {%- endif %}
- {%- if rule.match is defined %}
- - match: {{ rule.match }}
- {%- endif %}
- {%- if rule.comment is defined %}
- - comment: {{ rule.comment }}
- {%- endif %}
- {%- if rule.connection_state is defined %}
- - connstate: {{ rule.connection_state }}
- {%- endif %}
- {%- if rule.protocol is defined %}
- - proto: {{ rule.protocol }}
- {%- endif %}
- {%- if rule.destination_port is defined %}
- - dport: {{ rule.destination_port }}
- {%- endif %}
- {%- if rule.destination_ports is defined %}
- - dports:
- {%- for port in rule.destination_ports %}
- - {{ port }}
- {% endfor %}
- {%- endif %}
- {%- if rule.source_port is defined %}
- - sport: {{ rule.source_port }}
- {%- endif %}
- {%- if rule.in_interface is defined %}
- - in-interface: {{ rule.in_interface }}
- {%- endif %}
- {%- if rule.out_interface is defined %}
- - out-interface: {{ rule.out_interface }}
- {%- endif %}
- {%- if rule.to_destination is defined %}
- - to-destination: {{ rule.to_destination }}
- {%- endif %}
- {%- if rule.to_port is defined %}
- - to-port: {{ rule.to_port }}
- {%- endif %}
- {%- if rule.to_source is defined %}
- - to-source: {{ rule.to_source }}
- {%- endif %}
- {%- if rule.source_network is defined %}
- - source: {{ rule.source_network }}
- {%- endif %}
- {%- if rule.destination_network is defined %}
- - destination: {{ rule.destination_network }}
- {%- endif %}
- {%- if rule.log_prefix is defined %}
- - log-prefix: '{{ rule.log_prefix }}'
- {%- endif %}
- {%- if rule.log_level is defined %}
- - log-level: {{ rule.log_level }}
- {%- endif %}
- {%- if rule.limit is defined %}
- - limit: '{{ rule.limit }}'
- {%- endif %}
- {%- if chain.policy is defined %}
- {%- if chain.policy is string %}
- - require_in:
- - iptables: iptables_filter_{{ chain_name }}_policy
- {%- else %}
- {%- if table in chain.policy %}
- - require_in:
- - iptables: iptables_{{ table }}_{{ chain_name }}_policy
- {%- endif %}
- {%- endif %}
- {%- endif %}
- {%- if grains.get('virtual_subtype', None) not in ['Docker', 'LXC'] %}
- - require:
- - iptables: iptables_{{ table}}_{{ chain_name }}{% if rule.family is defined %}_{{ rule.family }}{% endif %}
- {%- endif %}
- - save: True
diff --git a/iptables/init.sls b/iptables/init.sls
index 43a97fc..9690eff 100644
--- a/iptables/init.sls
+++ b/iptables/init.sls
@@ -1,4 +1,21 @@
+{%- from "iptables/map.jinja" import schema with context %}
+
+{%- set include_allowed = true %}
+{%- if grains.get('virtual_subtype', None) in ['Docker', 'LXC'] %}
+{%- set include_allowed = false %}
+echo_usupported_environment:
+ cmd.run:
+ - name: echo "You are trying to use iptables inside of docker or lxc. Kernel modules loading are not supported here"
+{%- endif %}
+
+{%- if pillar.iptables.service.enabled is defined %}
+{%- set include_allowed = false %}
+echo_usupported_pillars_schema:
+ cmd.run:
+ - name: echo "You are trying to use old style pillars schema. Please update pillars according to the current schema"
+{%- endif %}
+
+{%- if include_allowed %}
include:
-{%- if pillar.iptables.service is defined %}
-- iptables.service
-{%- endif %}
\ No newline at end of file
+- iptables.v{{ schema.epoch }}
+{%- endif %}
diff --git a/iptables/map.jinja b/iptables/map.jinja
index 2562cb2..8ad45f6 100644
--- a/iptables/map.jinja
+++ b/iptables/map.jinja
@@ -1,15 +1,85 @@
+{% set schema = salt['grains.filter_by']({
+ 'default': {
+ },
+}, grain='os_family', merge=salt['pillar.get']('iptables:schema')) %}
{% set service = salt['grains.filter_by']({
- 'Debian': {
- 'pkgs': ['iptables','iptables-persistent' ],
- 'service': 'netfilter-persistent',
- },
'RedHat': {
- 'pkgs': ['iptables'],
- 'service': 'iptables',
+ 'v4': {
+ 'enabled': true,
+ 'persistent_config': '/etc/sysconfig/iptables',
+ 'pkgs': [' iptables' ],
+ 'service': 'iptables',
+ 'modules': [],
+ },
+ 'v6': {
+ 'enabled': true,
+ 'persistent_config': '/etc/sysconfig/ip6tables',
+ 'pkgs': [ 'iptables' ],
+ 'service': 'iptables',
+ 'modules': [],
+ },
},
-}, merge=salt['grains.filter_by']({
- 'trusty': {
- 'service': 'iptables-persistent',
+ 'Debian': {
+ 'v4': {
+ 'enabled': true,
+ 'persistent_config': '/etc/iptables/rules.v4',
+ 'pkgs': [ 'iptables','iptables-persistent' ],
+ 'service': 'netfilter-persistent',
+ 'modules': [ 'iptable_filter', 'ip_tables' ],
+ },
+ 'v6': {
+ 'enabled': true,
+ 'persistent_config': '/etc/iptables/rules.v6',
+ 'pkgs': [ 'iptables','iptables-persistent' ],
+ 'service': 'netfilter-persistent',
+ 'modules': [ 'ip6table_filter', 'ip6_tables' ],
+ },
},
-}, grain='oscodename', merge=salt['pillar.get']('iptables:service'))) %}
+}, grain='os_family', merge=salt['pillar.get']('iptables:service')) %}
+
+{% set defaults = salt['grains.filter_by']({
+ 'default': {
+ 'v4': {
+ 'metadata_rules': false,
+ 'policy': 'ACCEPT',
+ 'ruleset': {
+ 'action': 'ACCEPT',
+ 'params': '',
+ 'rule': '',
+ },
+ },
+ 'v6': {
+ 'metadata_rules': false,
+ 'policy': 'ACCEPT',
+ 'ruleset': {
+ 'action': 'ACCEPT',
+ 'params': '',
+ 'rule': '',
+ },
+ },
+ },
+}, grain='os_family', merge=salt['pillar.get']('iptables:defaults')) %}
+
+{% set tables = salt['grains.filter_by']({
+ 'default': {
+ 'v4': {
+ 'filter': {
+ 'chains': {
+ 'INPUT': {},
+ 'OUTPUT': {},
+ 'FORWARD': {},
+ },
+ },
+ },
+ 'v6': {
+ 'filter': {
+ 'chains': {
+ 'INPUT': {},
+ 'OUTPUT': {},
+ 'FORWARD': {},
+ },
+ },
+ },
+ },
+}, grain='os_family', merge=salt['pillar.get']('iptables:tables')) %}
diff --git a/iptables/rules.sls b/iptables/rules.sls
deleted file mode 100644
index 33c4747..0000000
--- a/iptables/rules.sls
+++ /dev/null
@@ -1,117 +0,0 @@
-{% from "iptables/map.jinja" import service with context %}
-{%- if grains.get('virtual_subtype', None) not in ['Docker', 'LXC'] %}
-
-{%- set chains = service.get('chain', {}).keys() %}
-{%- for chain_name, chain in service.get('chain', {}).iteritems() %}
-
-{%- set tables = [] %}
-{%- for rule in chain.get('rules', []) %}
-{%- set table = rule.get('table', 'filter') %}
-{%- if table not in tables %}
-{%- do tables.append(table) %}
-{%- endif %}
-{%- endfor %}
-{%- if chain.policy is defined %}
-{%- if chain.policy is string %}
-{%- if 'filter' not in tables %}
-{%- do tables.append('filter') %}
-{%- endif %}
-{%- else %}
-{%- for policy in chain.policy %}
-{%- if policy.table not in tables %}
-{%- do tables.append(policy.table) %}
-{%- endif %}
-{%- endfor %}
-{%- endif %}
-{%- endif %}
-
-{%- for table in tables %}
-iptables_{{ table }}_{{ chain_name }}:
- iptables.chain_present:
- - family: ipv4
- - name: {{ chain_name }}
- - table: {{ table }}
- - require:
- - pkg: iptables_packages
-
-{%- if grains.ipv6|default(False) and service.ipv6|default(True) %}
-iptables_{{ table }}_{{ chain_name }}_ipv6:
- iptables.chain_present:
- - family: ipv6
- - name: {{ chain_name }}
- - table: {{ table }}
- - require:
- - pkg: iptables_packages
-{%- if chain.policy is defined %}
-{%- if chain.policy is string %}
- - require_in:
- - iptables: iptables_filter_{{ chain_name }}_ipv6_policy
-{%- else %}
-{%- if table in chain.policy %}
- - require_in:
- - iptables: iptables_filter_{{ chain_name }}_ipv6_policy
-{%- endif %}
-{%- endif %}
-{%- endif %}
-{%- endif %}
-{%- endfor %}
-
-{%- if chain.policy is defined %}
-
-{%- if chain.policy is string %}
-{%- set map = [{'table':'filter', 'policy':chain.policy}] %}
-{%- else %}
-{%- set map = chain.policy %}
-{%- endif %}
-
-{%- for policy in map %}
-iptables_{{ policy.table }}_{{ chain_name }}_policy:
- iptables.set_policy:
- - family: ipv4
- - chain: {{ chain_name }}
- - policy: {{ policy.policy }}
- - table: {{ policy.table }}
- - require:
- - iptables: iptables_{{ policy.table }}_{{ chain_name }}
-
-{%- if grains.ipv6|default(False) and service.ipv6|default(True) %}
-iptables_{{ policy.table }}_{{ chain_name }}_ipv6_policy:
- iptables.set_policy:
- - family: ipv6
- - chain: {{ chain_name }}
- - policy: {{ policy.policy }}
- - table: {{ policy.table }}
- - require:
- - iptables: iptables_{{ policy.table }}_{{ chain_name }}_ipv6
-{%- endif %}
-{%- endfor %}
-{%- endif %}
-
-{%- for service_name, service in pillar.items() %}
-{%- if service is mapping %}
-{%- if service.get('_support', {}).get('iptables', {}).get('enabled', False) %}
-
-{%- set grains_fragment_file = service_name+'/meta/iptables.yml' %}
-{%- macro load_grains_file() %}{% include grains_fragment_file %}{% endmacro %}
-{%- set grains_yaml = load_grains_file()|load_yaml %}
-
-{%- if grains_yaml is iterable %}
-{%- if grains_yaml.get('iptables',{}).rules is defined %}
-{%- for rule in grains_yaml.iptables.rules %}
-{%- set rule_name = service_name+'_'+loop.index|string %}
-{% include "iptables/_rule.sls" %}
-{%- endfor %}
-{%- endif %}
-{%- endif %}
-
-{%- endif %}
-{%- endif %}
-{%- endfor %}
-
-{%- for rule in chain.get('rules', []) %}
-{%- set rule_name = loop.index %}
-{% include "iptables/_rule.sls" %}
-{%- endfor %}
-
-{%- endfor %}
-{%- endif %}
diff --git a/iptables/service.sls b/iptables/service.sls
deleted file mode 100644
index 3d041a1..0000000
--- a/iptables/service.sls
+++ /dev/null
@@ -1,63 +0,0 @@
-{% from "iptables/map.jinja" import service with context %}
-
-{%- if service.enabled %}
-
-include:
- - iptables.rules
-
-iptables_packages:
- pkg.installed:
- - names: {{ service.pkgs }}
-
-iptables_services:
-{%- if grains.init == 'systemd' %}
- service.running:
-{%- else %}
- service.dead:
-{%- endif %}
- - enable: true
- - name: {{ service.service }}
- - sig: test -e /etc/iptables/rules.v4
- - require:
- - pkg: iptables_packages
-
-{%- else %}
-
-iptables_services:
- service.dead:
- - enable: false
- - name: {{ service.service }}
-
-{%- for chain_name in ['INPUT', 'OUTPUT', 'FORWARD'] %}
-iptables_{{ chain_name }}_policy:
- iptables.set_policy:
- - chain: {{ chain_name }}
- - policy: ACCEPT
- - table: filter
- - require_in:
- - iptables: iptables_flush
-
-{%- if grains.ipv6|default(False) and service.ipv6|default(True) %}
-iptables_{{ chain_name }}_ipv6_policy:
- iptables.set_policy:
- - chain: {{ chain_name }}
- - family: ipv6
- - policy: ACCEPT
- - table: filter
- - require_in:
- - iptables: ip6tables_flush
-{%- endif %}
-
-{%- endfor %}
-
-iptables_flush:
- iptables.flush
-
-{%- if grains.ipv6|default(False) and service.ipv6|default(True) %}
-ip6tables_flush:
- iptables.flush:
- - family: ipv6
-{%- endif %}
-
-
-{%- endif %}
diff --git a/iptables/v1/files/v4_rules b/iptables/v1/files/v4_rules
new file mode 100644
index 0000000..e34b09f
--- /dev/null
+++ b/iptables/v1/files/v4_rules
@@ -0,0 +1,72 @@
+{%- from "iptables/map.jinja" import defaults,service,tables with context %}
+{%- if service.v4.enabled -%}
+# Generated by salt v{{ grains['saltversion'] }}
+{%- if not defaults.v4.metadata_rules %}
+ {%- for s_name, svc in pillar.items() %}
+ {%- if svc is mapping %}
+ {%- if svc.get('_support', {}).get('iptables', {}).get('enabled', False) %}
+ {%- macro load_grains_file() %}{% include s_name + '/meta/iptables.yml' %}{% endmacro %}
+ {%- set grains_yaml = load_grains_file()|load_yaml %}
+ {%- if grains_yaml is iterable %}
+ {%- set grains_tables = grains_yaml.get('iptables', {}).get('tables', {}).get('v4', {}) %}
+ {%- if grains_tables is iterable %}
+ {%- for gt_name, gt in grains_tables.items() %}
+ {%- if gt_name not in tables.v4 %}
+ {%- do tables.v4.update( { gt_name: gt } ) %}
+ {%- else %}
+ {%- for gc_name, gc in gt.chains.items() %}
+ {%- set gt_obj = tables.v4.get(gt_name) %}
+ {%- if gc_name not in gt_obj.chains %}
+ {%- do gt_obj.chains.update( { gc_name: gc } ) %}
+ {%- else %}
+ {%- set gc_obj = gt_obj.chains.get(gc_name) %}
+ {%- if gc.ruleset is defined %}
+ {%- if gc_obj.ruleset is not defined %}
+ {%- do gc_obj.update( { 'ruleset': {} } ) %}
+ {%- endif %}
+ {%- for grule_id, gr in gc.ruleset.items()|sort %}
+ {%- if grule_id not in gc_obj.ruleset %}
+ {%- do gc_obj.ruleset.update( { grule_id: gr } ) %}
+ {%- endif %}
+ {%- endfor %}
+ {%- endif %}
+ {%- endif %}
+ {%- endfor %}
+ {%- endif %}
+ {%- endfor %}
+ {%- endif %}
+ {%- endif %}
+ {%- endif %}
+ {%- endif %}
+ {%- endfor %}
+{%- endif %}
+{%- for t_name, t in tables.v4.items() %}
+*{{ t_name }}
+ {%- for c_name, c in t.chains.items() %}
+ {%- if c_name in ('INPUT','FORWARD','OUTPUT','PREROUTING','POSTROUTING') %}
+ {%- set policy = c.get('policy', defaults.v4.policy) %}
+ {%- else %}
+ {%- set policy = "- [0:0]" %}
+ {%- endif %}
+:{{ c_name }} {{ policy }}
+ {%- endfor %}
+ {%- for c_name, c in t.chains.items() %}
+ {%- for rule_id, r in c.get('ruleset', {}).items()|sort %}
+ {%- set rule = r.get('rule', defaults.v4.ruleset.rule) %}
+ {%- set action = r.get('action', defaults.v4.ruleset.action) %}
+ {%- set params = r.get('params', defaults.v4.ruleset.params) %}
+ {%- if rule != "" %}
+ {%- set rule = " " + rule %}
+ {%- endif %}
+ {%- if action != "" %}
+ {%- set action = " -j " + action %}
+ {%- endif %}
+ {%- if params != "" %}
+ {%- set params = " " + params %}
+ {%- endif %}
+-A {{ c_name }}{{ rule }}{{ action }}{{ params }}
+ {%- endfor %}
+ {%- endfor %}
+COMMIT
+{%- endfor %}
+{%- endif %}
diff --git a/iptables/v1/files/v6_rules b/iptables/v1/files/v6_rules
new file mode 100644
index 0000000..7ba8c73
--- /dev/null
+++ b/iptables/v1/files/v6_rules
@@ -0,0 +1,72 @@
+{%- from "iptables/map.jinja" import defaults,service,tables with context %}
+{%- if service.v6.enabled -%}
+# Generated by salt v{{ grains['saltversion'] }}
+{%- if not defaults.v6.metadata_rules %}
+ {%- for s_name, svc in pillar.items() %}
+ {%- if svc is mapping %}
+ {%- if svc.get('_support', {}).get('iptables', {}).get('enabled', False) %}
+ {%- macro load_grains_file() %}{% include s_name + '/meta/iptables.yml' %}{% endmacro %}
+ {%- set grains_yaml = load_grains_file()|load_yaml %}
+ {%- if grains_yaml is iterable %}
+ {%- set grains_tables = grains_yaml.get('iptables', {}).get('tables', {}).get('v6', {}) %}
+ {%- if grains_tables is iterable %}
+ {%- for gt_name, gt in grains_tables.items() %}
+ {%- if gt_name not in tables.v6 %}
+ {%- do tables.v6.update( { gt_name: gt } ) %}
+ {%- else %}
+ {%- for gc_name, gc in gt.chains.items() %}
+ {%- set gt_obj = tables.v6.get(gt_name) %}
+ {%- if gc_name not in gt_obj.chains %}
+ {%- do gt_obj.chains.update( { gc_name: gc } ) %}
+ {%- else %}
+ {%- set gc_obj = gt_obj.chains.get(gc_name) %}
+ {%- if gc.ruleset is defined %}
+ {%- if gc_obj.ruleset is not defined %}
+ {%- do gc_obj.update( { 'ruleset': {} } ) %}
+ {%- endif %}
+ {%- for grule_id, gr in gc.ruleset.items()|sort %}
+ {%- if grule_id not in gc_obj.ruleset %}
+ {%- do gc_obj.ruleset.update( { grule_id: gr } ) %}
+ {%- endif %}
+ {%- endfor %}
+ {%- endif %}
+ {%- endif %}
+ {%- endfor %}
+ {%- endif %}
+ {%- endfor %}
+ {%- endif %}
+ {%- endif %}
+ {%- endif %}
+ {%- endif %}
+ {%- endfor %}
+{%- endif %}
+{%- for t_name, t in tables.v6.items() %}
+*{{ t_name }}
+ {%- for c_name, c in t.chains.items() %}
+ {%- if c_name in ('INPUT','FORWARD','OUTPUT','PREROUTING','POSTROUTING') %}
+ {%- set policy = c.get('policy', defaults.v6.policy) %}
+ {%- else %}
+ {%- set policy = "- [0:0]" %}
+ {%- endif %}
+:{{ c_name }} {{ policy }}
+ {%- endfor %}
+ {%- for c_name, c in t.chains.items() %}
+ {%- for rule_id, r in c.get('ruleset', {}).items()|sort %}
+ {%- set rule = r.get('rule', defaults.v6.ruleset.rule) %}
+ {%- set action = r.get('action', defaults.v6.ruleset.action) %}
+ {%- set params = r.get('params', defaults.v6.ruleset.params) %}
+ {%- if rule != "" %}
+ {%- set rule = " " + rule %}
+ {%- endif %}
+ {%- if action != "" %}
+ {%- set action = " -j " + action %}
+ {%- endif %}
+ {%- if params != "" %}
+ {%- set params = " " + params %}
+ {%- endif %}
+-A {{ c_name }}{{ rule }}{{ action }}{{ params }}
+ {%- endfor %}
+ {%- endfor %}
+COMMIT
+{%- endfor %}
+{%- endif %}
diff --git a/iptables/v1/init.sls b/iptables/v1/init.sls
new file mode 100644
index 0000000..224d268
--- /dev/null
+++ b/iptables/v1/init.sls
@@ -0,0 +1,6 @@
+{%- from "iptables/map.jinja" import schema with context %}
+{%- if pillar.iptables.service.enabled is not defined %}
+include:
+ - iptables.v{{ schema.epoch }}.v4_service
+ - iptables.v{{ schema.epoch }}.v6_service
+{%- endif %}
diff --git a/iptables/meta/sphinx.yml b/iptables/v1/meta/sphinx.yml
similarity index 100%
rename from iptables/meta/sphinx.yml
rename to iptables/v1/meta/sphinx.yml
diff --git a/iptables/v1/v4_service.sls b/iptables/v1/v4_service.sls
new file mode 100644
index 0000000..41deedf
--- /dev/null
+++ b/iptables/v1/v4_service.sls
@@ -0,0 +1,79 @@
+{% from "iptables/map.jinja" import defaults,schema,service with context %}
+
+ {%- if service.v4.enabled %}
+
+iptables_packages_v4:
+ pkg.installed:
+ - names: {{ service.v4.pkgs }}
+
+iptables_modules_v4_load:
+ kmod.present:
+ - persist: true
+ - mods: {{ service.v4.modules }}
+ - require:
+ - pkg: iptables_packages_v4
+
+{{ service.v4.persistent_config }}:
+ file.managed:
+ - user: root
+ - group: root
+ - mode: 640
+ - source: salt://iptables/v{{ schema.epoch }}/files/v4_rules
+ - template: jinja
+ - require:
+ - pkg: iptables_packages_v4
+
+ {% if grains['os'] == 'Ubuntu' %}
+
+iptables_services_v4_start:
+ cmd.run:
+ - name: find /usr/share/netfilter-persistent/plugins.d/[0-9]*-ip4tables -exec {} start \;
+ - onlyif: test $(iptables-save | wc -l) -eq 0
+ - require:
+ - file: {{ service.v4.persistent_config }}
+ - kmod: iptables_modules_v4_load
+
+ {%- endif %}
+
+{{ service.v4.service }}:
+ service.running:
+ - enable: true
+ - require:
+ - file: {{ service.v4.persistent_config }}
+ - kmod: iptables_modules_v4_load
+ - watch:
+ - file: {{ service.v4.persistent_config }}
+
+iptables_tables_cleanup_v4:
+ module.wait:
+ - name: iptables_extra.remove_stale_tables
+ - config_file: {{ service.v4.persistent_config }}
+ - family: ipv4
+ - require:
+ - file: {{ service.v4.persistent_config }}
+ - watch:
+ - file: {{ service.v4.persistent_config }}
+ {%- else %}
+
+ {% if grains['os'] == 'Ubuntu' %}
+
+iptables_services_v4_stop:
+ cmd.run:
+ - name: find /usr/share/netfilter-persistent/plugins.d/[0-9]*-ip4tables -exec {} flush \;
+ - onlyif: test $(which iptables-save) -eq 0 && test $(iptables-save | wc -l) -ne 0
+
+{{ service.v4.persistent_config }}:
+ file.absent:
+ - require:
+ - cmd: iptables_services_v4_stop
+
+iptables_tables_flush_v4:
+ module.wait:
+ - name: iptables_extra.flush_all
+ - family: ipv4
+ - watch:
+ - file: {{ service.v4.persistent_config }}
+
+ {%- endif %}
+
+{%- endif %}
diff --git a/iptables/v1/v6_service.sls b/iptables/v1/v6_service.sls
new file mode 100644
index 0000000..1bea52c
--- /dev/null
+++ b/iptables/v1/v6_service.sls
@@ -0,0 +1,79 @@
+{% from "iptables/map.jinja" import defaults,schema,service with context %}
+
+ {%- if service.v6.enabled %}
+
+iptables_packages_v6:
+ pkg.installed:
+ - names: {{ service.v6.pkgs }}
+
+iptables_modules_v6_load:
+ kmod.present:
+ - persist: true
+ - mods: {{ service.v6.modules }}
+ - require:
+ - pkg: iptables_packages_v6
+
+{{ service.v6.persistent_config }}:
+ file.managed:
+ - user: root
+ - group: root
+ - mode: 640
+ - source: salt://iptables/v{{ schema.epoch }}/files/v6_rules
+ - template: jinja
+ - require:
+ - pkg: iptables_packages_v6
+
+ {% if grains['os'] == 'Ubuntu' %}
+
+iptables_services_v6_start:
+ cmd.run:
+ - name: find /usr/share/netfilter-persistent/plugins.d/[0-9]*-ip6tables -exec {} start \;
+ - onlyif: test $(ip6tables-save | wc -l) -eq 0
+ - require:
+ - file: {{ service.v6.persistent_config }}
+ - kmod: iptables_modules_v6_load
+
+ {%- endif %}
+
+{{ service.v6.service }}:
+ service.running:
+ - enable: true
+ - require:
+ - file: {{ service.v6.persistent_config }}
+ - kmod: iptables_modules_v6_load
+ - watch:
+ - file: {{ service.v6.persistent_config }}
+
+iptables_tables_cleanup_v6:
+ module.wait:
+ - name: iptables_extra.remove_stale_tables
+ - config_file: {{ service.v6.persistent_config }}
+ - family: ipv6
+ - require:
+ - file: {{ service.v6.persistent_config }}
+ - watch:
+ - file: {{ service.v6.persistent_config }}
+ {%- else %}
+
+ {% if grains['os'] == 'Ubuntu' %}
+
+iptables_services_v6_stop:
+ cmd.run:
+ - name: find /usr/share/netfilter-persistent/plugins.d/[0-9]*-ip6tables -exec {} flush \;
+ - onlyif: test $(which ip6tables-save) -eq 0 && test $(ip6tables-save | wc -l) -ne 0
+
+{{ service.v6.persistent_config }}:
+ file.absent:
+ - require:
+ - cmd: iptables_services_v6_stop
+
+iptables_tables_flush_v6:
+ module.wait:
+ - name: iptables_extra.flush_all
+ - family: ipv6
+ - watch:
+ - file: {{ service.v6.persistent_config }}
+
+ {%- endif %}
+
+{%- endif %}
diff --git a/metadata.yml b/metadata.yml
index e8276c0..0eb1e2a 100644
--- a/metadata.yml
+++ b/metadata.yml
@@ -1,4 +1,3 @@
name: "iptables"
-version: "0.2"
+version: "1.0.0"
source: "https://github.com/salt-formulas/salt-formula-iptables"
-
diff --git a/tests/pillar/iptables.sls b/tests/pillar/iptables.sls
new file mode 100644
index 0000000..05fe015
--- /dev/null
+++ b/tests/pillar/iptables.sls
@@ -0,0 +1,71 @@
+iptables:
+ schema:
+ epoch: 1
+ service:
+ v4:
+ enabled: true
+ modules:
+ - nf_conntrack_ftp
+ - nf_conntrack_pptp
+ v6:
+ enabled: false
+ modules:
+ - nf_conntrack_ipv6
+ defaults:
+ v4:
+ metadata_rules: false
+ policy: ACCEPT
+ ruleset:
+ action: ACCEPT
+ params: ""
+ rule: ""
+ v6:
+ metadata_rules: false
+ policy: DROP
+ ruleset:
+ action: ACCEPT
+ params: ""
+ rule: ""
+ tables:
+ v4:
+ filter:
+ chains:
+ INPUT:
+ ruleset:
+ 5:
+ action: log_drop
+ 10:
+ rule: -s 192.168.0.0/24 -p tcp
+ log_drop:
+ policy: DROP
+ ruleset:
+ 10:
+ rule: ""
+ action: LOG
+ nat:
+ chains:
+ OUTPUT:
+ PREROUTING:
+ POSTROUTING:
+ policy: ACCEPT
+ ruleset:
+ 10:
+ rule: -s 192.168.0.0/24 -p tcp -o lo
+ action: SNAT
+ params: --to-source=127.0.0.1
+ config: v4
+ v6:
+ filter:
+ chains:
+ INPUT:
+ ruleset:
+ 5:
+ action: log_drop
+ 10:
+ rule: -s 200A:0:200C::1/64 -p tcp
+ log_drop:
+ policy: DROP
+ ruleset:
+ 10:
+ rule: ""
+ action: LOG
diff --git a/tests/pillar/iptables_server.sls b/tests/pillar/iptables_server.sls
deleted file mode 100644
index 408b273..0000000
--- a/tests/pillar/iptables_server.sls
+++ /dev/null
@@ -1,28 +0,0 @@
-iptables:
- service:
- enabled: true
- chain:
- INPUT:
- policy:
- - table: nat
- policy: ACCEPT
- rules:
- - position: 1
- table: filter
- protocol: tcp
- destination_port: 8088
- source_network: 127.0.0.1
- jump: ACCEPT
- comment: Blah
- OUTPUT:
- policy: ACCEPT
- FORWARD:
- policy:
- - table: mangle
- policy: DROP
- POSTROUTING:
- rules:
- - jump: MASQUERADE
- protocol: icmp
- out_interface: ens3
- table: nat