validate

package
v1.230.0 Latest Latest
Warning

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

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

Documentation

Overview

============================================================================= NFTBan v1.73 - Installer Post-Install Assertions ============================================================================= SPDX-License-Identifier: MPL-2.0 SPDX-FileCopyrightText: Copyright (c) 2024-2026 Antonios Voulvoulis <contact@nftban.com> 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 SPDX-FileCopyrightText: Copyright (c) 2024-2026 Antonios Voulvoulis <contact@nftban.com> 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" =============================================================================

SPDX-License-Identifier: MPL-2.0 SPDX-FileCopyrightText: Copyright (c) 2024-2026 Antonios Voulvoulis <contact@nftban.com> meta:name="failed_units.go" meta:type="lib" meta:owner="Antonios Voulvoulis <contact@nftban.com>" meta:description="v1.222.1 Lane 4: injection-safe canonical normalization of the FATAL failed-unit set for structured propagation into install_state SERVICES_FAILED. Accepts only approved nftban systemd unit names (IsNftbanUnit + a strict charset), dedupes, sorts deterministically, and buckets by pre-existence attribution. The result is the single source of truth for the installer + update remediation renderers — no FAILURE_REASON prose parsing, no hardcoded unit."

Index

Constants

View Source
const (
	AttrNewFailed          = "NEW_FAILED_UNIT"
	AttrPreexistingFailed  = "PREEXISTING_STILL_FAILED"
	AttrFailureTimeUnknown = "FAILURE_TIME_UNKNOWN"
)

Attribution classifications surfaced to operators (v1.222.1 Lane 4).

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 AttributionFor added in v1.222.1

func AttributionFor(unit string, preexisting []string) string

AttributionFor returns the operator-facing attribution label for a unit given the pre-existing set (from NormalizeFailedUnits).

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 NormalizeFailedUnits added in v1.222.1

func NormalizeFailedUnits(units []FailedUnitPostInstall) (all, preexisting, inWindow []string)

NormalizeFailedUnits returns the canonical, deduplicated, deterministically sorted fatal nftban unit names, plus pre-existing and in-window buckets for attribution. Only approved nftban unit names survive (injection-safe); anything else is dropped. Classification "PRE_EXISTING_FATAL" → pre-existing; otherwise in-window (new). Order is preserved across multiple units.

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 SafeNftbanUnitName added in v1.222.1

func SafeNftbanUnitName(name string) bool

SafeNftbanUnitName reports whether name is an approved, injection-safe nftban systemd unit name.

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 SetInstallWindowStart added in v1.174.0

func SetInstallWindowStart(t time.Time)

