Skip to content

fix(firewall): block SSH and UFW changes when firewall state is unknown #89

Closed

Description

@entelecheia

Context

Audit of the modules on 2026-11-09. AGENTS.md requires: "Verify installed firewall state and port allowances before changing SSH ports; unknown state blocks the change." Ubuntu does not follow it.

  • verified: internal/module/ssh.go:195-202 opens the new SSH port in UFW only when queryUFW succeeds; on a failed query it silently goes on and changes the port. firewalld/iptables are never looked at. Rocky does this right (ssh_rocky.go:126).
  • verified: the ufw allow that Apply performs is not reported by Check (ssh.go:56-66), so Check and Apply disagree.
  • verified: internal/module/network.go:92 discards the queryUFW error (st, _ :=), so an unknown state is treated as "inactive, nothing allowed" and Apply runs ufw --force enable.
  • verified: UFW output is parsed three different ways (access.go:30, ssh_rocky.go:211-271, monitoring_firewall.go:23) with different rules for what counts as "allowed".

Implementation starts only on the owner's go-ahead.

Decisions

  • One parsed firewall model with an explicit Known flag; every caller that changes SSH reachability refuses when Known is false.
  • Ubuntu SSH port changes reuse the Rocky verification logic instead of a second implementation.
  • 2026-11-09: the shared inspector reuses the existing Rocky UFW + firewalld probes and their unknown-state semantics; no direct iptables or nftables probing (the earlier mention of iptables probes was wrong). Basis: source check by the implementer.

Design

  • New internal/module/firewall.go: type FirewallState struct { Known, Active bool; Allowed map[int]bool; Rules []Rule } and inspectFirewall(ctx, rc) covering UFW and firewalld, the probes the Rocky path already uses. No new iptables probing.
  • ssh.go Check reports the pending allow; Apply refuses the port change when state is unknown, with an error that names the cause and the fix (open the port, or set ssh.port back).
  • network.go Apply refuses ufw --force enable on unknown state.
  • access.go, ssh_rocky.go, monitoring_firewall.go call the shared parser.

Changes

  • internal/module/firewall.go (new), ssh.go, ssh_rocky.go, network.go, access.go, monitoring_firewall.go and their tests.

Acceptance criteria

  • AC1: Ubuntu SSH port change with a failing ufw status (fakeBin) returns an error and writes no sshd drop-in.
  • AC2: Check lists the UFW allow that Apply would add.
  • AC3: network Apply with a failing status query does not run ufw --force enable.
  • AC4: one UFW parser remains (grep -n 'func .*ufw' internal/module shows only firewall.go), existing firewall tests still pass.
  • AC5: exporter-firewall-scenario and rocky CI jobs pass.

Tasks

  • T1: firewall.go + parser tests from the three existing parsers' fixtures (AC4)
  • T2: switch ssh (Ubuntu) to it, Check/Apply agreement (AC1, AC2)
  • T3: network unknown-state refusal (AC3)
  • T4: switch access, ssh_rocky, monitoring_firewall (AC4, AC5)
    • Done when: go test ./internal/module/... -race and CI green.

Out of scope

  • nftables-native rules management.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions