Skip to content
LinkState
Go back

The remote SSH lockout after an atomic replace

Blameless Post‑Mortem: Self‑Inflicted Firewall Outage

What a blameless post‑mortem is

A structured review that answers what happened, why it happened, and how to prevent recurrence without assigning individual fault. It surfaces systemic gaps—broken assumptions, missing safety rails, detection or response deficiencies—while preserving a psychologically safe environment for engineers.

Why it matters in infrastructure‑heavy environments


Incident Overview

Timestamp (UTC)Event
2025-09-20 02:10:12CI pipeline pushed a new nftables ruleset to all 12 edge firewalls via SSH.
2025-09-20 02:14:03Alerts: loss of SSH, BGP peers flapped, downstream health checks failing.
2025-09-20 02:14:45On‑call engineer’s SSH reconnection timed out.
2025-09-20 02:16:10IPMI console obtained; inet filter table replaced with a rule set lacking an established,related accept rule and default policy drop.
2025-09-20 02:20:05Restored previous ruleset from nightly backup (nft -f /var/backups/nftables/2025-09-19.nft).
2025-09-20 02:28:30SSH connectivity, BGP peers, and service traffic restored.
2025-09-20 02:45:00Incident declared resolved; post‑mortem scheduled.

Technical details

Initial response & containment

  1. Detection – Prometheus alerts (ssh_down, bgp_peer_down) fired within 20 s.
  2. Isolation – Engineers switched to out‑of‑band IPMI consoles.
  3. Rollback – Re‑applied the last known good ruleset from a nightly Git‑backed backup via nft -f.
  4. Verification – Diffed nft list ruleset against the backup, confirmed equality, then validated SSH and BGP.
  5. Communication – Incident channel updated every 5 min; a temporary “firewall‑maintenance” banner warned ops teams.

Broken Assumption

Assumption: The CI‑driven nft -f push would be purely additive—only inserting the SSH allow rule while leaving the existing rule set intact.

Basis:

How it caused the outage

  1. flush table inet filter + delete table inet filter removed the existing table and all its chains.
  2. A new table was created with a default drop policy on the input chain.
  3. Only a single SSH allow rule was added; it matched new SYN packets from 10.0.0.0/8 but not existing ESTABLISHED traffic.
  4. Established sessions (including the SSH channel used to push the change) were dropped instantly.

Lessons


Missing Safety Rails

StepCurrent PracticeGap
AuthoringEngineers wrote nft scripts in Git; no linting for destructive ops.No automated detection of flush/delete.
ReviewPeer review focused on functional intent; no safety checklist.Missing item: “Does the script contain any table‑level destructive operations?”
Pre‑change validationNone; script sent directly via SSH.No syntax check (nft -c), no dry‑run, no diff against current state.
Canary / staged rolloutAll devices updated simultaneously.No incremental exposure; blast radius = 100 %.
Verification gatePost‑change manual SSH test (failed because of outage).No automated, pre‑emptive verification (connectivity, traffic flow).
Rollback triggerManual restore from backup after outage detected.No automatic detection‑and‑rollback mechanism.
Blast‑radius controlNo limits; change applied to entire fleet.No mechanism to restrict change to a subset.
Operator intervention pointOnly after total loss of connectivity (out‑of‑band needed).No early‑warning or manual abort option.

Key missing rails

  1. Pre‑change syntactic & semantic validation (nft -c + diff‑based dry‑run).
  2. Change‑scope limitation (canary hosts, feature flags, or time‑bounded windows).
  3. Automated verification (connectivity test, traffic‑plane validation, BGP peer state).
  4. Automatic rollback on verification failure.
  5. Blast‑radius controls and an explicit operator abort point.

Safer Remote‑Change Pattern for nftables

Adopt a transaction‑oriented workflow that validates, limits exposure, and can roll back automatically.

1. Prepare a immutable backup

# On each target firewall (or via a central orchestration tool)
nft list ruleset > /var/backups/nftables/$(date +%F_%T).nft

2. Validate the proposed changeset locally

# Syntax check
nft -c -f /opt/nftables/proposed.nft

# Dry‑run: load into a temporary table and diff against current state
nft -f /opt/nftables/proposed.nft -I netdev   # load into a temporary namespace if supported
nft list ruleset > /tmp/proposed.nft
diff -u /var/backups/nftables/current.nft /tmp/proposed.nft

If the diff shows only the intended additions, proceed; otherwise abort.

3. Canary rollout

4. Fleet‑wide apply with transaction boundaries

Use nft’s built‑in transaction semantics: the -f flag loads the entire file as a single atomic transaction.

ssh root@fwX "nft -f /opt/nftables/proposed.nft"

Because the load is atomic, either the whole ruleset replaces the old one or nothing changes if an error occurs.

5. Automatic rollback trigger

6. Post‑change validation & cleanup


Summary of improvements

Implementing this pattern transforms a risky, additive‑assumption‑driven push into a safe, verifiable, and self‑healing change process for nftables‑based firewalls.


Share this post on:

Previous Post
Running centralized and distributed gateways during migration
Next Post
Exporter overhead on already busy routers