validate

package
v1.136.1 Latest Latest
Warning

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

Go to latest
Published: May 27, 2026 License: MPL-2.0 Imports: 14 Imported by: 0

Documentation

Overview

============================================================================= NFTBan v1.73 - Installer Post-Install Assertions ============================================================================= SPDX-License-Identifier: MPL-2.0 meta:name="installer-validate-assertions" meta:type="lib" meta:owner="Antonios Voulvoulis <contact@nftban.com>" meta:created_date="2026-04-04" meta:description="Post-install kernel + service + state assertions" meta:inventory.files="internal/installer/validate/assertions.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 Authority File Write ============================================================================= SPDX-License-Identifier: MPL-2.0 meta:name="installer-validate-authority" meta:type="lib" meta:owner="Antonios Voulvoulis <contact@nftban.com>" meta:created_date="2026-04-04" meta:description="Write /var/lib/nftban/state/authority and .firewall_authority" meta:inventory.files="internal/installer/validate/authority.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

This section is empty.

Variables

View Source
var DefaultNftbanOwnedPrefixes = []string{
	"/usr/lib/nftban/",
	"/etc/nftban/",
}

DefaultNftbanOwnedPrefixes lists path prefixes considered nftban-owned for PAYLOAD-INVENTORY-001.

View Source
var DefaultSystemBinaryPrefixes = []string{
	"/usr/bin/",
	"/bin/",
	"/usr/sbin/",
	"/sbin/",
	"/usr/local/bin/",
	"/usr/local/sbin/",
}

DefaultSystemBinaryPrefixes lists path prefixes that are exempt from PAYLOAD-INVENTORY-001 (legitimate to call from a unit even though they are not part of the nftban payload).

View Source
var DefaultUnitDirs = []string{
	"/usr/lib/systemd/system",
	"/etc/systemd/system",
}

DefaultUnitDirs is the canonical systemd unit search path for nftban-owned units. Both vendor and operator-owned dirs are scanned so a unit dropped under /etc/systemd/system is still validated.

Functions

func AllPassed

func AllPassed(results []AssertionResult) bool

AllPassed returns true if all assertions passed.

func FailedNames

func FailedNames(results []AssertionResult) []string

FailedNames returns the names of all failed assertions.

func IsAuxiliaryUnit added in v1.135.0

func IsAuxiliaryUnit(basename string) bool

IsAuxiliaryUnit reports whether an nftban unit basename is an auxiliary (metrics/observability) unit per auxiliaryUnitStems. Non-nftban units are never auxiliary (they are out of scope for these invariants entirely).

func IsNftbanUnit added in v1.100.4

func IsNftbanUnit(basename string) bool

IsNftbanUnit returns true if a unit basename is owned by nftban for the purposes of these invariants. Recognized shapes:

nftban.<ext>            — singular "nftban" unit (legacy)
nftband.<ext>           — Go daemon unit (note: no hyphen between
                          "nftban" and "d" — distinct from
                          nftban-* units; both are owned)
nftban-<anything>.<ext> — any hyphenated nftban-prefixed unit
nftband-<anything>.<ext>— any hyphenated nftband-prefixed unit

where <ext> is one of the suffixes in nftbanUnitExtensions.

The matcher is structural rather than enumerated so that future unit shapes (sockets, targets, paths) are covered without a code change. "fake-nftban.service" / "nftbanfake.service" / "sshd.service" do NOT match — the prefix must be a complete identifier separated by '-' or by the unit-extension dot.

func RunPermissionsEnforce added in v1.76.0

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

RunPermissionsEnforce calls `nftban permissions enforce` for full FHS fix (G10 parity).

func SetImmutableFlags added in v1.76.0

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

SetImmutableFlags sets chattr +i on security-critical files (G8 parity). Shell postinst set immutable on nftban.conf and nft_schema.sh to prevent accidental or malicious modification.

func WriteAuthorityFiles

func WriteAuthorityFiles(exec executor.Executor, decision authority.Decision, log *logging.Logger)

WriteAuthorityFiles records the authority decision to state files. Two locations for compatibility:

  • /var/lib/nftban/state/authority (primary, read by Go daemon)
  • /etc/nftban/.firewall_authority (legacy, read by CLI scripts)

Types

type AssertionOpts added in v1.100.4

type AssertionOpts struct {
	// PanelPolicy controls how the panel-survival assertion converts
	// adapter outcomes into a pass/fail verdict. PR26.3+ wires this
	// from the operator's --no-panel flag and any future policy
	// flags through the installer's globalPhaseData.
	PanelPolicy panelfw.PanelPolicy

	// PanelAdapters is an optional adapter slice override. When nil,
	// the assertion uses panelfw.RegisteredAdapters(). Tests pass a
	// fixture-controlled slice (typically containing FakePanelAdapter)
	// to exercise the framework without touching the global registry.
	PanelAdapters []panelfw.PanelAdapter
	// contains filtered or unexported fields
}

AssertionOpts carries policy + dependency injections that downstream assertions need. New in PR26.2 to thread the panel-survival policy through without forcing every existing caller to construct one.

Zero-value AssertionOpts is safe: PanelPolicy defaults to panelfw.DefaultPolicy() (RequirePanelSuccess=true, AllowPanelAbsent=true, OperatorDisabled=false) when unset, and PanelAdapters defaults to the global registry (currently empty in PR26.2).

func (AssertionOpts) WithPanelPolicy added in v1.100.4

func (o AssertionOpts) WithPanelPolicy(p panelfw.PanelPolicy) AssertionOpts

WithPanelPolicy returns a copy of opts with PanelPolicy set and the internal "set" flag enabled, so RunAssertionsWithOpts honors the override even when the caller passes a zero-value PanelPolicy.

type AssertionResult

type AssertionResult struct {
	Name   string
	Passed bool
	Detail string
}

AssertionResult holds the outcome of a single assertion.

func RunAssertions

func RunAssertions(exec executor.Executor, sshPort int, log *logging.Logger) []AssertionResult

RunAssertions performs all post-install assertions with default AssertionOpts (panelfw.DefaultPolicy, empty adapter override). Equivalent to RunAssertionsWithOpts(exec, sshPort, log, AssertionOpts{}).

Existing callers stay source-compatible. Production paths that need to feed an operator-derived policy use RunAssertionsWithOpts.

func RunAssertionsWithOpts added in v1.100.4

func RunAssertionsWithOpts(exec executor.Executor, sshPort int, log *logging.Logger, opts AssertionOpts) []AssertionResult

RunAssertionsWithOpts is the policy-aware entry point. The new panel-survival assertion (PR26.2) consumes opts.PanelPolicy / opts.PanelAdapters; every other assertion is unchanged from the pre-PR26.2 surface.

type ExecDirective added in v1.100.4

type ExecDirective struct {
	// Directive is one of ExecStart, ExecStartPre, ExecStartPost,
	// ExecStop, ExecStopPost, ExecReload.
	Directive string

	// Raw is the full RHS as written in the unit file (after the "=").
	Raw string

	// Binary is the resolved primary executable absolute path.
	// For wrapper invocations like "/bin/sh -c '...'", this is "/bin/sh"
	// and EmbeddedNftbanPaths captures any nftban-owned absolute paths
	// found inside the quoted argument.
	Binary string

	// EmbeddedNftbanPaths lists any nftban-owned absolute paths
	// discovered inside Raw that are NOT the primary Binary. Caught
	// from common shell-wrapper patterns ("/bin/sh -c '/usr/lib/...sh'",
	// "/usr/bin/env BAR=1 /usr/lib/...bin").
	EmbeddedNftbanPaths []string
}

ExecDirective records one Exec* line from a unit file.

type FailedUnitFinding added in v1.100.4

type FailedUnitFinding struct {
	Unit   string
	Active string // e.g., "failed"
	Sub    string // e.g., "failed", "exit-code"
	Detail string // best-effort short description, may be empty
}

