1

I'm trying to configure a flexible iptables management solution with SaltStack, but I find it harder than I thought it would be.

My main requirement: to be able to have a pillar where I keep a list of IPs, which should be whitelisted for SSH access on all minions. This list of IPs will of course change every now and then: some IPs get added, some IPs are removed. The problem that I'm facing is with the removed IPs - when I remove them from the pillar file, SaltStack doesn't remove the actual whitelisting from the minions.

The only workaround I could find, was to create a new key named "removed-ips" and whenever I want to remove an IP, I would add it there. The second for loop will then remove it. Of course, this is a really nasty workaround, is there a better way of doing it?

/srv/pillar/iptables-default.sls:

iptables-default:
  whitelisted-ips:
    - '55.55.55.55'
    - '66.66.66.66'
    - '77.77.77.77'
  removed-ips:
    - '88.88.88.88'

/srv/salt/iptables-default.sls:

{% for ip in salt['pillar.get']('iptables-default:whitelisted-ips') %}
Whitelist OSF IP {{ip}} for SSH access:
  iptables.append:
    - table: filter
    - family: ipv4
    - chain: INPUT
    - jump: ACCEPT
    - match: state
    - connstate: NEW
    - source: '{{ ip }}'
    - dport: 22
    - proto: tcp
    - save: True
{% endfor %}

{% for ip in salt['pillar.get']('iptables-default:removed-ips') %}
Remove old IPs that are not needed anymore:
  iptables.delete:
    - table: filter
    - family: ipv4
    - chain: INPUT
    - jump: ACCEPT
    - match: state
    - connstate: NEW
    - source: {{ ip }}
    - dport: 22
    - proto: tcp
    - save: True
{% endfor %}
Tony
  • 269
  • 4
  • 15
  • For managing iptables, I use a complete list of rules in my pillar. Then the state flushes all rules and implements the whole stack at each execution if there are a different number of rules in my pillar than on the minion. It was not a simple task to implement that, and it's not perfect. Also I didn't publish it as a formula, but maybe somenone did that already. – Christophe Drevet Dec 14 '17 at 08:32
  • I actually did think about doing this too, but I'm not too happy about it either... It seems to me that something as basic as managing the firewall should be much easier. Thank you for your feedback, though! – Tony Dec 14 '17 at 09:36

2 Answers2

2

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

  1. flat iptables config files (as jinja templates)
  2. 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
perfecto25
  • 288
  • 3
  • 7
1

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.

stolenmoment
  • 181
  • 1
  • 4