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 ¶
- Variables
- func AllPassed(results []AssertionResult) bool
- func FailedNames(results []AssertionResult) []string
- func IsAuxiliaryUnit(basename string) bool
- func IsNftbanUnit(basename string) bool
- func RunPermissionsEnforce(exec executor.Executor, log *logging.Logger)
- func SetImmutableFlags(exec executor.Executor, log *logging.Logger)
- func WriteAuthorityFiles(exec executor.Executor, decision authority.Decision, log *logging.Logger)
- type AssertionOpts
- type AssertionResult
- type ExecDirective
- type FailedUnitFinding
- type FailedUnitPostInstall
- type MissingExecPath
- type MissingTimerTarget
- type ParsedUnit
- type PayloadInventory
- type RevalidateResult
- type SystemdPayloadInputs
- type SystemdPayloadValidationResult
- type TimerState
- type TimerValidationInputs
- type TimerValidationResult
- type UnknownPayloadRef
Constants ¶
This section is empty.
Variables ¶
var DefaultNftbanOwnedPrefixes = []string{
"/usr/lib/nftban/",
"/etc/nftban/",
}
DefaultNftbanOwnedPrefixes lists path prefixes considered nftban-owned for PAYLOAD-INVENTORY-001.
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).
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
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
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
RunPermissionsEnforce calls `nftban permissions enforce` for full FHS fix (G10 parity).
func SetImmutableFlags ¶ added in v1.76.0
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 ¶
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 ¶
AssertionResult holds the outcome of a single assertion.
func RunAssertions ¶
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
FailedUnitPostInstall records one FAILED-UNIT-POSTINSTALL-001 hit.
type MissingExecPath ¶ added in v1.100.4
MissingExecPath records one SYSTEMD-EXECSTART-001 hit.
type MissingTimerTarget ¶ added in v1.100.4
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
RunWithBoundedFix implements the VALIDATE_1 → FIX → VALIDATE_2 state machine.
Flow:
- Run assertions (VALIDATE_1)
- If all pass → return success immediately
- If some fail → run bounded safe fix (permissions enforce only, INV-I-011)
- Re-run assertions (VALIDATE_2, INV-I-013)
- 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
UnknownPayloadRef records one PAYLOAD-INVENTORY-001 hit: a unit references an nftban-owned path that is not known to the staged payload inventory.