You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

7.7 KiB

Ansible Firewall Role

Build Status Ansible Galaxy

After I found out UFW was too limited in terms of functionalities, I tried several firewall roles out there but none satisfied the requirements I had:

  • Support virtually all iptables rules from the start
  • Allow granular rules addition/overriding for specific hosts
  • Easily inject variables in the rules
  • Allow rules ordering
  • Simplicity (not having to learn how role variables would generate the rules)
  • Persistence (reload the rules at boot)

This role is an attempt to solve these requirements.

It supports ipv4 and ipv6* on Debian and RedHat distributions.

*ipv6 support was brought up thanks to @maloddon. It is currently in early stages and knowledgable people should review the default rules. ipv6 rules are not configured by default. If you which to use them, don't forget to set firewall_v6_configure to true.

Requirements

  • Ansible 2.2.1.0
  • iptables (installed by default on all official Debian and RedHat distributions)

Installation

$ ansible-galaxy install mikegleasonjr.firewall

Role Variables

defaults/main.yml:

firewall_v4_configure: true
firewall_v6_configure: false

firewall_v4_default_rules:
  001 default policies:
    - -P INPUT ACCEPT
    - -P OUTPUT ACCEPT
    - -P FORWARD DROP
  002 allow loopback:
    - -A INPUT -i lo -s 127.0.0.0/8 -d 127.0.0.0/8 -j ACCEPT
  003 allow ping replies:
    - -A INPUT -p icmp --icmp-type echo-request -j ACCEPT
    - -A OUTPUT -p icmp --icmp-type echo-reply -j ACCEPT
  100 allow established related:
    - -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
  200 allow ssh:
    - -A INPUT -p tcp --dport ssh -j ACCEPT
  999 drop everything:
    - -P INPUT DROP
firewall_v4_group_rules: {}
firewall_v4_host_rules: {}

firewall_v6_default_rules:
  001 default policies:
    - -P INPUT ACCEPT
    - -P OUTPUT ACCEPT
    - -P FORWARD DROP
  002 allow loopback:
    - -A INPUT -i lo -s ::1/128 -d ::1/128 -j ACCEPT
  003 allow ping replies:
    - -A INPUT -p icmpv6 --icmpv6-type echo-request -j ACCEPT
    - -A OUTPUT -p icmpv6 --icmpv6-type echo-reply -j ACCEPT
  100 allow established related:
    - -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
  200 allow ssh:
    - -A INPUT -p tcp --dport ssh -j ACCEPT
  999 drop everything:
    - -P INPUT DROP
firewall_v6_group_rules: {}
firewall_v6_host_rules: {}

The keys to the *_rules dictionaries (001 default policies, 002 allow loopback, ...) can be anything. They are only used for rules ordering and overriding. On rules generation, the keys are sorted alphabetically. That's why I chose here the 001s and 999s.

Those defaults will generate the following script to be executed on the host (for ipv4):

#!/bin/sh
# Ansible managed: <redacted>

# flush rules & delete user-defined chains
iptables -F
iptables -X
iptables -t raw -F
iptables -t raw -X
iptables -t nat -F
iptables -t nat -X
iptables -t mangle -F
iptables -t mangle -X

# 001 default policies
iptables -P INPUT ACCEPT
iptables -P OUTPUT ACCEPT
iptables -P FORWARD DROP

# 002 allow loopback
iptables -A INPUT -i lo -s 127.0.0.0/8 -d 127.0.0.0/8 -j ACCEPT

# 003 allow ping replies
iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT
iptables -A OUTPUT -p icmp --icmp-type echo-reply -j ACCEPT

# 100 allow established related
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# 200 allow ssh
iptables -A INPUT -p tcp --dport ssh -j ACCEPT

# 999 drop everything
iptables -P INPUT DROP

As you can see, you have complete control over the rules syntax.

$ iptables -L -n on the host then shows...

Chain INPUT (policy DROP)
target     prot opt source               destination
ACCEPT     all  --  0.0.0.0/0            0.0.0.0/0
ACCEPT     icmp --  0.0.0.0/0            0.0.0.0/0            icmptype 8
ACCEPT     all  --  0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:22

Chain FORWARD (policy DROP)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination
ACCEPT     icmp --  0.0.0.0/0            0.0.0.0/0            icmptype 0

Now that takes care of the default rules. What about overriding?

You can change the rules for specific hosts and groups instead of re-defining everything. Rules in firewall_v4_host_rules will be merged with firewall_v4_group_rules, and then the result will be merged back with the defaults. Same thing for ipv6.

This allows 3 levels of rules definition and overriding. I simply chose the names to match how the variable precedence works in Ansible (all -> group -> host). See the example playbook below to see rules overriding in action.

Example Playbook (ipv4)

- hosts: all
  roles:
    - mikegleasonjr.firewall

in group_vars/all.yml you could define the default rules for all your hosts:

firewall_v4_default_rules:
  001 default policies:
    - -P INPUT ACCEPT
    - -P OUTPUT ACCEPT
    - -P FORWARD DROP
  002 allow loopback:
    - -A INPUT -i lo -j ACCEPT
  003 allow ping replies:
    - -A INPUT -p icmp --icmp-type echo-request -j ACCEPT
    - -A OUTPUT -p icmp --icmp-type echo-reply -j ACCEPT
  100 allow established related:
    - -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
  200 allow ssh limiting brute force:
    - -I INPUT -p tcp -d {{ hostvars[inventory_hostname]['ansible_eth1']['ipv4']['address'] }} --dport 22 -m state --state NEW -m recent --set
    - -I INPUT -p tcp -d {{ hostvars[inventory_hostname]['ansible_eth1']['ipv4']['address'] }} --dport 22 -m state --state NEW -m recent --update --seconds 60 --hitcount 4 -j DROP
  999 drop everything:
    - -P INPUT DROP

in group_vars/webservers.yml you would open up port 80:

firewall_v4_group_rules:
  400 allow web traffic:
    - -A INPUT -p tcp --dport http -j ACCEPT

in host_vars/secureweb.yml you would want to open https as well and remove ssh logins:

firewall_v4_host_rules:
  400 allow web traffic:
    - -A INPUT -p tcp --dport http -j ACCEPT    # need to redefine this one as well because the whole key is overwritten
    - -A INPUT -p tcp --dport https -j ACCEPT
  200 allow ssh limiting brute force: []

To "delete" rules, you just assign an empty list to an existing dictionary key.

To summarize, rules in firewall_v4_host_rules will overwrite rules in firewall_v4_group_rules, and then rules in firewall_v4_group_rules will overwrite rules in firewall_v4_default_rules.

You can play with the rules and see the generated script on the host at the following location: /etc/iptables.v4.generated and /etc/iptables.v6.generated.

Dependencies

none

License

BSD

Contributing

A vagrant environment has been provided to test the role on different distributions. Add your tests in tests.yml and...

$ vagrant up
$ vagrant provision

Author Information

Mike Gleason jr Couturier (mikegleasonjr@gmail.com)

Other roles from the same author: