New iptables module questions

I know there have been quite a few requests for an iptables module which were usually closed by Michael (and others) saying that iptables should be controlled using a template module and similar suggestions. I found that solution very difficult and hard to use (and maintain). That is why we wrote our own iptables module which we are currently testing out and once we think it is ready, we would like to make a pull request to include this module in ansible-modules-extras (where ufw and firewalld modules are as well) and we hope that the core devs are interested in this module.

We designed the module with the following in mind:

  • make it similar to the ufw and firewalld modules
  • writing of rules should be as close as possible to writing iptables commands
  • the module should be able to test if the rules are correct and only apply them if they are correct
  • check mode should be supported
  • compatible with older OS version like CentOS 5 (currently it only supports CentOS 5+, but it should be possible to add support to other distributions as well with minor changes)
  • ipv4 and ipv6
  • different tables
  • ordering of rules (weight)
  • and many more
    Here is an example:

`

  • name: Allow all IPv4 traffic coming in on port 80 (http)
    iptables: >
    state=present
    ipversion=4
    table=filter
    weight=50
    name=allow_all_traffic_on_port_80
    rules=“-A INPUT -p tcp -m tcp --dport 80 -j ACCEPT”
    `

The reason why I’m making this topic and I haven’t provided any code is because we are not sure what is the best approach to two problems, so I would like to get your input before we make the needed changes:

  1. What should we do with the current iptables rules (those which were not added by the iptables module)?
    Currently if you ran the task above without adding some common iptables rules with the module before (like open port 22, etc), it would just remove all rules from the filter table and add this rule only. This means that if the INPUT table is set to DROP, you would lock yourself out of the host, which is bad and probably doesn’t feel natural. So we found three ways that we can handle this:
  2. By default set INPUT, FORWARD and OUTPUT to ACCEPT and allow the user to overwrite these defaults with a task which will use e.g. rules=“-P INPUT DROP” and the same for other tables (rules supports multiline).
  • This is the easiest solution, but it could make your server open if you would just like to add one open port (like with the task above)
  1. Add an additional parameter called e.g. init=yes (no by default) and only allow the usage of the iptables module if init was used for the first time.
  • Using of init would basically make sure that the user knows what he is doing and init would basically be used with a combination of default rules which won’t lock you out of the server.
  1. When the iptables module is run for the first time on the server it would save all the current rules as the highest priority rules and combine them with the rules added by the module until we run the iptables module with a special parameter which would basically tell iptables to clear those rules which were not added by Ansible (again it could be init=yes, or delete_foreign=yes, or whatever). What is important here is that these rules could only be saved the first time the module is executed, since it’s hard to detect how a rule was created, and the first time they are cleared with (init or delete_foreign) they can never be returned again and anything added later on without Ansible would be lost. This is ok, since the same thing happens if you edit a config file managed by Ansible template module. The biggest downside to this is that the task in the example above could not open a port if the last rule in the chain is a reject all rule, but in my opinion this is fine as well, if you want to manage iptables with Ansible, you should only use this module and correctly weight your rules.

  2. Should we try to check for potentially SSH lockouts?
    For example after applying a rule should the module try to connect to the SSH port of the server on it’s IP address and if it fails to connect, revert the rule?

I hope you understood my questions, but if not I will be more than happy to provide more details.

Strahinja Kustudić | Lead System Engineer | Nordeus

I still think at template is a better solution, if just to avoid the
back n forth with user/kernel space on each rule. A single file is
easier to validate, confirm order and then it just takes a single
iptables-restore to make effective.

In complex environments that file can get become very long and very hard to read, and in my opinion roles should be the ones which put holes in the firewall. I can understand that you like the template module more for managing the firewall, but not everyone is like you and Ansible also has ufw and firewalld modules, so if we manage to provide similar functionality to those modules, would you reconsider adding this module?

If the answer is yes, what would you suggest as an answer to my questions?

Strahinja Kustudić | Lead System Engineer | Nordeus

I don't think it should check for lockouts, as that might be what the
user intends (I just realized ssh is world available and I'm on
airport wifi ....), at least not w/o a way for them to override it.

About the existing rules, this is where it gets really hard and I go
back to 'template!' which you can do with roles and template includes
or assemble module. Another options is just having roles declare a
fact which can turn on/off a rule/set of rules which the final role
(firewall) would then compile into it's template, these approaches
will still step over each other, but it will be easy to see the final
product and then trace back to where the values were set.

Also note that ansible itself is normally 'stateless' and these
firewalls are 'stateful' by nature, I think a complete ruleset makes
more sense than having disparate rules being inserted/removed.
Ansible is meant to declare a 'prefered state', once you introduce
existing state you need a database and probably an agent.

I know that the ufw and firewalld modules were added, but I would use
not those either, specially since a lot of their options are not
'ansibleish', can lead to confusion, they still have many issues with
sane defaults and neither deals with 'unmanaged' rules (IIRC).

I am probably in the minority opinion here (I'm the werido that likes
using pf/nftables/iptables directly) and I can see how such a module
would appeal to some people, so I'll wait to see what the community
thinks on this topic before making any decisions.

Thanks for the input. We will not check for lockouts as you suggested. Regarding the explanation which you described about using a template, with template includes and assemble, this module basically does that, but in an easier way, so it manages those parts and everything for you. We are also introducing a “state”, but of course only for the rules defined with the module (template module does it the same way). We will brain storm a little what would be the best approach for unmanaged rules and I’ll update this topic.

Also, if anyone else has a suggestion, I’m all ears :slight_smile:

I don’t think it should check for lockouts, as that might be what the
user intends (I just realized ssh is world available and I’m on
airport wifi …), at least not w/o a way for them to override it.

I mean… This is what VPNs are for. In essentially all cases, the freedom to lock yourself out is a foot gun.

On a related note, I actually have written an implementation of this, if anyone is interested in it…

About the existing rules, this is where it gets really hard and I go
back to ‘template!’ which you can do with roles and template includes
or assemble module. Another options is just having roles declare a
fact which can turn on/off a rule/set of rules which the final role
(firewall) would then compile into it’s template, these approaches
will still step over each other, but it will be easy to see the final
product and then trace back to where the values were set.

Also note that ansible itself is normally ‘stateless’ and these
firewalls are ‘stateful’ by nature, I think a complete ruleset makes
more sense than having disparate rules being inserted/removed.
Ansible is meant to declare a ‘prefered state’, once you introduce
existing state you need a database and probably an agent.

This +100. It’s super important that the rules be auditable, consistent, and not rely on magic stale state on the machines being managed.

I still think at template is a better solution, if just to avoid the
back n forth with user/kernel space on each rule. A single file is
easier to validate, confirm order and then it just takes a single
iptables-restore to make effective.

When you are configuring a server sometimes you have to change
iptables rules in different roles(if you have role per service) and using
role from other roles is difficult.