# ADR-0017: Phase 7e.3 firewalld Audit

**Status:** Accepted
**Date:** 2026-05-12
**Predecessors:** ADR-0011 (Phase 7e.2 firewall audit)

## Context

`agentsec server-audit` Phase 7e.2 shipped iptables / nftables / ufw
Checks, covering Ubuntu / Debian / Alpine and raw RHEL. But the
out-of-the-box firewall on RHEL / CentOS Stream / Fedora is
**firewalld** (D-Bus + zone model), and 7e.2 spec §2 explicitly
deferred firewalld to this phase.

## Decision

Add `FirewalldAuditCheck` with a fixed 3-command pipeline:
1. `firewall-cmd --state` (service active probe)
2. `firewall-cmd --get-default-zone` (single-token output)
3. `firewall-cmd --list-all-zones` (one big blob)

Audit scope mirrors 7e.2 ufw scope: service-active + default-zone
target + per-active-zone target. Only `target == "ACCEPT"` reports;
default zone = critical, other active zones = high.

To avoid double-reporting on hosts where firewalld manages the
underlying kernel ruleset, `iptables_audit` and `nftables_audit` yield
`not_applicable` when they detect firewalld-injected chains
(`-N IN_*` / `-N FWDI_*` for iptables; `inet firewalld` table for
nftables) — parallel to the existing ufw-takeover yield from 7e.2.

## Consequences

- iptables/nftables Checks become silent on firewalld-managed RHEL hosts;
  firewalld_audit is the canonical evaluator there.
- Default polkit rules already allow non-root reads of all 3 commands,
  so permission failures should be rare; when they do occur, the same
  NOPASSWD-sudoers remediation as 7e.2 applies.
- ssh_policy gains 2 binary paths + 1 commands section (3 precise argv
  tuples). All read-only; write operations remain unreachable by
  construction.
- Future 7e.5 candidates: rich rules / dangerous services / runtime-vs-
  permanent drift / direct interface / `--get-log-denied` baseline.

## Alternatives Considered

- **Per-zone argv loop** (call `firewall-cmd --zone=<z> --get-target`
  per zone): rejected because it introduces an `args_schema` zone-name
  validator (asymmetric with 7e.2's all-`allowed_argv` form) and yields
  variable SSH round-trips.
- **Skip --get-default-zone, parse default from --list-all-zones**:
  rejected because list-all-zones output does not mark the default
  zone in any standard distribution.
- **No cross-talk yield (accept double-reporting)**: rejected because
  it produces noisy reports on every default RHEL host.