FailedUnitFinding describes one nftban-* unit currently in failed state per `systemctl list-units --state=failed`.

type FailedUnitPostInstall added in v1.100.4

type FailedUnitPostInstall struct {
	Unit   string
	Detail string
}

FailedUnitPostInstall records one FAILED-UNIT-POSTINSTALL-001 hit.

type MissingExecPath added in v1.100.4

type MissingExecPath struct {
	UnitFile  string
	Directive string
	Path      string
	RawLine   string
}

MissingExecPath records one SYSTEMD-EXECSTART-001 hit.

type MissingTimerTarget added in v1.100.4

type MissingTimerTarget struct {
	TimerUnit  string
	TargetUnit string
}

MissingTimerTarget records one SYSTEMD-TIMER-PAIR-001 hit.

type ParsedUnit added in v1.100.4

type ParsedUnit struct {
	// Name is the unit basename (e.g., "nftban-unified-exporter.service").
	Name string

	// Path is the absolute on-disk path of the unit file.
	Path string

	// IsTimer is true for *.timer units.
	IsTimer bool

	// Execs is the list of ExecStart/Pre/Post/Stop directives extracted
	// from [Service]. Order preserved for diagnostic output.
	Execs []ExecDirective

	// TimerUnit is the [Timer] Unit= value (or implicit basename.service
	// when the directive is absent). Empty for non-timer units.
	TimerUnit string
}

ParsedUnit is the minimum subset of a systemd unit file needed for PR26.1 validation. Constructed by the host-side gatherer or by tests directly.

func ParseUnitFile added in v1.100.4

func ParseUnitFile(name, path, content string) ParsedUnit

ParseUnitFile parses raw systemd unit content and returns a ParsedUnit. path is the absolute on-disk path; name is the unit basename.

Parsing scope (intentionally narrow):

  • section detection by [Section] header
  • Exec* directives in [Service]
  • Unit= directive in [Timer]
  • leading systemd prefixes ('-', '+', '!', '!!', '@') are stripped