SetInstallWindowStart records the install-window start instant. Call once at the very start of the installer run, before the phases execute. The validate gatherer reads this when building SystemdPayloadInputs. Note: state/file.go INSTALL_TIMESTAMP is the END/write time and must NOT be reused as the window start.

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

	// HealthResource is the v1.222.1 health-resource reconciliation verdict
	// produced in phaseConfigure. When non-nil, the health_resource_policy_active
	// assertion consumes it (single policy path — no recomputation). Nil in
	// contexts that did not run the reconciler (assertion is skipped/PASS).
	HealthResource *healthresource.Verdict

	// SystemdPayloadInputs, when non-nil, is used VERBATIM by RunAssertionsWithOpts
	// in place of GatherSystemdPayloadInputs(real host). v1.223.0 verdict-truth:
	// DATA-only dependency-injection so the systemd-payload assertions are
	// exercisable off-host WITHOUT a mutable package global or var-func seam.
	// Production leaves it nil → the real host gather runs (byte-identical default).
	// Fail-safe is PRESERVED: an empty/zero injected value is fed to
	// ValidateInstalledSystemdPayload exactly like an empty real gather — there is
	// NO nil/empty→PASS shortcut, so PAYLOAD-INVENTORY-001 (and the failed-unit
	// query-error fail-closed) still fire on injected inputs that warrant them.
	SystemdPayloadInputs *SystemdPayloadInputs

	// LogRetentionValidator, when non-nil, overrides the logrotate policy validator
	// used by the logretention_policy_ready assertion. v1.223.0 verdict-truth: nil in
	// production (the assertion then uses the package readinessValidator → the real
	// `logrotate -d`); execution-path tests inject a deterministic validator so the
	// assertion does NOT depend on the `logrotate` binary being installed (it is
	// absent in CI containers). Bounded DI, mirrors SystemdPayloadInputs.
	LogRetentionValidator lr.Validator

	// FailedUnitsOut, when non-nil, receives the FINAL-pass FATAL failed-unit set
	// (spr.FailedUnits) so the caller (phaseValidate) can propagate the STRUCTURED
	// names into install_state SERVICES_FAILED — the v1.222.1 Lane 4 fix for
	// BUG-INSTALLER-FAILED-UNITS-STRUCTURED-STATE-NOT-POPULATED. Recovered/auxiliary
	// units are excluded (only fatal, DEGRADED-causing units).
	FailedUnitsOut *[]FailedUnitPostInstall

	// WhitelistConvergence is the v1.228.5 durable whitelist convergence verdict
	// as persisted in install_state WHITELIST_CONVERGENCE. Callers pass the value
	// they hold (phaseValidate: the verdict phaseConfigure just recorded on the
	// live StateFile; revalidate: the value read back from disk). Consumed by the
	// whitelist_convergence_ok assertion — DATA only, no recomputation here
	// (SyncWhitelist is a mutation and must never run inside validation).
	//
	// BACKWARD COMPATIBILITY (explicit, not incidental): "" means NOT EVALUATED —
	// either an install_state written before v1.228.5 (no WHITELIST_CONVERGENCE=
	// line at all → the parser leaves the field zero) or a caller that does not
	// evaluate this dimension. "" is never read as CONVERGED and never converted
	// into a FAILED. It mirrors the nil-HealthResource SKIP convention above.
	WhitelistConvergence string

	// ConvergenceVerified is the v1.230.0 Gate 6R post-update convergence verdict as
	// persisted in install_state CONVERGENCE_VERIFIED. DATA only — the verification
	// itself (switchop.VerifyPostUpdateConvergence) runs in phaseSwitch, where the
	// BEFORE reading of the effective generation exists; re-probing here could only see
	// the AFTER value and would prove nothing.
	//
	// "" means NOT EVALUATED — a pre-v1.230.0 install_state, or a caller that does not
	// evaluate this dimension. ⛔ Never read as VERIFIED, never converted into a FAIL.
	ConvergenceVerified string
	// 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

	// FailureTime is the unit's last-failure timestamp (most recent of
	// InactiveEnterTimestamp / ExecMainExitTimestamp from `systemctl show`).
	// v1.174 D-INSTALL-FAILED-UNITS-STALE-LATCHED-STATE: used to classify
	// a failed unit as pre-existing (failed before the install window) vs
	// in-window. Zero value when unknown.
	FailureTime time.Time

	// FailureTimeKnown is true only when FailureTime was parsed
	// successfully. When false, the unit is treated as a hard failure
	// (fail closed) — an unparseable timestamp can never be classified as
	// pre-existing/recovered.
	FailureTimeKnown bool

	// OwnedWindowReverifyDone / OwnedWindowReverifyClean carry the result of
	// the v1.201 host-side gated re-verify, performed ONLY for
	// isCadenceReverifiableOneshot units (watchdog/soak/maintenance). The
	// gatherer reset-failed the unit and issued a fresh `systemctl start`;
	// Clean is true ONLY when that re-run succeeded and the unit is no longer
	// failed. The classifier consults these to tolerate a transient
	// (re-verified-clean) cadence-oneshot latch as non-fatal; an absent or
	// false result keeps the unit FATAL (no-mask: a persistent failure that
	// re-fails its run is never cleared). Zero value = no re-verify attempted.
	OwnedWindowReverifyDone  bool
	OwnedWindowReverifyClean bool

	// Ownership records WHY this unit is in the findings list. v1.228.5:
	// enumeration previously filtered on IsNftbanUnit at gather time, so a SYSTEM
	// unit that NFTBan itself had broken was never collected. MEASURED 2026-08-06:
	// INSTALL_STATE reached COMMITTED while logrotate.service was failed on
	// NFTBan's own generated /etc/logrotate.d/nftban stanza.
	// NFTBAN_UNIT_PREFIX != NFTBAN_FAILURE_OWNERSHIP.
	//
	// Empty on findings produced before this field existed; treat "" as
	// OwnNftbanPackageUnit for backward compatibility (every pre-v1.228.5 finding
	// had already passed the IsNftbanUnit filter).
	Ownership FailureOwnership
	// OwnershipWhy explains a non-package attribution so an operator can act on a
	// blocked install that names a unit not called nftban-*.
	OwnershipWhy string
}

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

	// Classification is the v1.174 disposition of the failure, one of:
	// "" (default / in-window fatal), "IN_WINDOW", "PRE_EXISTING_FATAL"
	// (pre-existing but live health not clean/unknown), or
	// "WARN_PRE_EXISTING_RECOVERED" (pre-existing + live health clean —
	// non-fatal). Diagnostic only; the fatal/non-fatal decision is encoded
	// by which result bucket the entry lands in.
	Classification string
}

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

type FailureOwnership added in v1.228.5

type FailureOwnership string

FailureOwnership classifies a failed unit by CAUSALITY, not by name.

