Managing IPtables efficiently with Saltstack
Solution 1:
I spent a few hours figuring out the best way to manage various iptables settings w Salt, and best solution seems to be to do a combination of
- flat iptables config files (as jinja templates)
- have salt do a iptables flush + restore only if flat file changes
this is how I have it in my env and it works very well. I tried using salt Iptables states, but it gets very cumbersome and unmanageable and you have to forcefully do iptables.flush
on every highstate run,
The following is a simpler and more manageable approach that avoids pillar use completely,
create a flat file for each host using {{ grains.id }}.j2 as layout,
cat /srv/salt/state/iptables/files/nycweb1.j2
##############################################################
## This file is managed by SALTSTACK - Do not modify manually
##############################################################
*filter
:INPUT ACCEPT
:FORWARD ACCEPT
:OUTPUT ACCEPT
## Allow all loopback (lo0) traffic
-A INPUT -i lo -j ACCEPT
## Drop all traffic to 127/8 that doesn't use lo0
-A INPUT ! -i lo -d 127.0.0.0/8 -j DROP
## Accept inbound traffic for already established connections.
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
## Effectively allow all outbound traffic.
-A OUTPUT -m state --state NEW,RELATED,ESTABLISHED -j ACCEPT
## Allow ping
-A INPUT -p icmp -m icmp --icmp-type 8 -j ACCEPT
## Blacklist
-A INPUT -s 0.0.0.0/32 -p tcp -m tcp --dport 22 -j REJECT --reject-with icmp-port-unreachable
## Whitelist
-A INPUT -s 121.236.129.235/32 -p tcp -m tcp --dport 22 -j ACCEPT {# NY office pub #}
-A INPUT -s 192.168.10.0/24 -p tcp -m tcp --dport 22 -j ACCEPT {# NY office priv #}
COMMIT
*nat
:PREROUTING ACCEPT
:INPUT ACCEPT
:OUTPUT ACCEPT
:POSTROUTING ACCEPT
-A PREROUTING -p tcp -m tcp --dport 27015 -j DNAT --to-destination 192.168.38.20
-A OUTPUT -p tcp -m addrtype --src-type LOCAL --dst-type LOCAL -m tcp --dport 1266 -j DNAT --to-destination 169.254.1.1:443
-A POSTROUTING -s 192.168.1.2/32 -d 10.3.4.65/32 -p tcp -m tcp --dport 48854 -j MASQUERADE
-A POSTROUTING -d 10.0.2.15/24 -p tcp -m tcp --dport 27045 -j SNAT --to-source 192.168.99.11 {# description #}
COMMIT
{# EOF #}
create a state file,
cat /srv/salt/state/iptables/init.sls
# STATE - IPTABLES
{% set iptables_file = '/etc/sysconfig/iptables' %}
iptables_pkg:
pkg.installed:
- name: iptables
{{ iptables_file }}:
file.managed:
- user: root
- group: root
- mode: 644
- source: salt://{{ slspath }}/files/{{ grains.id }}.j2
- template: jinja
flush_tables:
iptables.flush:
- table: filter
- family: ipv4
- onchanges:
- file: "{{ iptables_file }}"
restore_tables:
cmd.run:
- name: "/usr/sbin/iptables-restore < {{ iptables_file }}"
- onchanges:
- file: "{{ iptables_file }}"
thats it, when you run highstate on your targets, they will only do flush+restore whenever the flat file gets modified. Your config is also in native iptables format, not in pillar format
now apply the state, or add it to your highstate
salt nycweb01 state.sls iptables
Solution 2:
For this sort of thing, keeping a list of IP addresses for use by iptables, we template out the "ipset" config file and use iptables rules that reference them for allow- or deny-lists. An ipset pays off for large sets, and managing them this way eliminates the "remove an address" problem; the sets are completely reloaded whenever anything changes.
We're centrally managing networks of thousands of servers with strict membership and access requirements, so using salt and a jinja template to render /etc/sysconfig/ipset makes sense for us.