ansible-debian-firewall

CONFIGURATION

Variables by use case

Change the variables below to describe the firewall. Each example maps to files currently present in this repository.

Give an interface an address

Put node-specific addresses in host_vars/fw1/network.yml and host_vars/fw2/network.yml. The role writes systemd-networkd configuration from these values.

network_interfaces:
  - name: eth0
    address: 10.10.10.3
    netmask: 255.255.255.0
    gateway: 10.10.10.1
  - name: eth1
    address: 10.20.10.3
    netmask: 255.255.255.0

Change the address, mask and gateway for the target node. Keep the interface names identical on both HA nodes unless the templates and inventory explicitly account for the difference.

Define reusable firewall objects

Use group_vars/grp1/nftables/10_objects.yml. Hosts receive names, host groups combine them, and services define protocol and ports.

nftables_hosts:
  web: 10.20.10.7

nftables_host_groups:
  public_web:
    hosts:
      - web

nftables_services:
  web:
    protocols:
      tcp: [80, 443]

The name public_web can then be used by a forward rule. Do not put an IP directly in every rule when the same destination is reused.

Publish a service

Publishing requires two files. 40_nat.yml translates the public packet. 30_forward.yml authorizes the translated packet.

nftables_nat_rules:
  - chain: prerouting
    source_interface: wan
    protocol: tcp
    ports: [443]
    destination_host: web
    destination_port: 443
    action: dnat

nftables_forward_rules:
  - chain: forward
    source_interface: wan
    destination_interface: lan
    destination_group: public_web
    service: web
    protocol: tcp
    action: accept

Adding DNAT without the forward rule does not publish the service. Adding the forward rule without DNAT accepts a packet that still has no backend destination.

Allow outbound NAT

Masquerade belongs in 40_nat.yml; forwarding permission belongs in 30_forward.yml.

nftables_nat_rules:
  - chain: postrouting
    source_network: 10.20.10.0/24
    destination_interface: wan
    action: masquerade

nftables_forward_rules:
  - chain: forward
    source_interface: lan
    destination_interface: wan
    source_network: 10.20.10.0/24
    action: accept

Add a WireGuard peer

Peers are defined in group_vars/grp1/wireguard.yml. Use one unique address per peer and restrict allowed_ips to that peer.

wireguard_interfaces:
  - name: wg0
    address: 10.100.0.1/27
    listen_port: 51820
    peers:
      - public_key: PEER_PUBLIC_KEY
        allowed_ips:
          - 10.100.0.2/32
        persistent_keepalive: 25

Keep private keys in Vault. Check the result with wg show and verify that the peer handshake timestamp changes.

Enable OSPF on a node

Shared enablement belongs in group_vars/grp1/frr.yml; router identity and protocol configuration belong in host_vars/fw1/frr.yml.

frr_enabled: true

frr_daemons:
  - zebra
  - ospfd

frr_configs:
  ospf: |
    router ospf
      ospf router-id 10.10.10.3
      network 10.10.10.0/24 area 0
      passive-interface eth1

Use vtysh -c "show ip ospf neighbor" to verify adjacency and ip route to verify kernel installation.

Configure HA

Pair-wide VIPs and passwords belong in group_vars/grp1/keepalived.yml. Priority and peer addresses belong in each host file.

keepalived_enabled: true
keepalived_virtual_ip: 10.20.10.2
keepalived_ha_interface: eth1

keepalived_node_id: fw1
keepalived_priority: 110
keepalived_ha_address: 10.20.10.3
keepalived_peer_ha_address: 10.20.10.4

Only the preferred node should have the higher priority. Validate with systemctl status keepalived and ip addr show.

Validate before applying

ansible-inventory -i inventory.yml --host fw1
ansible-playbook -i inventory.yml site.yml --syntax-check
ansible-playbook -i inventory.yml site.yml --check --diff
ansible-playbook -i inventory.yml site.yml