1. Overview
The FirewallRuleManager module applies IP-based blocking rules to the system firewall, enabling BastionGuard (typically via the Anti-Phishing subsystem) to enforce network-level prevention against known malicious hosts.
The module supports two firewall backends:
- firewalld – using a persistent ipset and a single rich-rule binding
- UFW – using per-IP deny rules followed by a reload
All firewall changes are performed using privileged commands via pkexec.
2. Responsibilities
The module is responsible for:
- Validating and filtering input IP indicators before applying firewall rules
- Deduplicating input indicators to avoid redundant rule application
- Executing privileged firewall commands via
std::system() - Applying rules using a backend-specific strategy (firewalld/ipset vs UFW/deny)
- Providing minimal operator diagnostics through stdout/stderr logs
3. Input Validation and Filtering
3.1 Supported Address Families
Input values are accepted only if they match:
- IPv4 in dotted-decimal form
- IPv6 in a colon-separated hexadecimal form
Validation is performed using regular expressions.
3.2 Validation Rules
The IP validator checks:
- IPv4 octets constrained to
0..255 - IPv6 segments in the range
[0-9a-fA-F]and 1–4 hex digits per segment
Indicators failing validation are skipped and logged as diagnostic events.
3.3 Deduplication
Valid IPs are inserted into an std::unordered_set<std::string> to ensure uniqueness before rule application, reducing redundant firewall operations.
4. Command Execution Helper
4.1 run(cmd)
Privileged firewall operations are executed via a thin wrapper:
bool FirewallRuleManager::run(const std::string& cmd)
Execution details:
- Invokes
std::system(cmd.c_str()) - Returns
truewhen exit code is0 - Logs failures to stderr with the failing command string
This helper centralizes error reporting and keeps backend logic concise.
5. firewalld Backend Strategy
5.1 Approach
For firewalld, the module applies rules using a persistent ipset to avoid creating a large number of individual firewall rules.
Operational goals:
- Create the ipset once
- Add all IP entries without reloading between inserts
- Attach a single rich-rule referencing the ipset
- Reload firewalld exactly once
5.2 ipset Creation
The ipset is created (best-effort) with:
pkexec firewall-cmd --permanent --new-ipset=bastionguard-blocklist \
--type=hash:ip --option=family=inet
This creates a persistent set named bastionguard-blocklist using an IPv4 family setting (family=inet).
Note: the command is executed regardless of whether the ipset already exists; failure is logged but the process continues, enabling idempotent behavior when the set is pre-existing.
5.3 Entry Insertion
Each valid IP is inserted into the permanent ipset:
pkexec firewall-cmd --permanent --ipset=bastionguard-blocklist --add-entry=<ip>
Entries are added without reload to minimize overhead and reduce rule-application latency.
5.4 Rich Rule Attachment
A single permanent rich rule is installed to enforce blocking for sources contained in the ipset:
pkexec firewall-cmd --permanent \
--add-rich-rule='rule source ipset="bastionguard-blocklist" reject'
This creates a centralized enforcement rule rather than generating per-IP reject rules.
5.5 Single Reload
After all entries and the binding rule are installed, firewalld is reloaded once:
pkexec firewall-cmd --reload
This design intentionally reduces reload operations to a single step for performance and operational stability.
6. UFW Backend Strategy
6.1 Approach
For UFW, the module applies a deny rule per IP and reloads once at the end.
6.2 Per-IP Deny Rules
Each IP is applied as:
pkexec ufw deny from <ip>
This creates explicit deny rules within UFW’s rule-set.
6.3 Reload
After inserting all deny rules, UFW is reloaded once:
pkexec ufw reload
7. Backend Selection API
7.1 apply(fw, ips)
The entry point apply() selects the appropriate backend:
FirewallType::FIREWALLD→applyWithFirewalld()FirewallType::UFW→applyWithUfw()FirewallType::NONE→ logs an error and performs no action
Status messages are written to stdout/stderr for operator visibility.
8. Runtime and Security Considerations
- Privilege escalation: all system modifications are performed via
pkexec; callers should ensure proper polkit rules and UX expectations - Input hardening: invalid indicators are filtered out prior to firewall execution, reducing injection and misconfiguration risk
- Deduplication: unique set insertion prevents redundant rule execution and reduces firewall churn
- Idempotency: firewalld ipset creation is attempted unconditionally; failures are logged and subsequent steps proceed, supporting “already exists” cases
- IPv6 handling: the firewalld ipset is created with
family=inet(IPv4). If IPv6 enforcement is required, a dedicated IPv6 ipset (family=inet6) or dual-stack strategy should be introduced - Command execution surface: the implementation uses
std::system()with string concatenation; ensure upstream inputs are strictly validated and controlled (as done viais_valid_ip()) - Operational impact: firewall reload operations are intentionally minimized (single reload per batch) to reduce service disruption