Continuation lines (trailing backslash) are joined. Comments (#, ;) and blank lines are ignored. Quoting is respected: a quoted token is treated as a single argument; absolute-path scan inside the shell-wrapper case looks for tokens starting with '/'.

type PayloadInventory added in v1.100.4

type PayloadInventory struct {
	// Paths is the set of absolute paths the staged install knows
	// about. Population strategy: populate from the install's payload
	// destination table (see internal/installer/payload).
	Paths map[string]bool

	// NftbanOwnedPrefixes lists absolute path prefixes that, if a unit
	// references a path under one of them, that path MUST be in Paths
	// or referenced via a system-binary wrapper. Typical:
	// "/usr/lib/nftban/", "/usr/sbin/nftban", "/etc/nftban/".
	NftbanOwnedPrefixes []string

	// SystemBinaryPrefixes lists absolute path prefixes whose contents
	// are NOT required to be in Paths. Typical: "/usr/bin/", "/bin/",
	// "/usr/sbin/" (excluding the singular "/usr/sbin/nftban"), "/sbin/".
	SystemBinaryPrefixes []string
}

PayloadInventory describes which paths are considered known after install staging.

type RevalidateResult added in v1.98.0

type RevalidateResult struct {
	// Validate1Passed is true if initial assertions all passed.
	Validate1Passed bool

	// FixAttempted is true if bounded safe fix was run (only when V1 fails).
	FixAttempted bool

	// FixExitCode is the exit code of the permissions enforce command.
	FixExitCode int

	// Validate2Passed is true if re-validation passed after fix.
	// Only meaningful when FixAttempted is true.
	Validate2Passed bool

	// FinalPassed is the authoritative result: V1 if no fix needed, V2 if fix ran.
	FinalPassed bool

	// FailedNames contains assertion names that failed in the final validation.
	FailedNames []string
}

RevalidateResult captures the outcome of the VALIDATE_1 → FIX → VALIDATE_2 flow.

func RunWithBoundedFix added in v1.98.0

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

RunWithBoundedFix implements the VALIDATE_1 → FIX → VALIDATE_2 state machine.

Flow:

  1. Run assertions (VALIDATE_1)
  2. If all pass → return success immediately
  3. If some fail → run bounded safe fix (permissions enforce only, INV-I-011)
  4. Re-run assertions (VALIDATE_2, INV-I-013)
  5. Return VALIDATE_2 result as authoritative

The fix runs at most ONCE (INV-I-012). It calls ONLY 'nftban permissions enforce' which is bounded to chmod/chown on FHS-managed paths. It does NOT call 'health fix all'.

type SystemdPayloadInputs added in v1.100.4

type SystemdPayloadInputs struct {
	// Units is the parsed nftban-owned unit set in the install scope.
	// Non-nftban units are filtered upstream by the gatherer; this
	// validator does NOT enforce invariants on third-party units.
	Units []ParsedUnit

	// PathExists reports whether an absolute path exists on disk.
	// Tests stub with a closure over a map; production wires through
	// the executor.
	PathExists func(absPath string) bool

	// Inventory holds payload-known paths and prefix policy.
	Inventory PayloadInventory

	// FailedNftbanUnits is the set of currently-failed nftban units.
	// Populated upstream from `systemctl list-units --state=failed`.
	FailedNftbanUnits []FailedUnitFinding

	// FailedUnitQueryError is non-empty when the upstream gatherer
	// could not enumerate failed units (e.g., systemctl missing,
	// non-zero exit, dbus unavailable). When set,
	// FAILED-UNIT-POSTINSTALL-001 fails closed: the absence of
	// findings cannot be distinguished from a healthy install.
	// Operator must resolve the enumeration error before
	// StateCommitted is permitted.
	FailedUnitQueryError string

	// AllUnitNames is the set of every unit basename present in the
	// install's unit directories (services + timers + sockets). Used
	// by SYSTEMD-TIMER-PAIR-001 to confirm a timer's target service
	// is installed alongside it.
	AllUnitNames map[string]bool
}

SystemdPayloadInputs aggregates everything ValidateInstalledSystemdPayload needs. Decoupled from executor.Executor and the live filesystem so the validator can be exercised by fixture tests.

func GatherSystemdPayloadInputs added in v1.100.4

func GatherSystemdPayloadInputs(exec executor.Executor, log *logging.Logger, inventoryPaths map[string]bool) (SystemdPayloadInputs, error)

GatherSystemdPayloadInputs scans the host for nftban-owned systemd units, parses them, and assembles the input set used by ValidateInstalledSystemdPayload.

inventoryPaths is the staged install's known-path set (typically produced by the payload-staging layer). When nil/empty, the PAYLOAD-INVENTORY-001 check still functions but its `Paths` set is empty — every nftban-owned referenced path will be flagged. Callers SHOULD pass a populated set.

type SystemdPayloadValidationResult added in v1.100.4

type SystemdPayloadValidationResult struct {
	// OK is true iff every invariant passed.
	OK bool

	// MissingExecPaths is the SYSTEMD-EXECSTART-001 finding set.
	MissingExecPaths []MissingExecPath

	// MissingTimerTargets is the SYSTEMD-TIMER-PAIR-001 finding set.
	MissingTimerTargets []MissingTimerTarget

	// UnknownPayloadRefs is the PAYLOAD-INVENTORY-001 finding set.
	UnknownPayloadRefs []UnknownPayloadRef

	// FailedUnits is the FAILED-UNIT-POSTINSTALL-001 finding set, restricted
	// to protection-critical units. A non-empty slice fails the invariant.
	FailedUnits []FailedUnitPostInstall

	// FailedAuxiliaryUnits holds failed nftban units classified as auxiliary
	// (metrics/observability — see IsAuxiliaryUnit). These are surfaced as a
	// non-fatal warning and do NOT fail FAILED-UNIT-POSTINSTALL-001 (so a
	// transient exporter exit-2 cannot DEGRADE the install — D-EXPORTER-
	// SETTLE-WINDOW, v1.135).
	FailedAuxiliaryUnits []FailedUnitPostInstall

	// FailedUnitQueryError mirrors SystemdPayloadInputs.FailedUnitQueryError
	// for the assertion-side detail message. When non-empty,
	// FAILED-UNIT-POSTINSTALL-001 fails closed regardless of
	// FailedUnits content.
	FailedUnitQueryError string
}

SystemdPayloadValidationResult is the structured outcome.

func ValidateInstalledSystemdPayload added in v1.100.4

func ValidateInstalledSystemdPayload(in SystemdPayloadInputs) SystemdPayloadValidationResult

ValidateInstalledSystemdPayload runs all four PR26.1 invariants and returns the aggregate result. Pure function — no I/O. Caller assembles SystemdPayloadInputs from the live host (gatherer) or test fixtures.

func (SystemdPayloadValidationResult) ExecStartOK added in v1.100.4

func (r SystemdPayloadValidationResult) ExecStartOK() bool

ExecStartOK reports whether SYSTEMD-EXECSTART-001 passed.

func (SystemdPayloadValidationResult) FailedUnitsOK added in v1.100.4

func (r SystemdPayloadValidationResult) FailedUnitsOK() bool

FailedUnitsOK reports whether FAILED-UNIT-POSTINSTALL-001 passed. Fails closed if the failed-unit query itself errored: an empty FailedUnits slice cannot prove a healthy install when the enumeration source was unreachable.

func (SystemdPayloadValidationResult) PayloadInventoryOK added in v1.100.4

func (r SystemdPayloadValidationResult) PayloadInventoryOK() bool

PayloadInventoryOK reports whether PAYLOAD-INVENTORY-001 passed.

func (SystemdPayloadValidationResult) TimerPairOK added in v1.100.4

func (r SystemdPayloadValidationResult) TimerPairOK() bool

TimerPairOK reports whether SYSTEMD-TIMER-PAIR-001 passed.

type TimerState added in v1.135.0

type TimerState struct {
	Enabled           bool // systemctl is-enabled (will start on boot)
	ActiveOrScheduled bool // .timer unit active (scheduled to fire)
}

TimerState is the observed runtime state of one timer unit.

type TimerValidationInputs added in v1.135.0

type TimerValidationInputs struct {
	// ReconcileDisabled is true when NFTBAN_RECONCILE_CORE_TIMERS=false — an
	// intentional operator opt-out; validation passes (Skipped), not DEGRADED.
	ReconcileDisabled bool
	// Critical is the set of critical core timer unit names to check.
	Critical []string
	// States maps a critical timer unit -> observed state.
	States map[string]TimerState
}

TimerValidationInputs is the synthetic input set for ValidateCriticalTimers.

func GatherTimerInputs added in v1.135.0

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

GatherTimerInputs builds TimerValidationInputs from the live host.

type TimerValidationResult added in v1.135.0

type TimerValidationResult struct {
	OK      bool     // all critical timers enabled + active/scheduled (or Skipped)
	Skipped bool     // reconcile intentionally disabled
	Missing []string // critical timers not enabled+active (sorted)
}

TimerValidationResult is the verdict consumed by assertCriticalTimersEnabled.

func ValidateCriticalTimers added in v1.135.0

func ValidateCriticalTimers(in TimerValidationInputs) TimerValidationResult

ValidateCriticalTimers checks that every critical core timer is enabled AND active-or-scheduled. Pure (no I/O). NFTBAN_RECONCILE_CORE_TIMERS=false → Skipped+OK (intentional). Optional timers are never inspected.

type UnknownPayloadRef added in v1.100.4

type UnknownPayloadRef struct {
	UnitFile string
	Path     string
}

UnknownPayloadRef records one PAYLOAD-INVENTORY-001 hit: a unit references an nftban-owned path that is not known to the staged payload inventory.

Jump to

Keyboard shortcuts

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