Si tenemos un amplio grupo de servidores es importante poder gestionar de forma centralizada las reglas de firewall, Ansible es una buena opción ya que podemos crear grupos y aplicar unas reglas determinadas sobre un grupo u otro.
Instalamos las userlands de iptables:
Cargamos las reglas de iptables en el arranque:
Instalamos las librerías de python necesarias:
Creamos los directorios necesarios para Ansible:
chown -R root:kr0m /etc/ansible/
chmod 775 /etc/ansible/
Creamos un grupo de servidores llamado test:
[test]
kr0mtest
Escribimos el playbook:
- hosts: test
vars:
allowed_hosts:
- 1.1.1.1
- 2.2.2.2
- 3.3.3.3
tasks:
- name: Firewall rule - allow INPUT custom hosts
iptables:
action: insert
chain: INPUT
rule_num: 1
source: "{{ item }}"
jump: ACCEPT
with_items:
- "{{ allowed_hosts }}"
- name: Firewall rule - allow OUTPUT custom hosts
iptables:
action: insert
chain: OUTPUT
rule_num: 1
destination: "{{ item }}"
jump: ACCEPT
with_items:
- "{{ allowed_hosts }}"
- name: Save iptables rules
shell: /etc/init.d/iptables save
Lo ejecutamos:
Comprobamos que ha aplicado la config correctamente:
Chain INPUT (policy ACCEPT)
target prot opt source destination
ACCEPT all -- 3.3.3.3 0.0.0.0/0
ACCEPT all -- 2.2.2.2 0.0.0.0/0
ACCEPT all -- 1.1.1.1 0.0.0.0/0
Chain FORWARD (policy ACCEPT)
target prot opt source destination
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
ACCEPT all -- 0.0.0.0/0 3.3.3.3
ACCEPT all -- 0.0.0.0/0 2.2.2.2
ACCEPT all -- 0.0.0.0/0 1.1.1.1
NOTA: No he sido capaz de aplicar un DROP por defecto, la única manera con la que he podio hacerlo es dejando el DROP como última ACL tanto en INPUT como OUTPUT.
Algo muy común es tener unas reglas genéricas y otras mas específicas, para poder reutilizar playbooks haremos uso de los includes:
- hosts: "{{hostlist}}"
vars:
input_ports:
ssh32002:
port: 32002
protocol: tcp
ssh22:
port: 22
protocol: tcp
http:
port: 80
protocol: tcp
https:
port: 443
protocol: tcp
dns:
port: 53
protocol: tcp
dnsUdp:
port: 53
protocol: udp
output_ports:
http:
port: 80
protocol: tcp
https:
port: 443
protocol: tcp
dns:
port: 53
protocol: tcp
dnsUdp:
port: 53
protocol: udp
traffic_direction:
- INPUT
- OUTPUT
tasks:
- name: Firewall rule - flush previous INPUT ACLs
iptables:
flush: true
chain: "{{ item }}"
with_items: "{{ traffic_direction }}"
- name: Firewall rule - allow all INPUT loopback traffic
iptables:
action: append
chain: INPUT
in_interface: lo
jump: ACCEPT
- name: Firewall rule - allow all OUTPUT loopback traffic
iptables:
action: append
chain: OUTPUT
out_interface: lo
jump: ACCEPT
- name: Firewall rule - allow INPUT ICMP traffic
iptables:
action: append
chain: "{{ item }}"
protocol: icmp
jump: ACCEPT
with_items: "{{ traffic_direction }}"
- name: Firewall rule - allow INPUT ports
iptables:
action: append
chain: INPUT
protocol: "{{ item.value.protocol }}"
destination_port: "{{ item.value.port }}"
jump: ACCEPT
loop: "{{ lookup('dict', input_ports) }}"
- name: Firewall rule - allow INPUT2 ports
iptables:
action: append
chain: OUTPUT
protocol: "{{ item.value.protocol }}"
source_port: "{{ item.value.port }}"
jump: ACCEPT
loop: "{{ lookup('dict', input_ports) }}"
- name: Firewall rule - allow OUTPUT ports
iptables:
action: append
chain: INPUT
protocol: "{{ item.value.protocol }}"
source_port: "{{ item.value.port }}"
jump: ACCEPT
loop: "{{ lookup('dict', output_ports) }}"
- name: Firewall rule - allow OUTPUT2 ports
iptables:
action: append
chain: OUTPUT
protocol: "{{ item.value.protocol }}"
destination_port: "{{ item.value.port }}"
jump: ACCEPT
loop: "{{ lookup('dict', output_ports) }}"
- name: Firewall rule - deny all traffic
iptables:
action: append
chain: "{{ item }}"
jump: DROP
with_items: "{{ traffic_direction }}"
Ahora incluimos el playbook genérico en nuestro playbook:
- name: Load generic fw rules
import_playbook: genericFw.yml hostlist=test
- hosts: test
vars:
allowed_hosts:
- 1.1.1.1
- 2.2.2.2
- 3.3.3.3
tasks:
- name: Firewall rule - allow INPUT custom hosts
iptables:
action: insert
chain: INPUT
rule_num: 1
source: "{{ item }}"
jump: ACCEPT
with_items:
- "{{ allowed_hosts }}"
- name: Firewall rule - allow OUTPUT custom hosts
iptables:
action: insert
chain: OUTPUT
rule_num: 1
destination: "{{ item }}"
jump: ACCEPT
with_items:
- "{{ allowed_hosts }}"
- name: Save iptables rules
shell: /etc/init.d/iptables save
Ejecutamos el playbook:
Comprobamos que haya funcionado:
Chain INPUT (policy ACCEPT)
num target prot opt source destination
1 ACCEPT all -- 3.3.3.3 0.0.0.0/0
2 ACCEPT all -- 2.2.2.2 0.0.0.0/0
3 ACCEPT all -- 1.1.1.1 0.0.0.0/0
4 ACCEPT all -- 0.0.0.0/0 0.0.0.0/0
5 ACCEPT icmp -- 0.0.0.0/0 0.0.0.0/0
6 ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:32002
7 ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:80
8 ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:53
9 ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:22
10 ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:443
11 ACCEPT udp -- 0.0.0.0/0 0.0.0.0/0 udp dpt:53
12 ACCEPT udp -- 0.0.0.0/0 0.0.0.0/0 udp spt:53
13 ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp spt:80
14 ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp spt:53
15 ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp spt:443
16 DROP all -- 0.0.0.0/0 0.0.0.0/0
Chain FORWARD (policy ACCEPT)
num target prot opt source destination
Chain OUTPUT (policy ACCEPT)
num target prot opt source destination
1 ACCEPT all -- 0.0.0.0/0 3.3.3.3
2 ACCEPT all -- 0.0.0.0/0 2.2.2.2
3 ACCEPT all -- 0.0.0.0/0 1.1.1.1
4 ACCEPT all -- 0.0.0.0/0 0.0.0.0/0
5 ACCEPT icmp -- 0.0.0.0/0 0.0.0.0/0
6 ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp spt:32002
7 ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp spt:80
8 ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp spt:53
9 ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp spt:22
10 ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp spt:443
11 ACCEPT udp -- 0.0.0.0/0 0.0.0.0/0 udp spt:53
12 ACCEPT udp -- 0.0.0.0/0 0.0.0.0/0 udp dpt:53
13 ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:80
14 ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:53
15 ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:443
16 DROP all -- 0.0.0.0/0 0.0.0.0/0
Efectivamente están los puertos del playbook genérico y las ips del playbook normal.