237 lines
7.4 KiB
YAML
237 lines
7.4 KiB
YAML
---
|
|
# Firewall configuration using ufw
|
|
# Default deny incoming, allow outgoing
|
|
# Trusted subnets get full access
|
|
# Per-host rules for publicly exposed services
|
|
|
|
- name: Install ufw
|
|
ansible.builtin.apt:
|
|
name: ufw
|
|
state: present
|
|
update_cache: true
|
|
when: ansible_facts['os_family'] == "Debian"
|
|
|
|
- name: Install firewalld
|
|
ansible.builtin.dnf:
|
|
name: firewalld
|
|
state: present
|
|
when: ansible_facts['os_family'] == "RedHat"
|
|
|
|
- name: Ensure firewalld is started and enabled
|
|
ansible.builtin.systemd:
|
|
name: firewalld
|
|
state: started
|
|
enabled: true
|
|
when: ansible_facts['os_family'] == "RedHat"
|
|
|
|
# --- Firewalld (RedHat/AlmaLinux) ---
|
|
|
|
- name: Allow trusted subnets in firewalld
|
|
ansible.posix.firewalld:
|
|
zone: public
|
|
source: "{{ item.subnet }}"
|
|
permanent: true
|
|
immediate: true
|
|
state: enabled
|
|
loop: "{{ firewall_trusted_subnets }}"
|
|
when: ansible_facts['os_family'] == "RedHat"
|
|
|
|
- name: Allow SSH from specific IPs in firewalld
|
|
ansible.posix.firewalld:
|
|
zone: public
|
|
rich_rule: 'rule family="ipv4" source address="{{ item.ip }}" port port="22" protocol="tcp" accept'
|
|
permanent: true
|
|
immediate: true
|
|
state: enabled
|
|
loop: "{{ firewall_ssh_allow_from }}"
|
|
when:
|
|
- ansible_facts['os_family'] == "RedHat"
|
|
- firewall_ssh_allow_from | length > 0
|
|
|
|
- name: Allow per-host public services in firewalld (unrestricted)
|
|
ansible.posix.firewalld:
|
|
zone: public
|
|
port: "{{ item.port | string }}/{{ item.proto }}"
|
|
permanent: true
|
|
immediate: true
|
|
state: enabled
|
|
loop: "{{ firewall_allow_rules | rejectattr('sources', 'defined') | list }}"
|
|
when:
|
|
- ansible_facts['os_family'] == "RedHat"
|
|
- firewall_allow_rules | rejectattr('sources', 'defined') | list | length > 0
|
|
|
|
- name: Remove port-level rules for source-restricted services in firewalld
|
|
ansible.posix.firewalld:
|
|
zone: public
|
|
port: "{{ item.port | string }}/{{ item.proto }}"
|
|
permanent: true
|
|
immediate: true
|
|
state: disabled
|
|
loop: "{{ firewall_allow_rules | selectattr('sources', 'defined') | list }}"
|
|
when:
|
|
- ansible_facts['os_family'] == "RedHat"
|
|
- firewall_allow_rules | selectattr('sources', 'defined') | list | length > 0
|
|
|
|
- name: Allow source-restricted services in firewalld (IPv4 rich rules)
|
|
ansible.posix.firewalld:
|
|
zone: public
|
|
rich_rule: 'rule family="ipv4" source address="{{ item.1 }}" port port="{{ item.0.port | string }}" protocol="{{ item.0.proto }}" accept'
|
|
permanent: true
|
|
immediate: true
|
|
state: enabled
|
|
with_subelements:
|
|
- "{{ firewall_allow_rules | selectattr('sources', 'defined') | list }}"
|
|
- sources
|
|
when:
|
|
- ansible_facts['os_family'] == "RedHat"
|
|
- firewall_allow_rules | selectattr('sources', 'defined') | list | length > 0
|
|
|
|
- name: Allow source-restricted services in firewalld (IPv6 rich rules)
|
|
ansible.posix.firewalld:
|
|
zone: public
|
|
rich_rule: 'rule family="ipv6" source address="{{ item.1 }}" port port="{{ item.0.port | string }}" protocol="{{ item.0.proto }}" accept'
|
|
permanent: true
|
|
immediate: true
|
|
state: enabled
|
|
with_subelements:
|
|
- "{{ firewall_allow_rules | selectattr('ipv6_sources', 'defined') | list }}"
|
|
- ipv6_sources
|
|
when:
|
|
- ansible_facts['os_family'] == "RedHat"
|
|
- firewall_allow_rules | selectattr('ipv6_sources', 'defined') | list | length > 0
|
|
|
|
# --- UFW (Debian/Ubuntu) - Check current state ---
|
|
|
|
- name: "Check ufw status"
|
|
ansible.builtin.command: ufw status
|
|
register: _fw_ufw_active
|
|
changed_when: false
|
|
failed_when: false
|
|
check_mode: false
|
|
when: ansible_facts['os_family'] == "Debian"
|
|
|
|
# --- UFW (Debian/Ubuntu) - Disable when firewall not managed ---
|
|
|
|
- name: Disable ufw
|
|
community.general.ufw:
|
|
state: disabled
|
|
when:
|
|
- ansible_facts['os_family'] == "Debian"
|
|
- not (firewall_enabled | default(true))
|
|
- _fw_ufw_active.stdout is defined
|
|
- "'Status: active' in _fw_ufw_active.stdout"
|
|
|
|
# --- UFW (Debian/Ubuntu) - Configure when firewall managed ---
|
|
|
|
- name: Set default incoming policy to deny
|
|
community.general.ufw:
|
|
direction: incoming
|
|
policy: "{{ firewall_default_incoming }}"
|
|
when:
|
|
- ansible_facts['os_family'] == "Debian"
|
|
- firewall_enabled | default(true)
|
|
|
|
- name: Set default outgoing policy to allow
|
|
community.general.ufw:
|
|
direction: outgoing
|
|
policy: "{{ firewall_default_outgoing }}"
|
|
when:
|
|
- ansible_facts['os_family'] == "Debian"
|
|
- firewall_enabled | default(true)
|
|
|
|
- name: Set default routed policy
|
|
community.general.ufw:
|
|
direction: routed
|
|
policy: "{{ firewall_default_routed }}"
|
|
when:
|
|
- ansible_facts['os_family'] == "Debian"
|
|
- firewall_enabled | default(true)
|
|
|
|
- name: "Capture existing ufw rules"
|
|
ansible.builtin.command: ufw status verbose
|
|
register: _fw_ufw_rules
|
|
changed_when: false
|
|
failed_when: false
|
|
check_mode: false
|
|
when:
|
|
- ansible_facts['os_family'] == "Debian"
|
|
- firewall_enabled | default(true)
|
|
- _fw_ufw_active.stdout is defined
|
|
- "'Status: active' in _fw_ufw_active.stdout"
|
|
|
|
- name: Allow all traffic from trusted subnets
|
|
community.general.ufw:
|
|
rule: allow
|
|
from_ip: "{{ item.subnet }}"
|
|
comment: "{{ item.comment }}"
|
|
loop: "{{ firewall_trusted_subnets }}"
|
|
when:
|
|
- ansible_facts['os_family'] == "Debian"
|
|
- firewall_enabled | default(true)
|
|
- _fw_ufw_rules.skipped | default(true) or item.subnet not in _fw_ufw_rules.stdout
|
|
|
|
- name: Allow SSH from specific IPs
|
|
community.general.ufw:
|
|
rule: allow
|
|
port: "22"
|
|
proto: tcp
|
|
from_ip: "{{ item.ip }}"
|
|
comment: "SSH from {{ item.comment }}"
|
|
loop: "{{ firewall_ssh_allow_from }}"
|
|
when:
|
|
- ansible_facts['os_family'] == "Debian"
|
|
- firewall_enabled | default(true)
|
|
- firewall_ssh_allow_from | length > 0
|
|
- _fw_ufw_rules.skipped | default(true) or item.ip not in _fw_ufw_rules.stdout
|
|
|
|
- name: Allow per-host public services
|
|
community.general.ufw:
|
|
rule: allow
|
|
port: "{{ item.port | string }}"
|
|
proto: "{{ item.proto }}"
|
|
from_ip: "{{ item.from_ip | default('any') }}"
|
|
comment: "{{ item.comment }}"
|
|
loop: "{{ firewall_allow_rules }}"
|
|
when:
|
|
- ansible_facts['os_family'] == "Debian"
|
|
- firewall_enabled | default(true)
|
|
- firewall_allow_rules | length > 0
|
|
- _fw_ufw_rules.skipped | default(true) or (item.from_ip | default('any') + ' ' + item.port | string + '/' + item.proto) not in _fw_ufw_rules.stdout
|
|
|
|
- name: Enable IP forwarding
|
|
ansible.posix.sysctl:
|
|
name: net.ipv4.ip_forward
|
|
value: '1'
|
|
sysctl_set: true
|
|
state: present
|
|
reload: true
|
|
when:
|
|
- ansible_facts['os_family'] == "Debian"
|
|
- firewall_enabled | default(true)
|
|
- firewall_ip_forward
|
|
|
|
- name: Configure NAT masquerade rules in ufw before.rules
|
|
ansible.builtin.blockinfile:
|
|
path: /etc/ufw/before.rules
|
|
insertbefore: "\\*filter"
|
|
marker: "# {mark} ANSIBLE MANAGED - {{ item.comment }}"
|
|
block: |
|
|
*nat
|
|
:POSTROUTING ACCEPT [0:0]
|
|
-A POSTROUTING -s {{ item.source }} -o {{ item.out_interface }} -j MASQUERADE -m comment --comment "{{ item.comment }}"
|
|
COMMIT
|
|
loop: "{{ firewall_masquerade_rules }}"
|
|
when:
|
|
- ansible_facts['os_family'] == "Debian"
|
|
- firewall_enabled | default(true)
|
|
- firewall_masquerade_rules | length > 0
|
|
notify: Reload ufw
|
|
|
|
- name: Enable ufw
|
|
community.general.ufw:
|
|
state: enabled
|
|
when:
|
|
- ansible_facts['os_family'] == "Debian"
|
|
- firewall_enabled | default(true)
|
|
- _fw_ufw_active.stdout is defined
|
|
- "'Status: active' not in _fw_ufw_active.stdout"
|