const (
	// OwnNftbanPackageUnit is an nftban-* unit shipped by the package.
	OwnNftbanPackageUnit FailureOwnership = "NFTBAN_PACKAGE_UNIT"
	// OwnNftbanAffectedSystem is a system unit failing while consuming an NFTBan-owned
	// artifact. Blocks COMMITTED exactly like a package unit failure.
	OwnNftbanAffectedSystem FailureOwnership = "NFTBAN_AFFECTED_SYSTEM_UNIT"
	// OwnPreexistingUnrelated failed before the transaction with no NFTBan artifact
	// involved. Allowed, but ONLY when explicitly recorded.
	OwnPreexistingUnrelated FailureOwnership = "PREEXISTING_UNRELATED_UNIT"
	// OwnNewUnclassified became failed during the transaction with no established cause.
	// Fails closed: an unexplained new failure during our transaction is ours until proven
	// otherwise.
	OwnNewUnclassified FailureOwnership = "NEW_UNCLASSIFIED_UNIT"
)

func ClassifyFailedUnit added in v1.228.5

func ClassifyFailedUnit(unit string, preFailed map[string]bool, fileExists func(string) bool) (FailureOwnership, string)

ClassifyFailedUnit determines who owns a failed unit.

unit        the failing unit name
preFailed   units already failed BEFORE the transaction (snapshot)
fileExists  presence probe for an absolute path; injected so this is testable and so
            the caller controls whether it inspects a live root or a staged one

Ordering matters: an nftban-* unit is ours by name; otherwise a known integration whose artifact is PRESENT is ours by causality; otherwise a unit already failing beforehand is pre-existing; anything else newly failed during our transaction is unclassified and fails closed.

func (FailureOwnership) Blocks added in v1.228.5

func (o FailureOwnership) Blocks() bool

Blocks reports whether this ownership class must prevent INSTALL_STATE=COMMITTED.

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 SystemIntegration added in v1.228.5

type SystemIntegration struct {
	// Unit is the system unit name as systemd reports it.
	Unit string
	// Artifacts are absolute paths NFTBan installs or generates which this unit reads.
	// Presence of ANY of them means NFTBan participates in the integration. Absence means
	// NFTBan does not, and a failure of Unit is NOT attributable to NFTBan.
	Artifacts []string
	// Why documents the causal link, so an operator reading a blocked install understands
	// why a non-nftban unit is being attributed to NFTBan.
	Why string
}

SystemIntegration pairs a SYSTEM unit (one whose name does not begin with nftban-) with the NFTBan-owned artifacts it consumes. If any artifact is present, NFTBan is participating in that integration and owns failures arising from it.

func NftbanSystemIntegrations added in v1.228.5

func NftbanSystemIntegrations() []SystemIntegration

NftbanSystemIntegrations returns every system unit NFTBan can break through an artifact it owns. Covers both package families and both MAC implementations.

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

	// InstallWindowStart is the wall-clock instant the current installer
	// run began (set once at the very start of the run via
	// SetInstallWindowStart). A failed unit whose FailureTime is strictly
	// before this instant is "pre-existing"; one at/after is "in-window".
	// v1.174 D-INSTALL-FAILED-UNITS-STALE-LATCHED-STATE.
	InstallWindowStart time.Time

	// InstallWindowStartKnown is true only when InstallWindowStart was
	// set. When false, no failed unit can be classified as pre-existing —
	// all non-aux failures stay fatal (fail closed).
	InstallWindowStartKnown bool

	// LiveHealthClean reports whether a bounded live `nftban health` probe
	// returned a clean overall status (Overall OK/IDLE, Findings: none, no
	// CRITICAL). Only consulted to downgrade a PRE_EXISTING latched failure.
	LiveHealthClean bool

	// LiveHealthKnown is true only when the live-health probe produced a
	// confident clean/not-clean verdict. When false (probe timeout, missing
	// binary, unparseable output, non-zero exit), no downgrade is permitted
	// — pre-existing failures stay fatal (fail closed).
	LiveHealthKnown 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

	// FailedUnitsPreExistingRecovered holds protection-critical nftban units
	// that were ALREADY in a failed state before this install window started
	// AND whose live `nftban health` probe is clean. v1.174 D-INSTALL-FAILED-
	// UNITS-STALE-LATCHED-STATE: these are a stale latch, not a fresh break,
	// so they are surfaced as WARN_PRE_EXISTING_RECOVERED and do NOT fail
	// FAILED-UNIT-POSTINSTALL-001 (install COMMITTED-with-warning). A newly-
	// failed unit, or a pre-existing one with live health not clean/unknown,
	// stays in FailedUnits (fatal) — fail safe toward DEGRADED.
	FailedUnitsPreExistingRecovered []FailedUnitPostInstall

	// FailedUnitsTransientRecovered holds cadence-oneshot nftban units
	// (isCadenceReverifiableOneshot: watchdog/soak/maintenance) whose failed
	// latch was recovered by the v1.201 host-side gated re-verify — the unit
	// was reset-failed and a fresh `systemctl start` ran CLEAN, proving the
	// latch was transient (the v1.198.3 watchdog/update-swap EXEC-203 class).
	// Surfaced as WARN_TRANSIENT_RECOVERED and does NOT fail
	// FAILED-UNIT-POSTINSTALL-001. A cadence-oneshot whose re-run still fails
	// (or was never re-verified) stays in FailedUnits (fatal) — no-mask.
	FailedUnitsTransientRecovered []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