Install
openclaw skills install linux-firewall-hardeningSafe Linux firewall hardening with backend detection, idempotent atomic rules, rollback protection, and AI-executable state-machine workflows. Covers ufw, firewalld, nftables, iptables, Docker, Kubernetes CNI awareness, and fail2ban with compliance mapping to CIS/PCI-DSS/SOC2.
openclaw skills install linux-firewall-hardening| Condition | Alternative |
|---|---|
| Kubernetes worker node | Use NetworkPolicies / CiliumNetworkPolicy |
| Firewall managed by Terraform/Ansible/Puppet/Chef | Update IaC source of truth |
| Cloud workload with Security Group / NSG only | Use cloud provider's firewall API |
| Inside a container | Escalate to host operator |
| WSL2, macOS, or shared/managed hosting | See references/special-environments.md |
Support files:
scripts/audit-firewall.sh(run first),scripts/firewall-plan.sh(dry-run),scripts/firewall-verify.sh(post-apply). Detailed backend guides, Docker/K8s policies, observability, compliance, and recovery are inreferences/.
iptables -F or nft flush ruleset without a verified backup. Docker/K8s networking will break.DROP policy on INPUT before allowing your current SSH port. Immediate lockout.iptables management without replacement NAT/routing rules.networking.service or NetworkManager remotely without console access.limit rate. Disk flood.Follow states in order. Do not skip.
DETECT → SELECT → PLAN → VALIDATE → APPLY → VERIFY
Run the audit script:
bash scripts/audit-firewall.sh # Human-readable
bash scripts/audit-firewall.sh --json # Machine-readable
Key outputs: confidence, risk_tier, recommended_backend, halt_reasons, k8s_node, iac_owner.
| Tier | Confidence | Agent Behavior |
|---|---|---|
auto | ≥ 90% | Proceed automatically to PLAN |
confirmed | 70–89% | Proceed but require human confirmation before APPLY |
manual | 50–69% | Audit-only mode. Generate recommendations, do not apply. |
halt | < 50% | Stop immediately. Escalate findings to operator. |
Additional halt triggers (regardless of confidence): containerized, K8s node, IaC managed, no rollback mechanism available.
| Condition | Path | Detail |
|---|---|---|
Risk tier = halt | STOP | Resolve blockers first |
| Inside container | STOP | Escalate to host operator |
| K8s node detected | STOP | references/k8s-policy.md |
| Ubuntu/Debian + ufw active | Phase: UFW | references/backend-ufw.md |
| ufw + firewalld both active | STOP | Resolve conflict |
| RHEL/Rocky/Alma + firewalld active | Phase: firewalld | references/backend-firewalld.md |
| nftables active, no frontend | Phase: nftables | references/backend-nftables.md |
| iptables only | Phase: iptables | references/backend-iptables.md |
| Docker host | Apply Docker Hardening after phase above | references/docker-hardening.md |
Before modifying rules, verify no IaC tool manages the firewall. If Terraform/Ansible/Puppet/Chef/cloud-init is detected → do not mutate. Update the source of truth instead. Full detection logic is in scripts/audit-firewall.sh.
Optionally load a pre-built security profile (references/security-profiles.md):
| Profile | Use Case |
|---|---|
public-web-server | Open 22, 80, 443. Rate-limit SSH. |
internal-database | SSH from mgmt subnet only. DB port from app subnet only. |
bastion-host | SSH only. Aggressive rate limiting. |
zero-trust-node | Default deny all inbound and outbound. |
Or use declarative YAML (references/declarative-policy.md):
Imperative (state machine) → Ad-hoc hardening, incident response
Declarative (YAML) → GitOps, multi-host, reproducible
Mixed → YAML as source-of-truth, state machine for verification
Generate a dry-run diff before applying:
bash scripts/firewall-plan.sh --profile public-web-server
bash scripts/firewall-plan.sh --ports 22,80,443
bash scripts/firewall-plan.sh --json # Machine-readable diff
Review the output. If risk_tier is confirmed, present the plan and wait for human confirmation before APPLY.
Plan JSON schema (machine-readable output):
{
"backend": "ufw",
"current_state": "active",
"diff": {
"add": [{"port": 80, "proto": "tcp", "source": "any"}],
"skip": [{"port": 22, "proto": "tcp", "reason": "already_exists"}],
"remove": []
},
"risk_assessment": "low",
"estimated_disruption": "none",
"approval_token": "sha256:abc123..."
}
Approval gate: PLAN output includes an approval_token (hash of plan content). APPLY must be called with --approved-plan=<token>. Token mismatch → exit code 41. This forces explicit human confirmation before Apply.
Audit caching: firewall-plan.sh internally calls audit-firewall.sh --json and caches to /tmp/firewall-audit.json (TTL 5 min). Use --refresh-audit to force refresh.
BACKUP_DIR="$HOME/firewall-backup-$(date +%Y%m%d-%H%M%S)"
mkdir -p "$BACKUP_DIR"
sudo iptables-save > "$BACKUP_DIR/iptables-v4.rules" 2>/dev/null || true
sudo ip6tables-save > "$BACKUP_DIR/iptables-v6.rules" 2>/dev/null || true
sudo nft list ruleset > "$BACKUP_DIR/nftables.rules" 2>/dev/null || true
sudo ufw status verbose > "$BACKUP_DIR/ufw-status.txt" 2>/dev/null || true
sudo firewall-cmd --list-all --zone=$(sudo firewall-cmd --get-default-zone) > "$BACKUP_DIR/firewalld-default.txt" 2>/dev/null || true
echo "Backup saved to $BACKUP_DIR"
The rollback restores from backup — not just disables the firewall — so Docker NAT and pre-existing rules are preserved. Dual-backend: at preferred, systemd-run fallback. Full script in SKILL.md under Validate state (see previous version), also summarized in references/recovery.md.
atq or systemctl --user list-units)auto or confirmedAll commands use idempotent patterns: check-before-set. Safe to run repeatedly.
| Backend | Pattern |
|---|---|
| ufw | sudo ufw status | awk '{print $1}' | grep -qx "22/tcp" || sudo ufw allow 22/tcp |
| firewalld | sudo firewall-cmd --query-service=ssh || sudo firewall-cmd --permanent --add-service=ssh |
| iptables | sudo iptables -C INPUT -p tcp --dport 22 -j ACCEPT 2>/dev/null || sudo iptables -A ... |
| nftables | Atomic ruleset: nft -c -f /etc/nftables.conf.new && nft -f /etc/nftables.conf.new |
Full step-by-step commands per backend: references/backend-ufw.md, references/backend-firewalld.md, references/backend-nftables.md, references/backend-iptables.md.
Docker bypasses ufw by default. Use DOCKER-USER chain. Full guide: references/docker-hardening.md.
Default: AUDIT-ONLY. Never modify host firewall. Full policy: references/k8s-policy.md.
Run post-hardening checks:
bash scripts/firewall-verify.sh
Success criteria (all must pass):
0.0.0.0)Verify behavior contract:
bash scripts/manual-rollback.sh <backup_dir>| Code | Meaning | Agent Action |
|---|---|---|
| 0 | Success | Continue |
| 10 | Backend conflict | Halt; resolve manually |
| 11 | Backend detection failed | Halt; check firewall stack |
| 12 | Multiple backends active | Halt; resolve conflict |
| 20 | IaC-managed | Halt; update IaC source |
| 21 | Inside container | Halt; escalate to host operator |
| 22 | K8s node detected | Halt; audit-only mode |
| 30 | Low confidence (<70%) | Drop to audit-only mode |
| 31 | No rollback capability | Halt; ensure at or systemd-run |
| 40 | Preflight failed | Halt; check prerequisites |
| 41 | Plan approval mismatch | Halt; re-run PLAN with approval |
| 42 | Backup failed | Halt; resolve disk/permissions |
| 50 | Apply failed | Auto-rollback triggered |
| 51 | Apply partial | Auto-rollback triggered; verify backup |
| 60 | Verify failed | Auto-rollback triggered |
| 61 | State file conflict | Abort; resolve stale state |
If fail2ban is installed:
| Host Firewall | Recommended backend |
|---|---|
| ufw | ufw or systemd |
| firewalld | firewalld |
| nftables | nftables |
| iptables | auto (default) |
After changing backend: sudo fail2ban-client restart && sudo fail2ban-client status sshd.
If you lose connectivity, priority order:
Full procedures: references/recovery.md.
For agent interrupt-resume scenarios (e.g., Apply failed mid-run, agent restarted), the state machine writes a lightweight state file to enable recovery without starting from Detect:
STATE_DIR="$HOME/.firewall-hardening"
STATE_FILE="$STATE_DIR/state.json"
State file structure:
{
"state": "validate",
"started_at": "2026-05-11T16:00:00Z",
"backend": "ufw",
"risk_tier": "auto",
"backup_dir": "/home/user/firewall-backup-20260511-160000",
"rollback_timer_id": "firewall-rollback-12345",
"plan_hash": "sha256:abc123..."
}
Resume logic:
state.json exists and started_at is within 1 hour → resume from that statestate.json is stale (>1 hour) → delete it and start fresh from DetectState persistence is optional. The skill defaults to restarting from Detect each run. Enable by creating
$STATE_DIRbefore starting.
The host firewall is your second layer. Verify cloud SGs are aligned:
| Cloud | Outer Firewall |
|---|---|
| AWS | Security Groups |
| GCP | VPC Firewall Rules |
| Azure | Network Security Groups |
| DigitalOcean/Linode/Vultr | Cloud Firewall |
| Distro/Env | ufw | firewalld | nftables | iptables | Coverage |
|---|---|---|---|---|---|
| Ubuntu 22.04/24.04 | Primary | — | Backend | Fallback | Full |
| Debian 12 | Primary | — | Backend | Fallback | Full |
| RHEL 9 | — | Primary | Native | Backend | Full |
| Rocky/Alma 9 | — | Primary | Native | Backend | Full |
| Fedora 40+ | — | Primary | Native | Backend | Partial |
| Alpine 3.18+ | — | — | Native | Fallback | Partial |
| Arch | — | — | Native | Fallback | Community |
| Docker host | ✅ DOCKER-USER chain | ✅ docker-hardening.md | ✅ docker-hardening.md | ✅ docker-hardening.md | Full |
| LXC/LXD container | ⚠️ Limited | ⚠️ Limited | ⚠️ Limited | ⚠️ Limited | Partial |
| systemd-nspawn | ⚠️ Limited | ⚠️ Limited | ⚠️ Limited | ⚠️ Limited | Partial |
| WSL2 | ❌ Not supported | ❌ Not supported | ❌ Not supported | ❌ Not supported | None |
Container environments: Docker host is fully supported via DOCKER-USER chain. LXC/LXD/systemd-nspawn have limited support (kernel shares netfilter with host). WSL2 is explicitly unsupported. See
references/special-environments.md.
Establish baselines after hardening: conntrack usage, dropped packet rates, fail2ban ban rate. Monitor for anomalies. Full guide: references/observability.md.
Practices map to CIS, PCI-DSS, and SOC2 controls. Full mapping: references/compliance.md.
| Task | Command |
|---|---|
| Audit environment | bash scripts/audit-firewall.sh --json |
| Plan changes | bash scripts/firewall-plan.sh --profile web |
| Verify after apply | bash scripts/firewall-verify.sh |
| Allow port (ufw, idempotent) | sudo ufw status | awk '{print $1}' | grep -qx "80/tcp" || sudo ufw allow 80/tcp |
| View ufw rules | sudo ufw status numbered |
| View nft rules | sudo nft list ruleset |
| View iptables rules | sudo iptables -L -n -v |
| View ip6tables rules | sudo ip6tables -L -n -v |
| Atomic iptables replace | sudo iptables-restore < /tmp/rules.v4 |
| Dry-run nftables | sudo nft -c -f /etc/nftables.conf |
| Backup rules | sudo iptables-save > ~/iptables.backup |
| fail2ban status | sudo fail2ban-client status sshd |
| Cancel rollback (at) | atrm <jobid> |
| Cancel rollback (systemd-run) | systemctl --user stop firewall-rollback-<pid> |
references/backend-ufw.md — Full UFW phasereferences/backend-firewalld.md — Full firewalld phasereferences/backend-nftables.md — Full nftables phasereferences/backend-iptables.md — Full iptables phasereferences/docker-hardening.md — Docker firewall hardeningreferences/k8s-policy.md — Kubernetes node policyreferences/security-profiles.md — Pre-built configurationsreferences/declarative-policy.md — YAML policy schemareferences/observability.md — Monitoring and baselinesreferences/compliance.md — CIS/PCI-DSS/SOC2 mappingreferences/recovery.md — Recovery proceduresreferences/special-environments.md — WSL2, containers, exit codesscripts/audit-firewall.sh — Environment detectionscripts/firewall-plan.sh — Dry-run diffscripts/firewall-verify.sh — Post-apply verification