Firewall IP Block Helper (UFW / firewalld)

1. Overview

This component is a privileged command-line helper responsible for applying or clearing firewall rules derived from a BastionGuard-generated IP blacklist. It is designed to integrate with the Anti-Phishing subsystem (or similar detection pipelines) that produce a list of malicious IP addresses to block at the network perimeter.

The helper supports two firewall backends:

  • firewalld (preferred when available) – implemented using a persistent ipset and zone source attachment
  • UFW – implemented using idempotent deny from <ip> rules

The blacklist input is read from a temporary staging file and is always removed after processing to ensure cleanup and reduce stale state risk.


2. Input File and Data Contract

2.1 Blacklist Staging Path

The helper consumes a plain-text blacklist stored at:

/tmp/bastionguard-firewall-ips.txt

Each non-empty, non-comment line is treated as a single IP address entry. Lines beginning with # are ignored.


2.2 Blacklist Parsing Behavior

The blacklist is parsed by load_blacklist(), which:

  • Opens the staging file
  • Reads line-by-line
  • Skips empty lines and comment lines (#)
  • Appends remaining lines to the IP vector

If the file cannot be opened, an error is logged and an empty list is returned.


3. Security Model and Hardening

3.1 Environment Sanitization

Before executing any command, the helper performs privileged hardening steps:

  • clearenv() – removes all environment variables
  • setenv("PATH", "/usr/sbin:/usr/bin:/sbin:/bin", 1) – enforces a trusted PATH

This reduces the risk of environment-based privilege escalation when invoking shell commands.


3.2 Root Requirement

The helper requires effective root privileges to manipulate firewall configuration:

  • If geteuid() != 0, it exits with code 10.

4. Command Execution Helpers

4.1 Shell Execution

Commands are executed via:

  • run_cmd(cmd) → wraps std::system() and checks WEXITSTATUS()

A command is considered successful only if it exits cleanly with status 0.


4.2 Backend Detection

Firewall backend availability is detected through:

command -v <cmd>

This check is implemented by command_exists() and is used to determine whether ufw or firewall-cmd is present.


5. Firewall Backend: UFW

5.1 Enable and Rule Application

If UFW is available, the helper:

  1. Ensures UFW is enabled: ufw --force enable
  2. Applies a deny rule per IP: ufw deny from <ip>

The deny operation is treated as idempotent in practice; repeated applications should not break enforcement consistency.


6. Firewall Backend: firewalld

6.1 Persistent IPSet Strategy

If firewalld is available, the helper configures a dedicated persistent ipset:

  • IPSet name: bastionguard-blacklist
  • Type: hash:ip

Creation is attempted using:

firewall-cmd --permanent --new-ipset=bastionguard-blacklist --type=hash:ip

Errors from ipset creation are suppressed because the ipset may already exist.


6.2 Zone Integration

The ipset is attached to the public zone as a source set:

firewall-cmd --permanent --zone=public --add-source-ipset=bastionguard-blacklist

This ensures that traffic sourced from any IP contained in the set is handled according to the zone’s source policy.


6.3 Entry Population and Reload

Each IP is inserted into the ipset:

firewall-cmd --permanent --ipset=bastionguard-blacklist --add-entry=<ip>

After population, the firewall configuration is activated via:

firewall-cmd --reload

7. Clear Operation

7.1 UFW Reset

If UFW is available, the helper clears rules using:

ufw --force reset

This resets UFW configuration and removes previously applied rules.


7.2 firewalld IPSet Removal

If firewalld is available, the helper deletes the dedicated ipset:

firewall-cmd --permanent --delete-ipset=bastionguard-blacklist

and then reloads firewalld:

firewall-cmd --reload

7.3 Blacklist File Cleanup

When clearing rules, the helper also removes the staging file:

/tmp/bastionguard-firewall-ips.txt

This prevents reuse of stale IP data and enforces a “one-shot” blacklist application model.


8. Apply Operation and Backend Selection

8.1 Apply Flow

When invoked with apply, the helper:

  1. Loads the blacklist file into memory
  2. Fails fast if the list is empty or invalid
  3. Attempts to apply rules using:
    • apply_firewalld() first
    • then apply_ufw() as fallback
  4. Always removes the staging file after attempting application

The selection logic uses short-circuit evaluation:

  • If firewalld succeeds, UFW is not attempted.
  • If firewalld is unavailable or fails, UFW is attempted.

9. Exit Codes and Result Semantics

  • 0 – success
  • 1 – invalid argument count (usage error)
  • 2 – invalid command argument
  • 3 – blacklist missing/empty/invalid
  • 5 – no supported firewall backend found or rule application failed
  • 10 – not executed as root

10. Runtime and Security Considerations

  • Privileged execution: requires root and sanitizes environment before command execution.
  • Backend priority: firewalld is preferred when present; UFW is used as fallback.
  • State cleanup: the blacklist staging file is always removed after processing to reduce stale state risk.
  • Operational impact: UFW reset is broad and may remove unrelated UFW rules; firewalld removal targets only the BastionGuard ipset.
  • Input validation: the helper assumes each line is a valid IP string; optional enhancements include strict IP parsing and deduplication.
  • Localization: user-visible messages are wrapped using glib/gi18n for translation.