Firewall (nftables)
CSM includes a native nftables firewall engine that replaces LFD and fail2ban. It uses the kernel netlink API directly via google/nftables - no iptables, no Perl, no shell commands.
Features
- Atomic ruleset - single netlink transaction, no partial application
- Named IP sets with per-element timeouts (blocked, allowed, infra, country)
- Rate limiting - SYN flood, UDP flood, per-IP connection rate, per-port flood
- Country blocking via MaxMind GeoIP CIDR ranges
- Outbound SMTP restriction by UID (prevent spam from compromised accounts)
- Subnet/CIDR blocking with auto-escalation from individual IPs and safety guards for infra, local, and allowed addresses
- Permanent block escalation after repeated temp blocks
- Dynamic DNS hostname resolution (updated every 5 min) with grace-period guard against transient resolver failures
- IPv6 dual-stack with separate sets
- Commit-confirmed safety - Juniper-style auto-rollback timer
- Infra IP protection - refuses to block infrastructure IPs
- Auto-response dry-run - safety default that records intended blocks without touching nftables
- Verdict callback - optional advisory hook to the panel before each auto-block (allow / block / attach metadata)
- cphulk integration - unblock flushes cphulk too
- Audit trail - JSONL log with 10MB rotation
- State persistence with atomic writes
CLI Commands
# Status
csm firewall status # Show status and statistics
csm firewall ports # Show configured port rules
# Block / Allow
csm firewall deny <ip> [reason] # Block IP permanently
csm firewall allow <ip> [reason] # Allow IP (all ports)
csm firewall allow-port <ip> <port> [reason] # Allow IP on specific port
csm firewall remove <ip> # Remove from blocked and allowed
csm firewall remove-port <ip> <port> # Remove port-specific allow
# Temporary
csm firewall tempban <ip> <dur> [reason] # Temporary block
csm firewall tempallow <ip> <dur> [reason] # Temporary allow
# Subnets
csm firewall deny-subnet <cidr> [reason] # Block subnet
csm firewall remove-subnet <cidr> # Remove subnet block
# Search
csm firewall grep <pattern> # Search blocked/allowed IPs
csm firewall lookup <ip> # GeoIP + block status lookup
# Bulk operations
csm firewall deny-file <path> # Bulk block from file
csm firewall allow-file <path> # Bulk allow from file
csm firewall flush # Clear all dynamic blocks
# Safety
csm firewall apply-confirmed <minutes> # Apply with auto-rollback timer
csm firewall confirm # Confirm applied changes
csm firewall rollback status|confirm|revert # Manage pending config rollback
csm firewall restart # Reapply full ruleset
# Profiles
csm firewall profile save|list|restore <name> # Profile management
# Audit
csm firewall audit [limit] # View audit log
# GeoIP
csm firewall update-geoip # Download country IP blocks
# Cloudflare
csm firewall cf-status # Show Cloudflare IP whitelist status
Configuration
Firewall defaults can be edited in two places:
- Web UI: Settings -> Firewall section. Port lists, rate limits, flood protection, deny caps, country block, and outbound SMTP restriction are all editable. Changes are restart-class. The save endpoint warns if the WebUI listen port is missing from
tcp_in. Theport_floodper-port rule list is YAML-only for now. - YAML: edit
/etc/csm/csm.yamldirectly. Runcsm rehashthensystemctl restart csm.
Tentative apply (rollback timer)
The Firewall section in the Web UI offers two save buttons. Save writes
the new config and prompts you to restart. Apply with rollback timer
writes the new config, restarts the daemon, and starts a timer (default 5
minutes, range 1-30). If you do not click Confirm before the timer
expires, the daemon restores the previous config and restarts again. This
protects against locking yourself out by, for example, removing the WebUI
port from tcp_in.
When the Web UI is unreachable (firewall mistuned, daemon broken), use the CLI escape hatch:
csm firewall rollback status
csm firewall rollback confirm
csm firewall rollback revert
Rollback state survives daemon restarts (the snapshot is persisted in bbolt). On startup the daemon checks for a pending rollback: if the deadline has already passed it restores the previous config and restarts; otherwise it rearms the timer for the remaining window.
firewall:
enabled: true
ipv6: false
conn_rate_limit: 200 # new connections per minute per IP (CGNAT-tolerant)
syn_flood_protection: true
conn_limit: 400 # max concurrent connections per IP (0 = disabled)
smtp_block: false # restrict outbound SMTP
log_dropped: true
dyndns_hosts: # resolved every 5 min and whitelisted
- "monitoring.example.com"
Full firewall reference: Configuration - Firewall.
Auto-response interaction
Auto-block calls require firewall.enabled: true because they go through the firewall engine. The engine consults two policy hooks first:
-
auto_response.verdict_callback- when enabled, the engine POSTs a signed JSON request to the panel after local validation and infra-IP safety checks. When a secret is configured, CSM rejects unsigned callback replies by default. The panel can downgrade toallow(audit-only), attachtenant_idfor downstream correlation, or add a note. CSM fails open on hook errors. Wire contract:docs/verdict-callback-contract.md. -
auto_response.dry_run- when true (or absent; safety default),BlockIP()records the intended block to bbolt and returns success without touching nftables. Manualcsm firewall ...operator commands bypass viaBlockIPForceand always apply. Verify withcsm firewall statusafter policy changes; “Recently Blocked” timestamps newer than the last restart confirm live mode. See Auto-response - Dry-run safety default.
Subnet blocks refuse the default route and any range that contains an infrastructure IP, a resolved infra hostname, a local host address, a full-IP allow, or a port-specific allow. Remove the allow or narrow the CIDR before applying the block.
Infrastructure IP DNS guard
Hostnames listed in top-level infra_ips or firewall.infra_ips are resolved every 5 minutes and their current addresses feed the infra auto-block guard. If a hostname stops resolving, the daemon emits an infra_ips_unresolvable Warning finding and keeps the last known addresses protected during the grace period (default 10 min). This prevents a transient DNS outage from deprotecting the management plane. The finding auto-clears when resolution recovers.