switchop

package
v1.164.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 8, 2026 License: MPL-2.0 Imports: 13 Imported by: 0

Documentation

Overview

============================================================================= NFTBan v1.164 - Empty-table classifier (switchop ghost cleanup) ============================================================================= SPDX-License-Identifier: MPL-2.0 meta:name="installer-switchop-classify" meta:type="lib" meta:owner="Antonios Voulvoulis <contact@nftban.com>" meta:created_date="2026-06-08" meta:description="Classify-empty primitive for ghost-table cleanup: count non-structural rule lines so populated/operator tables are preserved" meta:inventory.files="internal/installer/switchop/classify.go" meta:inventory.binaries="" meta:inventory.env_vars="" meta:inventory.config_files="" meta:inventory.systemd_units="" meta:inventory.network="" meta:inventory.privileges="root" =============================================================================

v1.164 switchop classify-empty (PR-A + PR-B)

Ghost-table cleanup historically deleted certain nftables tables unconditionally. That is correct for tables that an iptables-nft compat shim creates purely as a side-effect of iptables being installed (ip filter/nat/mangle/security, inet firewalld) — those are skeletons by construction and never hold operator rules.

It is NOT correct for tables that can legitimately hold operator or kernel content even when an empty skeleton of the same name also occurs in the iptables-nft world. Two cases motivated this primitive:

ip raw / ip6 raw  — an empty `raw` table is the iptables-nft skeleton, but
                    a populated `raw` table holds real NOTRACK / conntrack
                    exemptions an operator (or the kernel default path) put
                    there. Deleting it silently drops those rules.
inet filter       — already classify-then-act'd by the CVE-2025-NFTBAN-001
                    guard (detect/cve_inet_filter.go), which PRESERVES a
                    populated operator-owned table. The unconditional
                    delete in CleanGhostTables could then destroy exactly
                    what the detect phase had deliberately kept.

TableIsEmpty is the shared primitive that lets CleanGhostTables remove only proven-empty skeletons and preserve populated tables. It reuses the same "is this a rule?" structural-line heuristic as the CVE guard's inetFilterRuleCount (detect/cve_inet_filter.go) so the two paths classify identically; this is the generalized form (it also treats set/elements/map structural blocks as non-rules so set-only tables read as empty-of-rules).

=============================================================================

============================================================================= NFTBan v1.75.1 - Installer nftables Service Enable ============================================================================= SPDX-License-Identifier: MPL-2.0 meta:name="installer-switchop-enable" meta:type="lib" meta:owner="Antonios Voulvoulis <contact@nftban.com>" meta:created_date="2026-04-04" meta:description="Enable and start nftables service with xt-compat pre-check" meta:inventory.files="internal/installer/switchop/enable.go" meta:inventory.binaries="" meta:inventory.env_vars="" meta:inventory.config_files="" meta:inventory.systemd_units="nftables.service" meta:inventory.network="" meta:inventory.privileges="root" =============================================================================

============================================================================= NFTBan v1.73 - Installer Ghost Table Cleanup ============================================================================= SPDX-License-Identifier: MPL-2.0 meta:name="installer-switchop-ghost" meta:type="lib" meta:owner="Antonios Voulvoulis <contact@nftban.com>" meta:created_date="2026-04-04" meta:description="Remove ghost nftables tables from conflicting firewalls" meta:inventory.files="internal/installer/switchop/ghost.go" meta:inventory.binaries="" meta:inventory.env_vars="" meta:inventory.config_files="" meta:inventory.systemd_units="" meta:inventory.network="" meta:inventory.privileges="root" =============================================================================

============================================================================= NFTBan v1.73 - Installer Firewall Rebuild ============================================================================= SPDX-License-Identifier: MPL-2.0 meta:name="installer-switchop-rebuild" meta:type="lib" meta:owner="Antonios Voulvoulis <contact@nftban.com>" meta:created_date="2026-04-04" meta:description="Run nftban firewall rebuild with timeout — FATAL on failure" meta:inventory.files="internal/installer/switchop/rebuild.go" meta:inventory.binaries="" meta:inventory.env_vars="" meta:inventory.config_files="" meta:inventory.systemd_units="" meta:inventory.network="" meta:inventory.privileges="root" =============================================================================

============================================================================= NFTBan v1.73 - Installer SSH Port Live Set Guard ============================================================================= SPDX-License-Identifier: MPL-2.0 meta:name="installer-switchop-sshguard" meta:type="lib" meta:owner="Antonios Voulvoulis <contact@nftban.com>" meta:created_date="2026-04-04" meta:description="Ensure SSH port is in live nft sets before rebuild" meta:inventory.files="internal/installer/switchop/sshguard.go" meta:inventory.binaries="" meta:inventory.env_vars="" meta:inventory.config_files="" meta:inventory.systemd_units="" meta:inventory.network="" meta:inventory.privileges="root" =============================================================================

