Managing iptables across hundreds of servers presents a unique dichotomy: we need both centralized control for baseline security policies and flexibility for host-specific exceptions. Traditional template-based approaches often fail when server configurations diverge significantly.
The include-based approach you mentioned actually aligns with how many large-scale operations handle firewall management. Here's why it works:
#!/bin/bash
# /etc/iptables.d/main.iptables (managed by Ansible)
# Common rules for all hosts
*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [0:0]
# Include base protections
. /etc/iptables.d/base_protections.inc
# Include host-specific rules if exists
[ -f /etc/iptables.d/local_rules.inc ] && . /etc/iptables.d/local_rules.inc
COMMIT
Here's how to structure the Ansible components:
# playbooks/roles/iptables/tasks/main.yml
- name: Deploy base iptables configuration
template:
src: main.iptables.j2
dest: /etc/iptables.d/main.iptables
mode: 0640
- name: Deploy common rules
template:
src: base_protections.inc.j2
dest: /etc/iptables.d/base_protections.inc
mode: 0640
- name: Ensure local rules directory exists
file:
path: /etc/iptables.d/local_rules.inc
state: touch
mode: 0640
For servers requiring custom rules, we can use Ansible's host_vars:
# host_vars/webserver01.yml
iptables_local_rules: |
# Allow HTTP/HTTPS
-A INPUT -p tcp --dport 80 -j ACCEPT
-A INPUT -p tcp --dport 443 -j ACCEPT
# Special monitoring access
-A INPUT -s 10.10.1.100 -p tcp --dport 5666 -j ACCEPT
Combine these elements in a playbook:
# playbooks/configure_iptables.yml
- hosts: all
become: yes
roles:
- iptables
tasks:
- name: Push host-specific rules when defined
template:
src: local_rules.inc.j2
dest: /etc/iptables.d/local_rules.inc
when: iptables_local_rules is defined
To prevent locking yourself out:
# playbooks/roles/iptables/handlers/main.yml
- name: flush all temporary rules
command: iptables -F
listen: "flush iptables"
- name: apply new rules
command: iptables-restore < /etc/iptables.d/main.iptables
listen: "apply iptables rules"
- name: test rules before applying
command: iptables-restore -t < /etc/iptables.d/main.iptables
register: test_result
failed_when: test_result.rc != 0
listen: "test iptables rules"
Always maintain SSH access during updates:
# templates/base_protections.inc.j2
# Emergency SSH access
-A INPUT -p tcp --dport {{ ansible_ssh_port | default(22) }} -j ACCEPT
# Allow established connections
-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
Managing iptables across hundreds of servers with varying requirements presents a unique operational challenge. We need to maintain:
- Centralized control for common firewall rules
- Local flexibility for host-specific configurations
- An audit trail of all changes
- Minimal service disruption during updates
Here's a battle-tested approach combining Ansible's power with local flexibility:
inventory/
├── group_vars/
│ ├── all.yml # Common rules for all hosts
│ ├── web_servers.yml # Web-specific rules
│ └── db_servers.yml # Database-specific rules
└── host_vars/
├── host1.yml # Host-specific overrides
└── host2.yml
Create a modular iptables template system:
# roles/iptables/templates/iptables.rules.j2
# Common rules
*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [0:0]
# Include common rules
{% include 'common.rules.j2' %}
# Include group-specific rules
{% if 'web_servers' in group_names %}
{% include 'web.rules.j2' %}
{% endif %}
# Include host-specific rules
{% if iptables_custom_rules is defined %}
{% for rule in iptables_custom_rules %}
{{ rule }}
{% endfor %}
{% endif %}
COMMIT
For servers requiring local modifications:
# playbooks/iptables.yml
- hosts: all
tasks:
- name: Deploy main iptables rules
template:
src: iptables.rules.j2
dest: /etc/iptables.rules
notify: reload iptables
- name: Check for local overrides
stat:
path: /etc/iptables.local
register: local_rules
- name: Apply local rules if present
command: /sbin/iptables-restore /etc/iptables.local
when: local_rules.stat.exists
Sample playbook demonstrating rule deployment with variable precedence:
# playbooks/deploy_firewall.yml
- hosts: all
vars_files:
- "{{ inventory_dir }}/group_vars/all.yml"
- "{{ inventory_dir }}/group_vars/{{ group }}.yml"
tasks:
- name: Gather host-specific variables
include_vars:
file: "{{ inventory_dir }}/host_vars/{{ inventory_hostname }}.yml"
when: hostvars[inventory_hostname].iptables_custom is defined
- include_role:
name: iptables
For environments with complex rule requirements:
# roles/iptables/tasks/dynamic_rules.yml
- name: Generate dynamic rules
template:
src: dynamic.rules.j2
dest: /etc/iptables.d/{{ item }}.rules
with_items: "{{ iptables_rule_sets }}"
notify: assemble rules
- name: Assemble final ruleset
command: cat /etc/iptables.d/*.rules > /etc/iptables.rules
args:
creates: /etc/iptables.rules
notify: reload iptables
- Always deploy changes with
--check
first - Implement a rollback mechanism
- Use ansible vault for sensitive rules
- Maintain rule documentation in YAML comments