firewall: interface-coverage apply guard, target drift, non-destructive DHCP sync
Post-DHCP-incident hardening per HARDEN.md.
- apply guard: refuse (ConflictError, `force` overrides) when a
network-managed interface would end up in no zone; absent
`interfaces` key = hands-off, explicit `[]` = unassign-all
- surface `uncovered_interfaces` in firewall state (lo/wg* filtered)
+ advisory in /api/status/pending; zones.js banner + interfaces-picker
last-zone confirm
- target drift (Option A): absent or default-normalizing target is
unmanaged: not diffed, never re-set by apply; create_zone runs
--new-zone first and sets non-default targets only; importer omits
the target key for default zones
- FirewallToDhcpSync keeps stale DHCP ranges and flags them instead of
deleting; `dnsmasq` affected only on a real gateway mutation
- real pre-apply recovery snapshot in data/firewall/rules.json
({timestamp, default_zone, zones, config}); drop the empty post-apply
skeleton
- daemon shutdown: bounded grace for in-flight tasks + suppressed
teardown exception noise on SIGTERM
- also carries the firewall service-descriptions feature
(get_service_descriptions + service_descriptions state field + UI)
- tests + docs across firewall/status/state/sync/schema; ruff clean,
867 passing
This commit is contained in:
+9
-1
@@ -35,7 +35,7 @@ return annotation references them.
|
||||
|
||||
| Subsystem | Poll | Volatile fields | Top-level keys |
|
||||
|---|---|---|---|
|
||||
| `firewall` | 30s | `interfaces[].ips`, `interfaces[].ipv6` | `config`, `active_zones`, `interfaces`, `available_services`, `zones`, `rich_rules`, `pending`, `timestamp` |
|
||||
| `firewall` | 30s | `interfaces[].ips`, `interfaces[].ipv6` | `config`, `active_zones`, `interfaces`, `available_services`, `service_descriptions`, `uncovered_interfaces`, `zones`, `rich_rules`, `pending`, `timestamp` |
|
||||
| `dnsmasq` | 10s | *(none)* | `config`, `status`, `leases`, `timestamp` |
|
||||
| `nginx` | 60s | *(none)* | `config`, `domains`, `status`, `timestamp` |
|
||||
| `acme` | 300s | *(none)* | `certs`, `email`, `account`, `timestamp` |
|
||||
@@ -58,6 +58,14 @@ Top-level `FirewallState`:
|
||||
{name, mac, state, mtu, ips, ipv6, zone}
|
||||
],
|
||||
available_services: [str], // firewall-cmd --get-services
|
||||
service_descriptions: {svc: str}, // one-line description from the
|
||||
// firewalld service XML definitions
|
||||
// (lib/firewall.py get_service_descriptions,
|
||||
// cached per process)
|
||||
uncovered_interfaces: [str], // network-config interfaces (excluding
|
||||
// lo/wg*) not in any live zone —
|
||||
// advisory coverage warning; empty =
|
||||
// fully covered; NOT counted in pending
|
||||
zones: {zone: zoneDict}, // --list-all-zones; hyphenated keys,
|
||||
// may carry "sources", "ports",
|
||||
// "protocols", "forward-ports", "ics",
|
||||
|
||||
Reference in New Issue
Block a user