============================================================================= NFTBan v1.75.1 - Installer Takeover Operations ============================================================================= SPDX-License-Identifier: MPL-2.0 meta:name="installer-switchop-takeover" meta:type="lib" meta:owner="Antonios Voulvoulis <contact@nftban.com>" meta:created_date="2026-04-04" meta:description="Disable conflicting firewalls during takeover with CSF panel disarm" meta:inventory.files="internal/installer/switchop/takeover.go" meta:inventory.binaries="" meta:inventory.env_vars="" meta:inventory.config_files="" meta:inventory.systemd_units="" meta:inventory.network="" meta:inventory.privileges="root" =============================================================================

Index

Constants

View Source
const (
	// CronManifestSchemaVersion is the on-disk schema version of the
	// manifest. Reader rejects manifests whose schema_version field
	// does not match this constant exactly. Bumping this constant is
	// a contract event (treat as Amendment-1 §31 A.4 evolution).
	CronManifestSchemaVersion = "1.0.0"

	// CronManifestDir is the on-disk directory the manifest writer
	// stores backups + manifest.json under. Hardcoded by §42.2 lock.
	CronManifestDir = "/var/lib/nftban/state/csf-cron-backup"

	// CronManifestFile is the absolute path of the manifest JSON
	// file (CronManifestDir + "/manifest.json").
	CronManifestFile = "/var/lib/nftban/state/csf-cron-backup/manifest.json"

	// CronCSFSrcPath / CronLFDSrcPath are the canonical /etc/cron.d
	// source paths the writer backs up and the reader restores to.
	// Hardcoded by §42.2 lock — only these two cron files are
	// backed up; never any other /etc/cron.d/* entry.
	CronCSFSrcPath = "/etc/cron.d/csf-cron"
	CronLFDSrcPath = "/etc/cron.d/lfd-cron"
)

Variables

View Source
var (
	// ErrCronManifestSchemaMismatch is returned by the reader when
	// manifest.json parses but its schema_version does not match
	// CronManifestSchemaVersion exactly.
	ErrCronManifestSchemaMismatch = errors.New("cron manifest: schema_version mismatch")

	// ErrCronManifestSHA256Mismatch is returned by the reader when
	// a manifest entry's recorded sha256 does not match the actual
	// sha256 of the on-disk backup file. Indicates corruption or
	// tampering — restore MUST refuse this entry.
	ErrCronManifestSHA256Mismatch = errors.New("cron manifest: sha256 mismatch — backup file does not match manifest entry")

	// ErrCronManifestUnknownEntry is returned when the manifest lists
	// a Path that is not one of the two §42.2-locked cron source
	// paths (/etc/cron.d/csf-cron or /etc/cron.d/lfd-cron). Defensive
	// guard; readers MUST refuse unknown entries.
	ErrCronManifestUnknownEntry = errors.New("cron manifest: entry path is not in the §42.2 locked set {csf-cron, lfd-cron}")

	// ErrCronManifestParseFailed is returned by the reader when
	// manifest.json cannot be parsed as JSON. Distinct from a
	// missing manifest (ReadCronBackupManifest returns ok=false in
	// that case, no error).
	ErrCronManifestParseFailed = errors.New("cron manifest: failed to parse manifest.json")
)

Functions

func AssertSSHInLiveSet

func AssertSSHInLiveSet(exec executor.Executor, sshPort int, log *logging.Logger)

AssertSSHInLiveSet verifies the SSH port exists in the live nft tcp_ports_in sets for both ip and ip6. If missing, adds it. Call after EnableNftables (nftban tables must exist) and before/after rebuild.

func CleanGhostTables

func CleanGhostTables(exec executor.Executor, log *logging.Logger)

CleanGhostTables removes known ghost nftables tables.

Three classes, by removability:

  • ghostTables (filter/nat/mangle/security/firewalld): unconditional delete — these are always compat-shim skeletons.
  • rawGhostTables (ip raw / ip6 raw): classify-empty (PR-A) — delete only if proven empty; preserve populated operator/kernel content.
  • inet filter: classify-empty + override (PR-B) — delete only if proven empty; preserve populated operator-owned table unless the operator explicitly sets NFTBAN_ALLOW_REMOVE_INET_FILTER=1.

Ignores errors for tables that don't exist.

func ComputeCronBackupSHA256 added in v1.100.4

func ComputeCronBackupSHA256(content []byte) string

ComputeCronBackupSHA256 returns the lowercase-hex sha256 of the given content. Used by the writer to record the manifest entry and by the reader to verify the on-disk backup file's integrity before A.4 restoration.

func DisableConflicts

func DisableConflicts(exec executor.Executor, conflicts []detect.Conflict, panel detect.PanelType, log *logging.Logger) error

DisableConflicts stops, disables, and masks all conflicting firewalls. For CSF conflicts on DirectAdmin servers, also disarms CustomBuild so that `./build update` does not re-enable CSF.

func EnableNftables

func EnableNftables(exec executor.Executor, distro *detect.DistroInfo, log *logging.Logger) error

EnableNftables enables and starts the nftables service, then verifies. Runs cleanXtCompat() first to remove stale xt target rules that would prevent nftables from starting (common on CSF/cPanel servers).

func InjectEmergencySSH added in v1.74.0

func InjectEmergencySSH(exec executor.Executor, sshPort int, log *logging.Logger) error

InjectEmergencySSH creates a minimal inet table that accepts the SSH port. This table acts as a last-resort safety net during install transitions. It MUST be removed only after nftban rules are proven in the kernel. Idempotent: deletes any existing emergency table before creating.

Priority -1: evaluated before nftban chains (priority 0). Policy accept: fail-open — safety net, not security boundary.

func Rebuild

func Rebuild(exec executor.Executor, log *logging.Logger) error

Rebuild runs "nftban firewall rebuild" and returns an error if it fails. Shell rebuild exit code contract (authoritative — do not redefine):

0 = PROTECTED (all checks passed)
1 = DEGRADED  (firewall operational, some module checks failed)
2 = FAILED    (rollback happened)
3 = FATAL     (rollback also failed)

Exit 1 (DEGRADED) is expected during upgrade: module-scoped chains require the daemon to be running, which may not be the case yet. Only exit 2+ is treated as a fatal rebuild failure.

func RemoveEmergencySSH added in v1.74.0

func RemoveEmergencySSH(exec executor.Executor, log *logging.Logger)

RemoveEmergencySSH removes the emergency SSH table. Call only after nftban rules are proven in the kernel with SSH port present. No-op if table doesn't exist.

func TableIsEmpty added in v1.164.0

func TableIsEmpty(exec executor.Executor, family, table string) bool

TableIsEmpty reports whether the named nft table exists and contains zero rule lines — i.e. it is a bare iptables-nft / distro skeleton safe to remove. It runs `nft list table <family> <table>` and counts non-structural, non-blank lines (the same heuristic the CVE inet-filter guard uses).

Conservative by contract: if the table does not exist, or `nft` errors (non-zero exit), TableIsEmpty returns false so the caller does NOT treat it as a removable skeleton. Callers gate the actual delete on NftTableExists first; the existence re-check here is defense-in-depth for the error case.

func VerifyCronBackupEntry added in v1.100.4

func VerifyCronBackupEntry(exec executor.Executor, entry CronManifestEntry) ([]byte, error)

VerifyCronBackupEntry reads the on-disk backup for entry and compares its sha256 to the manifest record. Returns ErrCronManifestSHA256Mismatch on mismatch. Reads via the executor abstraction.

Types

type CronManifest added in v1.100.4

type CronManifest struct {
	SchemaVersion string              `json:"schema_version"`
	CapturedAt    time.Time           `json:"captured_at"`
	Files         []CronManifestEntry `json:"files"`
}

CronManifest is the manifest.json on-disk shape.

func ReadCronBackupManifest added in v1.100.4

func ReadCronBackupManifest(exec executor.Executor, log *logging.Logger) (CronManifest, bool, error)

ReadCronBackupManifest returns the parsed manifest if present and schema-valid. Three return shapes:

  • Manifest absent (no manifest.json at CronManifestFile): returns (zero, false, nil). Caller (A.4 step) treats this as the graceful soft-skip case for pre-PR-26 hosts.
  • Manifest present but corrupt (parse failure or schema mismatch): returns (zero, true, ErrCronManifestParseFailed or ErrCronManifestSchemaMismatch). Caller refuses A.4.
  • Manifest present and parseable: returns (manifest, true, nil). Caller still verifies per-entry sha256 against on-disk backups before restoring.

Per-entry integrity is the caller's responsibility (use ComputeCronBackupSHA256 + compare to entry.SHA256). The reader here only validates the manifest structure.

func WriteCronBackupManifest added in v1.100.4

func WriteCronBackupManifest(exec executor.Executor, log *logging.Logger) (CronManifest, error)

WriteCronBackupManifest captures the two §42.2-locked cron files (CronCSFSrcPath, CronLFDSrcPath) before they are removed at install-time. For each file that exists:

  • Reads content via exec.ReadFile.
  • Reads metadata via exec.Stat (mode/uid/gid/size).
  • Computes sha256.
  • Writes the content to CronManifestDir/<backup-name> via exec.WriteFileAtomic.

Then writes manifest.json containing exactly the entries that were backed up. Entries for files that did not exist at capture time are NOT included in the manifest.

Returns the manifest that was written, plus an error if any step failed. The caller (disarmCSFArtifacts) MAY proceed with the rm even if manifest writing fails — the rm is the install-time invariant; the manifest is best-effort fidelity. But on success the caller should log the manifest path so the operator can observe it.

MUST be called BEFORE the cron files are removed; otherwise the content read returns os.ErrNotExist and the entry is skipped.

type CronManifestEntry added in v1.100.4

type CronManifestEntry struct {
	Path       string `json:"path"`        // absolute /etc/cron.d/<name>
	BackupName string `json:"backup_name"` // basename within CronManifestDir
	SHA256     string `json:"sha256"`      // hex sha256 of the backed-up content
	Mode       uint32 `json:"mode"`        // os.FileMode-compatible permission bits
	UID        int    `json:"uid"`
	GID        int    `json:"gid"`
	Size       int64  `json:"size"`
}

CronManifestEntry records one backed-up cron file.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL