Documentation
¶
Overview ¶
============================================================================= NFTBan v1.78 - Validator CLI Helpers ============================================================================= SPDX-License-Identifier: MPL-2.0 SPDX-FileCopyrightText: Copyright (c) 2024-2026 Antonios Voulvoulis <contact@nftban.com> meta:name="validator-cli" meta:type="lib" meta:owner="Antonios Voulvoulis <contact@nftban.com>" meta:created_date="2026-04-05" meta:description="CLI output helpers for the validator" meta:inventory.files="internal/validator/cli.go" meta:inventory.binaries="" meta:inventory.env_vars="" meta:inventory.config_files="" meta:inventory.systemd_units="" meta:inventory.network="" meta:inventory.privileges="none" =============================================================================
============================================================================= NFTBan v1.82 - Consistency Axis Evaluator ============================================================================= SPDX-License-Identifier: MPL-2.0 SPDX-FileCopyrightText: Copyright (c) 2024-2026 Antonios Voulvoulis <contact@nftban.com> meta:name="consistency" meta:type="lib" meta:version="1.82.0" meta:owner="Antonios Voulvoulis <contact@nftban.com>" meta:description="Cross-source consistency verification per M81-4 health derivation" meta:inventory.files="internal/validator/consistency.go" meta:inventory.binaries="" meta:inventory.env_vars="" meta:inventory.config_files="" meta:inventory.systemd_units="" meta:inventory.network="" meta:inventory.privileges="none"
The consistency axis detects disagreements between truth sources:
config vs kernel (enabled but missing = DEGRADED) kernel vs validator (structural truth agreement)
Per vocabulary Rule 8: DISABLED + PRESENT = valid residual, NOT a mismatch. Per vocabulary Rule 1: zero counters/sets = NEUTRAL, NOT evidence of failure. =============================================================================
============================================================================= NFTBan — CSF-CLOSE-5: PROTECTED must not be reported while CSF can act ============================================================================= SPDX-License-Identifier: MPL-2.0 SPDX-FileCopyrightText: Copyright (c) 2024-2026 Antonios Voulvoulis <contact@nftban.com> meta:name="validator-csf-conflict" meta:type="lib" meta:owner="Antonios Voulvoulis <contact@nftban.com>" meta:created_date="2026-08-11" meta:description="Read-only, conservative CSF conflict truth for the validator. Emits a finding when CSF is effective or re-enterable, so the status authority cannot report PROTECTED on a contested host. No mutation, no vendor binary execution." meta:inventory.files="internal/validator/csf_conflict.go" meta:inventory.binaries="" meta:inventory.privileges="none" =============================================================================
WHY THIS EXISTS — two proven runtime failures (el9-clean, 2026-08-11):
ARM 3 /etc/csf removed, csf.service failed BUT STILL ENABLED, binary
present, 129 CSF rules live. Takeover neutralized nothing.
Restoring the config and starting the still-enabled unit brought
CSF back to 129 rules — and `nftban status` said PROTECTED
throughout.
ARM 4 CSF detected and listed in CONFLICTS, yet the disarm path was
skipped. lfd left enabled, binary left executable, PROTECTED.
In both cases NFTBan's own authority was genuinely healthy. That is exactly the trap:
NFTBAN HEALTHY != SOLE EFFECTIVE AUTHORITY SERVICE INACTIVE != AUTHORITY ABSENT
SCOPE (owner-constrained, deliberately narrow): this is NOT the future universal external-firewall observer. It answers one question about one product, conservatively. UFW / firewalld / native-nftables generalization belongs to a later lane and must not be built here.
PURITY: this file performs NO mutation and NEVER executes a vendor firewall binary. `csf -s` is CSF's START command — using it as a probe re-armed a competing firewall from a read-only report (fixed in v1.229.0 R0). Evidence here is systemd state + filesystem presence only.
============================================================================= NFTBan v1.81 - Health Output Mapper ============================================================================= SPDX-License-Identifier: MPL-2.0 SPDX-FileCopyrightText: Copyright (c) 2024-2026 Antonios Voulvoulis <contact@nftban.com> meta:name="health_mapper" meta:type="lib" meta:version="1.81.0" meta:owner="Antonios Voulvoulis <contact@nftban.com>" meta:description="Maps internal ValidationResult to frozen HealthOutput JSON schema" meta:inventory.files="internal/validator/health_mapper.go" meta:inventory.binaries="" meta:inventory.env_vars="" meta:inventory.config_files="" meta:inventory.systemd_units="" meta:inventory.network="" meta:inventory.privileges="none"
This mapper is the ONLY path from internal state to JSON output. It enforces: - vocabulary-approved values only - omission rules (disabled modules, kernel-only modules) - no nulls - schema contract compliance
It MUST NOT contain derivation logic. It projects. =============================================================================
============================================================================= NFTBan v1.81 - Health JSON Output (M81-6 Schema Contract) ============================================================================= SPDX-License-Identifier: MPL-2.0 SPDX-FileCopyrightText: Copyright (c) 2024-2026 Antonios Voulvoulis <contact@nftban.com> meta:name="health_output" meta:type="lib" meta:version="1.81.0" meta:owner="Antonios Voulvoulis <contact@nftban.com>" meta:description="JSON output schema types and mapper per JSON_SCHEMA_SPEC_v1.81.md" meta:inventory.files="internal/validator/health_output.go" meta:inventory.binaries="" meta:inventory.env_vars="" meta:inventory.config_files="" meta:inventory.systemd_units="" meta:inventory.network="" meta:inventory.privileges="none"
This file defines the JSON OUTPUT contract. It is a PROJECTION layer only. It MUST NOT contain derivation logic — that belongs in module_health.go. It MUST NOT contain evaluation logic — that belongs in validator.go.
All field values are vocabulary-approved terms from NFTBAN_VOCABULARY_REFERENCE_v1.81.md. Schema frozen per JSON_SCHEMA_SPEC_v1.81.md. =============================================================================
============================================================================= NFTBan v1.84 - Bounded Journal Evidence Reader ============================================================================= SPDX-License-Identifier: MPL-2.0 SPDX-FileCopyrightText: Copyright (c) 2024-2026 Antonios Voulvoulis <contact@nftban.com> meta:name="journal-evidence" meta:type="lib" meta:owner="Antonios Voulvoulis <contact@nftban.com>" meta:created_date="2026-04-15" meta:description="Bounded journal query for daemon runtime evidence" meta:inventory.files="internal/validator/journal.go" meta:inventory.binaries="journalctl" meta:inventory.env_vars="" meta:inventory.config_files="" meta:inventory.systemd_units="" meta:inventory.network="" meta:inventory.privileges="root"
A1-1: Bounded journal evidence reader, consumed by BotGuard (A1-2) and LoginMon (A1-3) module evaluators for runtime evidence beyond systemctl is-active.
Design contract: - Bounded: time window (-15m) + line count (-n 200), no full scan - Deterministic: same input → same output, newest-first (-r) - Fail-safe: command failure returns structured error, never panics - Silent: no impact on validator status, findings, or exit code
Command shape:
journalctl -u nftband --since "-15m" --no-pager -o cat -r [-g REGEX] -n 200
v1.216.3: pattern queries are scoped SERVER-SIDE with `-g <regex>` (a regexp.QuoteMeta-escaped OR of the literal patterns) so the `-n` line cap bounds MATCHING lines, not all unit lines. Without this, a high-volume journal (e.g. a host under auth attack emitting hundreds of [EVENT]/[BAN] lines per minute) evicts low-rate evidence — the LoginMon 5-minute heartbeat — from the returned window, reintroducing a false VAL-LOGINMON-001. The Go-side substring scan is retained as first-match confirmation. =============================================================================
============================================================================= NFTBan v1.81 - Module Health Evaluator ============================================================================= SPDX-License-Identifier: MPL-2.0 SPDX-FileCopyrightText: Copyright (c) 2024-2026 Antonios Voulvoulis <contact@nftban.com> meta:name="module_health" meta:type="lib" meta:version="1.81.0" meta:owner="Antonios Voulvoulis <contact@nftban.com>" meta:description="Per-module 4-axis health evaluation per M81-4 HEALTH_METRIC_DERIVATION_v1.81.md" meta:inventory.files="internal/validator/module_health.go" meta:inventory.binaries="" meta:inventory.env_vars="" meta:inventory.config_files="conf.d/botguard/main.conf,conf.d/ddos/main.conf,conf.d/portscan/main.conf,conf.d/login_alert.conf" meta:inventory.systemd_units="" meta:inventory.network="" meta:inventory.privileges="root"
This evaluator implements the truth tables from HEALTH_METRIC_DERIVATION_v1.81.md. It MUST NOT invent derivation logic outside that specification. All state values are vocabulary-approved terms from NFTBAN_VOCABULARY_REFERENCE_v1.81.md. =============================================================================
============================================================================= NFTBan v1.78 - nft JSON Output Parsing ============================================================================= SPDX-License-Identifier: MPL-2.0 SPDX-FileCopyrightText: Copyright (c) 2024-2026 Antonios Voulvoulis <contact@nftban.com> meta:name="validator-nftjson" meta:type="lib" meta:owner="Antonios Voulvoulis <contact@nftban.com>" meta:created_date="2026-04-05" meta:description="Parse nft -j list ruleset JSON output" meta:inventory.files="internal/validator/nftjson.go" meta:inventory.binaries="nft" meta:inventory.env_vars="" meta:inventory.config_files="" meta:inventory.systemd_units="" meta:inventory.network="" meta:inventory.privileges="root" =============================================================================
============================================================================= NFTBan v1.78 - Kernel Truth Validator Types ============================================================================= SPDX-License-Identifier: MPL-2.0 SPDX-FileCopyrightText: Copyright (c) 2024-2026 Antonios Voulvoulis <contact@nftban.com> meta:name="validator-types" meta:type="lib" meta:owner="Antonios Voulvoulis <contact@nftban.com>" meta:created_date="2026-04-05" meta:description="Type definitions for kernel state validation" meta:inventory.files="internal/validator/types.go" meta:inventory.binaries="" meta:inventory.env_vars="" meta:inventory.config_files="" meta:inventory.systemd_units="" meta:inventory.network="" meta:inventory.privileges="none" =============================================================================
============================================================================= NFTBan v1.78 - Kernel Truth Validator ============================================================================= SPDX-License-Identifier: MPL-2.0 SPDX-FileCopyrightText: Copyright (c) 2024-2026 Antonios Voulvoulis <contact@nftban.com> meta:name="validator" meta:type="lib" meta:owner="Antonios Voulvoulis <contact@nftban.com>" meta:created_date="2026-04-05" meta:description="Validates live nftables kernel state against NFTBan requirements" meta:inventory.files="internal/validator/validator.go" meta:inventory.binaries="nft" meta:inventory.env_vars="" meta:inventory.config_files="" meta:inventory.systemd_units="" meta:inventory.network="" meta:inventory.privileges="root"
CRITICAL: This validator is ZERO-SIDE-EFFECT. It MUST NEVER write files, modify nft, or call mutating shell scripts. =============================================================================
Index ¶
- Constants
- Variables
- func CompareChainCounts(pre, post ChainCounts, tolerance int) (bool, string)
- func NormalizeNFTFamily(nftFamily string) string
- func SetJournalReader(r JournalReader)
- func SetServiceChecker(sc ServiceChecker)
- func SetTimerChecker(tc TimerChecker)
- type AnchorCheck
- type BlacklistHealth
- type BlacklistJSON
- type BlacklistSubHealth
- type BlacklistSubJSON
- type BotGuardCountersJSON
- type BotScanCountersJSON
- type CSFConflictResult
- type CSFConflictState
- type ChainCheck
- type ChainCounts
- type ConfigState
- type ConsistencyCheck
- type ConsistencyJSON
- type ConsistencyResult
- type CountersJSON
- type EffectiveState
- type ErrKind
- type FamilyCounts
- type FamilyResult
- type Finding
- type FindingJSON
- type HealthOutput
- type InputState
- type JournalEvidence
- type JournalQuery
- type JournalReader
- type ModuleClass
- type ModuleHealth
- type ModuleHealthMap
- type ModuleJSON
- type ModulesJSON
- type NFTAnchorJSON
- type NFTCountersJSON
- type NftChain
- type NftCounter
- type NftMetainfo
- type NftObject
- type NftRule
- type NftRuleset
- type NftSet
- type NftSetType
- type NftTable
- type RulesetDocument
- func (d *RulesetDocument) ChainExists(family, table, chain string) bool
- func (d *RulesetDocument) CountChains(family string) int
- func (d *RulesetDocument) CountRulesInChain(family, table, chain string) int
- func (d *RulesetDocument) CountSets(family string) int
- func (d *RulesetDocument) ExtractAnchorsFromChain(family, table, chain string) []string
- func (d *RulesetDocument) GetAllCounters(family, table string) map[string]*NftCounter
- func (d *RulesetDocument) GetChains(family, table string) []string
- func (d *RulesetDocument) GetCounter(family, table, name string) int64
- func (d *RulesetDocument) GetCounterFull(family, table, name string) *NftCounter
- func (d *RulesetDocument) GetCounterOK(family, table, name string) (int64, bool)
- func (d *RulesetDocument) GetRules(family, table, chain string) []*NftRule
- func (d *RulesetDocument) GetSets(family, table string) []string
- func (d *RulesetDocument) SetExists(family, table, set string) bool
- func (d *RulesetDocument) TableExists(family, name string) bool
- type RuntimeState
- type ServiceChecker
- type ServiceState
- type ServiceStateJSON
- type SetCheck
- type Severity
- type Status
- type StructuralReason
- type StructuralState
- type SummaryCounts
- type SystemdChecker
- type SystemdJournalReader
- type SystemdTimerChecker
- type TimerChecker
- type ValidationResult
- type WhitelistCountersJSON
Constants ¶
const ( FamilyIPv4 = "ipv4" // JSON; bridges Prometheus family="ip" FamilyIPv6 = "ipv6" // JSON; bridges Prometheus family="ip6" FamilyInet = "inet" // table-level only (genuinely-inet rules); never fake-split FamilyUnknown = "unknown" )
NFT family-vocabulary constants — the SINGLE JSON vocabulary (SOS-3). JSON output uses ONLY these; raw nft table families "ip"/"ip6" never appear in JSON.
const ( CountersPhaseContract = "contract" CountersPhasePopulated = "populated" )
CountersPhaseContract / CountersPhasePopulated are the counters_phase values. v1.190.0 emits "contract" (schema/fields defined, NOT populated → consumers must NOT read absent/zero as real). v1.191.0 will emit "populated" once live counters move.
const ( // Table findings CodeTableMissing = "VAL-TABLE-001" CodeTableBothMissing = "VAL-TABLE-002" // Chain findings CodeChainMissing = "VAL-CHAIN-001" CodeHelperChainMissing = "VAL-CHAIN-002" CodeChainCountDrop = "VAL-CHAIN-003" CodeChainEmpty = "VAL-CHAIN-004" // B80-3: chain exists but has no rules // Anchor findings CodeAnchorMissing = "VAL-ANCHOR-001" CodeAnchorOrder = "VAL-ANCHOR-002" CodeAnchorFinal = "VAL-ANCHOR-003" // FINAL GUARD CodeAnchorTruncate = "VAL-ANCHOR-004" // Set findings CodeSetMissing = "VAL-SET-001" // Module findings CodeModuleDegraded = "VAL-MODULE-001" // Service findings (B80-4) CodeServiceDown = "VAL-SERVICE-001" // required service not active CodeTimerNone = "VAL-TIMER-001" // v1.83: no nftban timers active CodeTimerError = "VAL-TIMER-002" // v1.83: timer query failed // Module-specific findings (M81-4) CodeGeobanDBMissing = "VAL-GEOBAN-001" // geoip database missing/empty CodeBotGuardNoEvidence = "VAL-BOTGUARD-001" // v1.84: no recent BotGuard runtime evidence CodeLoginMonNoEvidence = "VAL-LOGINMON-001" // v1.84: no recent LoginMon runtime evidence CodeLoginMonNoInput = "VAL-LOGINMON-002" // v1.183: enabled but a detection source reports no input // Consistency findings (v1.82) CodeConsistencyMismatch = "VAL-CONS-001" // config/kernel disagreement // v1.229.7 PR-3A: the expectation could not be established at all (MODE=auto // with no valid resolved plan). This is NOT a mismatch -- asserting one would // claim a kernel/config disagreement that was never observed. It is a // contract failure, and it degrades status rather than collapsing to "ok". CodeConsistencyUnknown = "VAL-CONS-002" // EXPECTED unknown: no authoritative plan (provenance side failed) // v1.229.7 PR-3A: converges to the same non-PASS status as VAL-CONS-002 but // keeps the cause distinct -- here the plan was fine and the RUNTIME // OBSERVATION could not be completed. CodeConsistencyObserveUnknown = "VAL-CONS-003" // OBSERVED unknown: observation incomplete // Not a state of the firewall -- a state of THIS PROGRAM. Reached only if a // StructuralState value exists that this consumer was never taught. CodeConsistencyInvalidState = "VAL-CONS-004" // internal contract error: unrecognised StructuralState // System findings CodeNftFailed = "VAL-SYSTEM-001" CodeNftNoOutput = "VAL-SYSTEM-002" CodeParseError = "VAL-SYSTEM-003" )
Finding codes (stable for automation).
const CodeCSFConflict = "VAL-CSF-001"
CodeCSFConflict is the stable finding code for automation.
const SchemaVersionCurrent = "1.84.0"
SchemaVersionCurrent is the output/IPC schema version per M81-6. v1.83: bumped for timer_count addition to service_state. v1.190.0 SCHEMA-UNFREEZE: deliberate bump 1.83.0 → 1.84.0 — adds the counters CONTRACT (additive, omitempty/absent until populated in v1.191.0) + the counters_phase marker. Coordinated with the freeze-guard tests in internal/{blacklist,whitelist,installer/render,validator}. Additive-only: existing fields/types unchanged so old consumers keep parsing.
Variables ¶
var AllHelperChains = GeneratedAllHelperChains
AllHelperChains lists all known helper chains for module-truth derivation and informational reporting. Not used for base structural validation.
var ConfigDir = "/etc/nftban"
ConfigDir is the base config directory. Overridable for testing.
var DataDir = resolveDataDir()
DataDir is the base data directory (project NFTBAN_DATA_DIR). Resolved from the environment with the project-canonical default; overridable for testing. Mirrors the shell rule `${NFTBAN_DATA_DIR:-/var/lib/nftban}` used by cmd_geoip.sh / cmd_selftest.sh so the validator looks for the GeoIP DB in the same place the geoip sync writes it (instead of a hardcoded /var/lib/nftban).
var GeneratedAllHelperChains = []string{
"ddos_penalty",
"ddos_prefix",
"ddos_protection",
"ddos_sanity",
"http_bot_guard",
"portscan_detection",
}
GeneratedAllHelperChains — all known helper chains (shell-declared + DDoS fragment sub-chains). Module-scoped: only required when their module is enabled.
var GeneratedAllSetsIPv4 = []string{
"blacklist_ipv4",
"blacklist_manual_ipv4",
"http_bot_allow",
"http_bot_ban",
"http_bot_emergency",
"http_bot_grey",
"http_bot_pending",
"http_bot_suspect",
"port_allow_tcp_ipv4",
"port_allow_udp_ipv4",
"ssh_ports",
"tcp_ports_in",
"tcp_ports_out",
"udp_ports_in",
"udp_ports_out",
"whitelist_ipv4",
}
GeneratedAllSetsIPv4 — all known IPv4 set names from canonical schema (including optional module sets).
var GeneratedAllSetsIPv6 = []string{
"blacklist_ipv6",
"blacklist_manual_ipv6",
"http_bot_allow6",
"http_bot_ban6",
"http_bot_emergency6",
"http_bot_grey6",
"http_bot_pending6",
"http_bot_suspect6",
"port_allow_tcp_ipv6",
"port_allow_udp_ipv6",
"ssh_ports",
"tcp_ports_in",
"tcp_ports_out",
"udp_ports_in",
"udp_ports_out",
"whitelist_ipv6",
}
GeneratedAllSetsIPv6 — all known IPv6 set names from canonical schema (including optional module sets).
var GeneratedRequiredBaseChains = []string{
"forward",
"input",
"output",
}
GeneratedRequiredBaseChains from NFTBAN_IPV4_CHAINS keys.
var GeneratedRequiredHelperChains = []string{}
GeneratedRequiredHelperChains — helper chains universally required for base PROTECTED. Currently empty: all helper chains are module-scoped.
var GeneratedRequiredSetsIPv4 = []string{
"blacklist_ipv4",
"blacklist_manual_ipv4",
"ssh_ports",
"tcp_ports_in",
"udp_ports_in",
"whitelist_ipv4",
}
GeneratedRequiredSetsIPv4 — core sets required for base protection.
var GeneratedRequiredSetsIPv6 = []string{
"blacklist_ipv6",
"blacklist_manual_ipv6",
"ssh_ports",
"tcp_ports_in",
"udp_ports_in",
"whitelist_ipv6",
}
GeneratedRequiredSetsIPv6 — core sets required for base protection.
var ModuleClassification = map[string]ModuleClass{ "ddos": ModuleCoreModule, "botguard": ModuleCoreModule, "portscan": ModuleCoreModule, "loginmon": ModuleCoreModule, "geoban": ModuleCoreModule, "botscan": ModuleCoreInfra, "geoip": ModuleCoreInfra, "panels": ModuleCoreInfra, "tunnel": ModuleAdvisory, "rbl": ModuleMonitor, "suricata": ModuleExternal, }
ModuleClassification maps every config-present module to its class. This is the completeness contract: if a module has a config directory, it MUST appear here. CI gate G8-2 enforces this.
CORE_MODULE entries MUST have: evaluator + ModuleHealthMap field + JSON + tests. CORE_INFRA entries are served by a parent module's evaluator. ADVISORY/MONITOR/EXTERNAL entries MUST NOT appear in evaluator or JSON.
var RequiredAnchors = []string{
"ANCHOR_HYGIENE",
"ANCHOR_TRUSTED",
"ANCHOR_BAN",
"ANCHOR_ESTABLISHED",
"ANCHOR_DETECT",
"ANCHOR_SERVICE",
"ANCHOR_FINAL",
}
Required anchors in strict order. AUTHORITY NOTE (B80-5): Anchors are a validator-defined invariant, NOT derived from the shell schema. They represent the required rule-comment sequence in the input chain. The canonical authority for chains/sets is nft_schema.sh via schema_generated.go. The canonical authority for anchor ordering is this list, maintained manually in this file.
var RequiredBaseChains = GeneratedRequiredBaseChains
Required base chains per family. B80-5/6: sourced from schema_generated.go (canonical: cli/lib/nftban/lib/nft_schema.sh).
var RequiredHelperChains = GeneratedRequiredHelperChains
Required helper chains per family. Currently EMPTY — all helper chains are module-scoped (only exist when their module is enabled). Base PROTECTED does not require any helper chains. Module-level chain validation is handled by per-module evaluators in module_health.go.
var RequiredSetsIPv4 = GeneratedRequiredSetsIPv4
Required sets for IPv4. B80-5/6: sourced from schema_generated.go (core required sets only).
var RequiredSetsIPv6 = GeneratedRequiredSetsIPv6
Required sets for IPv6. B80-5/6: sourced from schema_generated.go (core required sets only).
var RunDir = "/run/nftban"
RunDir holds the transient runtime records. A package var, like ConfigDir, so the plan-record failure contract is testable without a real /run.
Functions ¶
func CompareChainCounts ¶
func CompareChainCounts(pre, post ChainCounts, tolerance int) (bool, string)
CompareChainCounts compares pre and post chain counts for rebuild safety. Returns (degraded bool, message string).
func NormalizeNFTFamily ¶ added in v1.190.0
NormalizeNFTFamily maps a raw nftables table family to the canonical JSON family vocabulary (SOS-3 OPTION_1). nft "ip"→"ipv4", "ip6"→"ipv6", "inet"→"inet"; anything else (incl. empty) → "unknown". This is the ONLY bridge between the Prometheus named-counter label space (ip/ip6, unchanged for compat) and the JSON 1.84.0 contract (ipv4/ipv6/inet/unknown). For per-IP/anchor counters a result of "unknown" signals a classification/collection defect (v1.191 tests assert absence).
func SetJournalReader ¶ added in v1.84.0
func SetJournalReader(r JournalReader)
SetJournalReader replaces the journal reader (for testing only).
func SetServiceChecker ¶ added in v1.80.0
func SetServiceChecker(sc ServiceChecker)
SetServiceChecker replaces the service checker (for testing only).
func SetTimerChecker ¶ added in v1.83.0
func SetTimerChecker(tc TimerChecker)
SetTimerChecker replaces the timer checker (for testing only).
Types ¶
type AnchorCheck ¶
type AnchorCheck struct {
RequiredCount int `json:"required_count"`
FoundCount int `json:"found_count"`
Ordered bool `json:"ordered"`
AnchorsFound []string `json:"anchors_found"`
Missing []string `json:"missing"`
OrderExpected []string `json:"order_expected"`
OrderActual []string `json:"order_actual"`
FinalPresent bool `json:"final_present"` // FINAL GUARD invariant
}
AnchorCheck holds anchor validation results.
type BlacklistHealth ¶ added in v1.81.0
type BlacklistHealth struct {
Manual BlacklistSubHealth `json:"manual"`
Feeds BlacklistSubHealth `json:"feeds"`
Geoban BlacklistSubHealth `json:"geoban"`
}
BlacklistHealth holds the split blacklist state per M81-3 contract.
type BlacklistJSON ¶ added in v1.81.0
type BlacklistJSON struct {
Manual BlacklistSubJSON `json:"manual"`
Feeds BlacklistSubJSON `json:"feeds"`
Geoban BlacklistSubJSON `json:"geoban"`
}
BlacklistJSON is the composite blacklist module output. Does NOT follow the 4-axis model — blacklist is source-split.
type BlacklistSubHealth ¶ added in v1.81.0
type BlacklistSubHealth struct {
State string `json:"state"` // enforcing|primed|idle|loaded|stale|disabled
Entries int `json:"entries,omitempty"` // element count (manual/feeds)
Drops int64 `json:"drops,omitempty"` // counter value (manual only — attributable)
}
BlacklistSubHealth represents a blacklist sub-source state.
type BlacklistSubJSON ¶ added in v1.81.0
type BlacklistSubJSON struct {
State string `json:"state"` // enforcing|primed|idle|loaded|stale|degraded|disabled
Entries int `json:"entries,omitempty"` // element count (omitted if 0 or not applicable)
Drops int64 `json:"drops,omitempty"` // counter value (omitted if 0 or not attributable)
}
BlacklistSubJSON represents one blacklist source.
type BotGuardCountersJSON ¶ added in v1.190.0
type BotGuardCountersJSON struct {
Decisions FamilyCounts `json:"decisions"` // IP-backed (also by decision in Prometheus)
EventbusDrops uint64 `json:"eventbus_drops"` // not IP-backed → no family
}
BotGuardCountersJSON — populated v1.191.0 (counters absent in BotGuard today).
type BotScanCountersJSON ¶ added in v1.190.0
type BotScanCountersJSON struct {
ScannedEntries FamilyCounts `json:"scanned_entries"`
Matched FamilyCounts `json:"matched"` // also by category in Prometheus
Signals FamilyCounts `json:"signals"` // also ban vs grey in Prometheus
Bans FamilyCounts `json:"bans"` // also by reason/category in Prometheus
CrawlerVerify FamilyCounts `json:"crawler_verify"` // also ok/bad/timeout/cachehit in Prometheus
FilesDeferred uint64 `json:"files_deferred"` // per-file rotation/budget — NOT IP-backed, no family
}
BotScanCountersJSON — populated v1.191.0 (BotScan processor state). IP-backed counters carry family; files_deferred is per-FILE (no IP → no family).
type CSFConflictResult ¶ added in v1.228.12
type CSFConflictResult struct {
State CSFConflictState
Evidence []string
}
CSFConflictResult carries the verdict plus the evidence that produced it, so an operator is told WHY the host is contested rather than just that it is.
func DetectCSFConflict ¶ added in v1.228.12
func DetectCSFConflict() CSFConflictResult
DetectCSFConflict reports whether CSF is effective or re-enterable.
CONSERVATIVE BY CONSTRUCTION. Any of the following counts as PRESENT:
- a CSF unit ACTIVE (effective now)
- a CSF unit ENABLED but failed/inactive (re-enters on boot or restart — this is the arm-3 shape that previously read as "absent")
- an executable CSF binary at its canonical path (invokable directly)
- CSF config or cron persistence present (re-arms on reinstall/schedule)
If systemd cannot be interrogated at all, the answer is UNKNOWN — never NOT_OBSERVED. Absence of evidence is not evidence of absence.
type CSFConflictState ¶ added in v1.228.12
type CSFConflictState string
CSFConflictState is the conservative tri-state answer.
const ( // CSFPresent — CSF is effective, or can re-enter without reinstallation. CSFPresent CSFConflictState = "PRESENT" // CSFNotObserved — no credible CSF evidence found. CSFNotObserved CSFConflictState = "NOT_OBSERVED" // CSFUnknown — the host could not be interrogated. Never treated as clean. CSFUnknown CSFConflictState = "UNKNOWN" )
type ChainCheck ¶
type ChainCheck struct {
Required []string `json:"required"`
Found []string `json:"found"`
Missing []string `json:"missing"`
AllFound bool `json:"all_found"`
}
ChainCheck holds chain validation results.
type ChainCounts ¶
type ChainCounts struct {
IPv4Total int `json:"ipv4_total"`
IPv4Base int `json:"ipv4_base"`
IPv4Helper int `json:"ipv4_helper"`
IPv6Total int `json:"ipv6_total"`
IPv6Base int `json:"ipv6_base"`
IPv6Helper int `json:"ipv6_helper"`
TotalChains int `json:"total_chains"`
}
ChainCounts for relative comparison (rebuild safety).
type ConfigState ¶ added in v1.81.0
type ConfigState string
ConfigState represents operator intent from config files.
const ( ConfigEnabled ConfigState = "enabled" ConfigDisabled ConfigState = "disabled" )
type ConsistencyCheck ¶ added in v1.82.0
type ConsistencyCheck struct {
Module string `json:"module"`
Check string `json:"check"` // what was compared
Result string `json:"result"` // "ok" | "mismatch"
Detail string `json:"detail,omitempty"`
}
ConsistencyCheck represents one cross-source comparison.
type ConsistencyJSON ¶ added in v1.81.0
type ConsistencyJSON struct {
KernelVsValidator string `json:"kernel_vs_validator"` // ok|mismatch|unknown (v1.229.7)
}
ConsistencyJSON holds cross-source agreement status. Stub for v1.81.0 — expanded in v1.82.
type ConsistencyResult ¶ added in v1.82.0
type ConsistencyResult struct {
Overall string `json:"overall"` // "ok" | "mismatch" | "unknown" (v1.229.7)
Checks []ConsistencyCheck `json:"checks"`
Findings []Finding `json:"-"` // collected, appended to main result
}
ConsistencyResult holds the outcome of cross-source verification.
type CountersJSON ¶ added in v1.190.0
type CountersJSON struct {
BotScan *BotScanCountersJSON `json:"botscan,omitempty"`
BotGuard *BotGuardCountersJSON `json:"botguard,omitempty"`
Whitelist *WhitelistCountersJSON `json:"whitelist,omitempty"`
}
CountersJSON is the v1.84 counters CONTRACT. In v1.190.0 the pointer is always nil → the "counters" object is ABSENT from JSON (omitempty). The contract exists in the type system / schema; population begins v1.191.0 (which also flips counters_phase → "populated"). Absent is deliberately NOT zero so consumers do not read "nothing happened" from contract-only output.
type EffectiveState ¶ added in v1.81.0
type EffectiveState string
EffectiveState represents the module's activity level based on evidence.
const ( EffectiveEnforcing EffectiveState = "enforcing" EffectiveObserving EffectiveState = "observing" EffectiveIdle EffectiveState = "idle" EffectivePrimed EffectiveState = "primed" )
type ErrKind ¶ added in v1.84.0
type ErrKind string
ErrKind classifies journal query failures.
const ( ErrNone ErrKind = "" // success (may still have Found=false) ErrTimeout ErrKind = "timeout" // context deadline exceeded ErrExec ErrKind = "exec" // binary not found / permission denied ErrNonZero ErrKind = "nonzero" // journalctl exited non-zero (not "no matches") ErrEmpty ErrKind = "empty" // command succeeded but zero output )
type FamilyCounts ¶ added in v1.190.0
type FamilyCounts struct {
IPv4 uint64 `json:"ipv4,omitempty"`
IPv6 uint64 `json:"ipv6,omitempty"`
Inet uint64 `json:"inet,omitempty"` // table-level only (e.g. inet-chain forward/output)
Unknown uint64 `json:"unknown,omitempty"` // last-resort; on per-IP counters indicates a classification gap
}
FamilyCounts is an IP-backed counter split by address family (v1.84 contract; DESIGN_AMENDMENT_V1_190_IP_FAMILY). In Prometheus this is the low-cardinality label family="ipv4|ipv6|inet|unknown"; in JSON it is these omitempty sub-fields. Semantics:
- PER-IP counters (bans/signals/crawler_verify/etc.): use ipv4 / ipv6 only. `unknown` here signals a classification BUG (a banned IP is always v4 or v6); `inet` is NOT valid for per-IP counters.
- TABLE-LEVEL counters (nft forward/output from an inet chain): use `inet`.
Never fake-split a genuinely-inet rule into ipv4/ipv6. omitempty → absent in the contract phase (no false-zero) and zero families omitted once populated.
type FamilyResult ¶
type FamilyResult struct {
Family string `json:"family"` // "ip" or "ip6"
Status Status `json:"status"`
TablePresent bool `json:"table_present"`
ChainCount int `json:"chain_count"`
SetCount int `json:"set_count"`
BaseChains ChainCheck `json:"base_chains"`
HelperChains ChainCheck `json:"helper_chains"`
Anchors AnchorCheck `json:"anchors"`
Sets SetCheck `json:"sets"`
}
FamilyResult holds validation results for a single address family (ip/ip6).
type Finding ¶
type Finding struct {
Code string `json:"code"`
Severity Severity `json:"severity"`
Component string `json:"component"`
Family string `json:"family,omitempty"`
Message string `json:"message"`
Remediation string `json:"remediation,omitempty"`
}
Finding represents a single validation finding.
type FindingJSON ¶ added in v1.81.0
type FindingJSON struct {
Code string `json:"code"`
Severity string `json:"severity"` // info|warn|error|critical
Component string `json:"component"`
Family string `json:"family,omitempty"`
Message string `json:"message"`
Remediation string `json:"remediation,omitempty"`
}
FindingJSON is a single validation finding in the output.
type HealthOutput ¶ added in v1.81.0
type HealthOutput struct {
SchemaVersion string `json:"schema_version"`
Status string `json:"status"`
Timestamp string `json:"timestamp"`
ServiceState ServiceStateJSON `json:"service_state"`
Modules ModulesJSON `json:"modules"`
Consistency ConsistencyJSON `json:"consistency"`
Findings []FindingJSON `json:"findings"`
ChainCounts ChainCounts `json:"chain_counts"`
Summary SummaryCounts `json:"summary"`
// v1.84 SCHEMA-UNFREEZE counters contract (additive).
CountersPhase string `json:"counters_phase"` // "contract" (v1.190.0) | "populated" (v1.191.0); the anti-false-zero gate
Counters *CountersJSON `json:"counters,omitempty"` // nil/ABSENT until v1.191.0 populates — absent ≠ zero (no false-zero dashboards)
// v1.84 NFT counting-model contract view (additive; nil/ABSENT until v1.191.0).
NFT *NFTCountersJSON `json:"nft,omitempty"` // interpreted JSON VIEW over existing nft named counters — NOT a new Prometheus family (SOS-2 Option A)
}
HealthOutput is the top-level JSON output structure for nftban-validate --json. This is the canonical M81-6 schema. Fields MUST NOT be added without a schema version bump. See JSON_SCHEMA_SPEC_v1.81.md Section 8.
func MapToHealthOutput ¶ added in v1.81.0
func MapToHealthOutput(r *ValidationResult) HealthOutput
MapToHealthOutput converts a ValidationResult to the frozen HealthOutput schema. This is the single mapping point — all JSON output goes through here.
type InputState ¶ added in v1.183.0
type InputState string
InputState represents detection-source input readability (v1.183 HEALTH-NO-INPUT-AXIS increment 2). It makes an enabled-but-starved module visible in `nftban health` instead of reading healthy. Derived from the daemon's "[LOGINMON] <src>: state=..." journal lines (the daemon is the authority on its own watchers). Omitted unless determinable.
const ( InputOK InputState = "ok" // a source is present + producing input InputWarnNoLogs InputState = "warn_no_logs" // stack present but no logs / no recent lines InputNoLogs InputState = "no_logs" // no source/daemon for an enabled module InputUnknown InputState = "unknown" // no input-state evidence available )
type JournalEvidence ¶ added in v1.84.0
type JournalEvidence struct {
Found bool // true if at least one pattern matched
MatchedLine string // first matching line (newest-first order)
MatchedIndex int // index of matched line in output (0-based)
LinesRead int // total lines read from journal
Truncated bool // true if journalctl hit -n limit
ErrKind ErrKind // failure classification
Err error // underlying error (nil on success)
}
JournalEvidence holds the result of a bounded journal query.
type JournalQuery ¶ added in v1.84.0
type JournalQuery struct {
Unit string // systemd unit to filter (e.g. "nftband")
Patterns []string // substring patterns to match (first match wins)
Since time.Duration // how far back to look (default 15m)
MaxLines int // max lines from journalctl -n (default 200)
Timeout time.Duration // exec timeout (default 2s)
}
JournalQuery defines a bounded journal search.
type JournalReader ¶ added in v1.84.0
type JournalReader interface {
Query(ctx context.Context, q JournalQuery) JournalEvidence
}
JournalReader abstracts journal queries for testability.
type ModuleClass ¶ added in v1.85.0
type ModuleClass string
ModuleClass defines a module's role in the system.
const ( ModuleCoreModule ModuleClass = "core_module" // Protection module: evaluator + JSON + tests required ModuleCoreInfra ModuleClass = "core_infra" // Internal support: served by parent evaluator ModuleAdvisory ModuleClass = "advisory" // Non-blocking, no kernel enforcement ModuleMonitor ModuleClass = "monitor" // Observes but does not enforce ModuleExternal ModuleClass = "external" // Third-party integration, deferred )
type ModuleHealth ¶ added in v1.81.0
type ModuleHealth struct {
Config ConfigState `json:"config"`
Structural StructuralState `json:"structural,omitempty"`
Runtime RuntimeState `json:"runtime,omitempty"`
Effective EffectiveState `json:"effective,omitempty"`
Input InputState `json:"-"` // internal (v1.183); surfaced via CodeLoginMonNoInput finding, not the frozen schema
// StructuralReason carries WHY the structural verdict is what it is.
// Internal (v1.229.7 PR-3A), same schema-safe pattern as Input above: it is
// surfaced through the consistency detail and the findings array, never as a
// new field in the frozen M81-6 ModuleJSON.
//
// It exists because the three-value axis cannot distinguish causes that are
// operationally opposite:
// MISSING — expected objects absent vs the OTHER mode's objects PRESENT
// UNKNOWN — the EXPECTATION failed vs the OBSERVATION failed
// Reporting "objects missing" for cross-mode drift is a false statement, and
// merging the two unknowns hides which side of the contract broke.
StructuralReason StructuralReason `json:"-"`
}
ModuleHealth holds the per-module health evaluation. v1.183 adds the Input axis (input-readability) as an INTERNAL field (json:"-") — it drives a findings-array signal (CodeLoginMonNoInput) which is the schema-safe way to surface enabled-but-starved in `nftban health`. The frozen M81-6 health-output schema (ModuleJSON) is NOT extended here; promoting Input to a first-class JSON axis is deferred to the SCHEMA-UNFREEZE major.
type ModuleHealthMap ¶ added in v1.81.0
type ModuleHealthMap struct {
BotGuard *ModuleHealth `json:"botguard,omitempty"`
DDoS *ModuleHealth `json:"ddos,omitempty"`
Portscan *ModuleHealth `json:"portscan,omitempty"`
LoginMon *ModuleHealth `json:"loginmon,omitempty"`
Blacklist *BlacklistHealth `json:"blacklist,omitempty"`
}
ModuleHealthMap holds all per-module health evaluations.
type ModuleJSON ¶ added in v1.81.0
type ModuleJSON struct {
Config string `json:"config"` // enabled|disabled (always present)
Structural string `json:"structural,omitempty"` // present|missing|unknown (omitted if disabled); unknown = expectation unestablished (v1.229.7)
Runtime string `json:"runtime,omitempty"` // running|stopped (omitted if not daemon-dependent)
Effective string `json:"effective,omitempty"` // enforcing|observing|idle (omitted if disabled)
}
ModuleJSON is the standard 4-axis module output. Fields are omitted when not applicable (e.g. runtime for kernel-only modules).
type ModulesJSON ¶ added in v1.81.0
type ModulesJSON struct {
BotGuard *ModuleJSON `json:"botguard,omitempty"`
DDoS *ModuleJSON `json:"ddos,omitempty"`
Portscan *ModuleJSON `json:"portscan,omitempty"`
LoginMon *ModuleJSON `json:"loginmon,omitempty"`
Blacklist *BlacklistJSON `json:"blacklist,omitempty"`
}
ModulesJSON holds all per-module health in JSON output form. Disabled modules emit config only (no other fields). No nulls. Omitted fields = not applicable.
type NFTAnchorJSON ¶ added in v1.190.0
type NFTAnchorJSON struct {
Family string `json:"family"` // ipv4|ipv6 (normalized from nft ip/ip6); anchors never inet/unknown — unknown ⇒ collection defect
Anchor string `json:"anchor"` // anchor_hygiene|anchor_trusted|anchor_ban|anchor_established|anchor_detect|anchor_service|anchor_final
Packets uint64 `json:"packets"` // from the named counter (real value once populated v1.191.0)
Bytes uint64 `json:"bytes"` // from the named counter
}
NFTAnchorJSON is one anchor phase-boundary counter (v1.84 contract). Anchors (anchor_hygiene→…→anchor_final, both ip + ip6 tables, comment NFTBAN_ANCHOR:*) are PHASE-BOUNDARY / pipeline-continuity counters — they answer "did traffic reach this firewall phase?" They are NOT accept/drop verdict counters and MUST NOT be summed into total_input_accept / total_input_drop. They do not change health status in v1.190.0.
Family is NORMALIZED per SOS-3 (OPTION_1_NORMALIZE_JSON_KEEP_PROMETHEUS_COMPAT): the kernel/Prometheus family "ip"/"ip6" is mapped to JSON "ipv4"/"ipv6" so the entire 1.84.0 JSON contract speaks ONE vocabulary (ipv4|ipv6|inet|unknown). The Prometheus label nft_named_counter_*{family="ip"|"ip6"} is left UNCHANGED for compatibility; the bridge is documented: Prometheus ip == JSON ipv4, ip6 == ipv6.
type NFTCountersJSON ¶ added in v1.190.0
type NFTCountersJSON struct {
Anchors []NFTAnchorJSON `json:"anchors,omitempty"` // phase-boundary view; see NFTAnchorJSON
}
NFTCountersJSON is the v1.84 NFT counting-model JSON view. It is an INTERPRETED projection over the EXISTING nft named counters (Prometheus nftban_nft_named_counter_packets_total{family,counter} / _bytes_total) — it does NOT introduce a parallel Prometheus metric (SOS-2 Option A: no double-count). In v1.190.0 the pointer is always nil → the "nft" object is ABSENT (omitempty); population begins v1.191.0 from the same named-counter / nft-JSON source.
type NftChain ¶
type NftChain struct {
Family string `json:"family"`
Table string `json:"table"`
Name string `json:"name"`
Type string `json:"type,omitempty"`
Hook string `json:"hook,omitempty"`
Priority int `json:"prio,omitempty"`
Policy string `json:"policy,omitempty"`
Handle int `json:"handle,omitempty"`
}
NftChain represents an nftables chain.
type NftCounter ¶
type NftCounter struct {
Family string `json:"family"`
Table string `json:"table"`
Name string `json:"name"`
Handle int `json:"handle,omitempty"`
Packets int64 `json:"packets,omitempty"`
Bytes int64 `json:"bytes,omitempty"`
}
NftCounter represents a named counter.
type NftMetainfo ¶
type NftMetainfo struct {
Version string `json:"version"`
ReleaseName string `json:"release_name"`
JsonSchemaVersion int `json:"json_schema_version"`
}
NftMetainfo holds nft version info.
type NftObject ¶
type NftObject struct {
Metainfo *NftMetainfo `json:"metainfo,omitempty"`
Table *NftTable `json:"table,omitempty"`
Chain *NftChain `json:"chain,omitempty"`
Set *NftSet `json:"set,omitempty"`
Rule *NftRule `json:"rule,omitempty"`
Counter *NftCounter `json:"counter,omitempty"`
}
NftObject is a wrapper for any nft object (table, chain, set, rule, etc.). Only one field will be populated per object.
type NftRule ¶
type NftRule struct {
Family string `json:"family"`
Table string `json:"table"`
Chain string `json:"chain"`
Handle int `json:"handle,omitempty"`
Comment string `json:"comment,omitempty"`
Expr []interface{} `json:"expr,omitempty"`
}
NftRule represents an nftables rule.
type NftRuleset ¶
type NftRuleset struct {
Nftables []NftObject `json:"nftables"`
}
NftRuleset represents the top-level nft -j list ruleset output. The JSON structure is: { "nftables": [ {...}, {...}, ... ] }
func LoadRulesetJSON ¶
func LoadRulesetJSON(ctx context.Context) (*NftRuleset, error)
LoadRulesetJSON executes nft -j list ruleset and parses the output. This is a PURE function - it only reads kernel state, never modifies.
type NftSet ¶
type NftSet struct {
Family string `json:"family"`
Table string `json:"table"`
Name string `json:"name"`
Type NftSetType `json:"type,omitempty"`
Flags []string `json:"flags,omitempty"`
Timeout int `json:"timeout,omitempty"`
Handle int `json:"handle,omitempty"`
}
NftSet represents an nftables set.
type NftSetType ¶
type NftSetType struct {
Types []string
}
NftSetType handles nft set type which can be string or []string (for concatenations).
func (*NftSetType) UnmarshalJSON ¶
func (t *NftSetType) UnmarshalJSON(data []byte) error
UnmarshalJSON handles both string and []string for nft set type field.
type NftTable ¶
type NftTable struct {
Family string `json:"family"`
Name string `json:"name"`
Handle int `json:"handle,omitempty"`
}
NftTable represents an nftables table.
type RulesetDocument ¶
type RulesetDocument struct {
// contains filtered or unexported fields
}
RulesetDocument provides structured access to the parsed ruleset.
func ParseRuleset ¶
func ParseRuleset(raw *NftRuleset) *RulesetDocument
ParseRuleset converts raw NftRuleset into a structured document.
func (*RulesetDocument) ChainExists ¶
func (d *RulesetDocument) ChainExists(family, table, chain string) bool
ChainExists checks if a chain exists.
func (*RulesetDocument) CountChains ¶
func (d *RulesetDocument) CountChains(family string) int
CountChains returns total chain count for a family's nftban table.
func (*RulesetDocument) CountRulesInChain ¶ added in v1.80.0
func (d *RulesetDocument) CountRulesInChain(family, table, chain string) int
CountRulesInChain returns the number of rules in a specific chain. Returns 0 if the chain has no rules or does not exist.
func (*RulesetDocument) CountSets ¶
func (d *RulesetDocument) CountSets(family string) int
CountSets returns total set count for a family's nftban table.
func (*RulesetDocument) ExtractAnchorsFromChain ¶
func (d *RulesetDocument) ExtractAnchorsFromChain(family, table, chain string) []string
ExtractAnchorsFromChain extracts anchor names from rule comments in a chain. Anchors are identified by comments containing "NFTBAN_ANCHOR:ANCHOR_*".
func (*RulesetDocument) GetAllCounters ¶ added in v1.89.0
func (d *RulesetDocument) GetAllCounters(family, table string) map[string]*NftCounter
GetAllCounters returns all named counters for a family and table. Keys are counter names (without family/table prefix). v1.89: Added for evidence layer extraction (INV-M-002).
func (*RulesetDocument) GetChains ¶
func (d *RulesetDocument) GetChains(family, table string) []string
GetChains returns all chain names for a table.
func (*RulesetDocument) GetCounter ¶ added in v1.81.0
func (d *RulesetDocument) GetCounter(family, table, name string) int64
GetCounter returns a named counter's packet count, or 0 if not found.
The zero it returns for a MISSING counter is indistinguishable from the zero of a counter that exists and has not fired. That ambiguity is fine for display and totals, and wrong for verdicts: a counter absent because the ruleset was rendered by a different nft version, or because the counter was renamed, would read as "present and never triggered" — which is how an enforcing module came to be reported idle. Callers deriving a VERDICT must use GetCounterOK and treat absence as unestablished, not as zero.
func (*RulesetDocument) GetCounterFull ¶ added in v1.89.0
func (d *RulesetDocument) GetCounterFull(family, table, name string) *NftCounter
GetCounterFull returns a named counter with both packets and bytes. Returns nil if the counter does not exist. v1.89: Added for evidence layer extraction (INV-M-002).
func (*RulesetDocument) GetCounterOK ¶ added in v1.228.8
func (d *RulesetDocument) GetCounterOK(family, table, name string) (int64, bool)
GetCounterOK returns a named counter's packet count and whether the counter was actually present in the parsed ruleset. Absence is a distinct fact from a zero count and the two must not be collapsed by a verdict-bearing caller.
func (*RulesetDocument) GetRules ¶
func (d *RulesetDocument) GetRules(family, table, chain string) []*NftRule
GetRules returns all rules for a chain.
func (*RulesetDocument) GetSets ¶
func (d *RulesetDocument) GetSets(family, table string) []string
GetSets returns all set names for a table.
func (*RulesetDocument) SetExists ¶
func (d *RulesetDocument) SetExists(family, table, set string) bool
SetExists checks if a set exists.
func (*RulesetDocument) TableExists ¶
func (d *RulesetDocument) TableExists(family, name string) bool
TableExists checks if a table exists.
type RuntimeState ¶ added in v1.80.0
type RuntimeState string
RuntimeState represents a three-state service status. Aligns with the v1.82 health model direction (RUNNING / STOPPED / ERROR).
const ( RuntimeRunning RuntimeState = "RUNNING" RuntimeStopped RuntimeState = "STOPPED" RuntimeError RuntimeState = "ERROR" // systemctl query itself failed )
type ServiceChecker ¶ added in v1.80.0
type ServiceChecker interface {
// CheckUnit returns (RuntimeState, detail string).
// RuntimeState: RUNNING / STOPPED / ERROR.
// detail: the raw systemctl output (e.g. "active", "inactive", "failed").
CheckUnit(unit string) (RuntimeState, string)
}
ServiceChecker abstracts service-state queries so unit tests can mock systemd without requiring a real init system. The default implementation calls `systemctl is-active <unit>`.
type ServiceState ¶ added in v1.80.0
type ServiceState struct {
Nftband RuntimeState `json:"nftband"`
NftbandDetail string `json:"nftband_detail,omitempty"`
TimerCount int `json:"timer_count"` // v1.83: active nftban-* timer count
}
ServiceState holds the live status of required system services. B80-4: the validator MUST check these before declaring PROTECTED. A system with correct kernel structure but a dead daemon is not protected.
type ServiceStateJSON ¶ added in v1.81.0
type ServiceStateJSON struct {
Nftband string `json:"nftband"` // RUNNING|STOPPED|ERROR
NftbandDetail string `json:"nftband_detail,omitempty"` // raw systemctl output
TimerCount int `json:"timer_count"` // v1.83: active nftban-* timer count
}
ServiceStateJSON is the JSON representation of daemon and timer state.
type SetCheck ¶
type SetCheck struct {
Required []string `json:"required"`
Found []string `json:"found"`
Missing []string `json:"missing"`
AllFound bool `json:"all_found"`
}
SetCheck holds set validation results.
type Status ¶
type Status string
Status represents the overall protection state.
const ( // StatusProtected means all validation checks passed and at least one module active. StatusProtected Status = "protected" // StatusIdle means all axes pass but no relevant traffic observed. Exit 0. StatusIdle Status = "idle" // StatusDegraded means partial protection - some checks failed. StatusDegraded Status = "degraded" // StatusDown means no viable nftban protection detected. StatusDown Status = "down" )
type StructuralReason ¶ added in v1.229.7
type StructuralReason string
StructuralReason is the internal cause tag for a structural verdict. v1.229.7 PR-3A. See ModuleHealth.StructuralReason.
const ( ReasonNone StructuralReason = "" // ReasonExpectedAbsent — the expected objects for the resolved mode are // simply not there. The classic "enable it" case. ReasonExpectedAbsent StructuralReason = "expected_objects_absent" // ReasonDriftPresent — objects belonging to the OTHER mode are live while // this mode is in force. EXPECTED MISSING + OBSERVED PRESENT = DRIFT. // This is the motivating defect of the v1.229.7 lane; it must never be // reported as "objects missing". ReasonDriftPresent StructuralReason = "unexpected_objects_present" // ReasonExpectationUnknown — MODE=auto with no authoritative resolved plan. // The PROVENANCE side failed: we do not know what to expect. ReasonExpectationUnknown StructuralReason = "expectation_unestablished" // ReasonObservationUnknown — the expectation is known but the runtime state // could not be observed. The OBSERVATION side failed. ReasonObservationUnknown StructuralReason = "observation_incomplete" )
type StructuralState ¶ added in v1.81.0
type StructuralState string
StructuralState represents kernel object presence.
const ( StructuralPresent StructuralState = "present" StructuralMissing StructuralState = "missing" // StructuralUnknown — the expectation itself could not be established. // v1.229.7 PR-3A: with MODE=auto the effective mode is only knowable from // the resolved plan; if that record is absent, malformed, or belongs to // another module, this process MUST NOT guess. // MISSING PLAN != CLASSIC · != SURICATA · != DISABLED // Reporting present/missing here would be a false claim in either direction. StructuralUnknown StructuralState = "unknown" )
type SummaryCounts ¶
type SummaryCounts struct {
TotalFindings int `json:"total_findings"`
CriticalFindings int `json:"critical_findings"`
ErrorFindings int `json:"error_findings"`
WarnFindings int `json:"warn_findings"`
CheckedFamilies int `json:"checked_families"`
ProtectedFams int `json:"protected_families"`
DegradedFams int `json:"degraded_families"`
}
SummaryCounts provides quick stats.
type SystemdChecker ¶ added in v1.80.0
type SystemdChecker struct{}
SystemdChecker is the default production ServiceChecker.
func (SystemdChecker) CheckUnit ¶ added in v1.80.0
func (SystemdChecker) CheckUnit(unit string) (RuntimeState, string)
CheckUnit queries systemd for the unit's active state. This is a read-only query — zero side effects.
type SystemdJournalReader ¶ added in v1.84.0
type SystemdJournalReader struct{}
SystemdJournalReader is the production implementation using journalctl.
func (SystemdJournalReader) Query ¶ added in v1.84.0
func (SystemdJournalReader) Query(ctx context.Context, q JournalQuery) JournalEvidence
Query executes a bounded journalctl query and matches patterns in Go.
Strategy: fetch server-side pattern-scoped output (-g, v1.216.3) bounded by -o cat -r -n N, then scan lines for substring matches (first match wins).
type SystemdTimerChecker ¶ added in v1.83.0
type SystemdTimerChecker struct{}
SystemdTimerChecker is the default production TimerChecker.
func (SystemdTimerChecker) CountActiveTimers ¶ added in v1.83.0
func (SystemdTimerChecker) CountActiveTimers() (int, error)
CountActiveTimers queries systemd for active nftban-* timers. Read-only — zero side effects.
type TimerChecker ¶ added in v1.83.0
type TimerChecker interface {
// CountActiveTimers returns the number of active nftban-* timers.
// On query error it returns (0, error).
CountActiveTimers() (int, error)
}
TimerChecker abstracts timer-count queries so unit tests can mock systemd without requiring a real init system.
type ValidationResult ¶
type ValidationResult struct {
SchemaVersion string `json:"schema_version"`
Status Status `json:"status"`
Timestamp time.Time `json:"timestamp"`
Families []FamilyResult `json:"families"`
Findings []Finding `json:"findings"`
Summary SummaryCounts `json:"summary"`
ChainCount ChainCounts `json:"chain_counts"`
ServiceState ServiceState `json:"service_state"`
Modules ModuleHealthMap `json:"modules"` // M81-4
ConsistencyOverall string `json:"consistency_overall"` // v1.82: "ok" | "mismatch"
// v1.89 INV-M-001/002: Expose kernel state for evidence layer.
// These fields are internal — not serialized to the frozen JSON schema.
Doc *RulesetDocument `json:"-"` // parsed kernel ruleset (counters, chains, sets)
SetElementCounts map[string]int `json:"-"` // "family:set" → element count
// v1.229.11: keys whose element count could NOT be observed (exec/decode
// failure). ADDITIVE — a key present here has NO entry in SetElementCounts,
// so a consumer can never read a failed observation as a count of zero.
// ABSENT COUNT + UNKNOWN=true != COUNT 0
SetElementUnknown map[string]bool `json:"-"` // "family:set" → observation failed
}
ValidationResult is the complete output of kernel validation.
func RunValidation ¶
func RunValidation(ctx context.Context) (*ValidationResult, error)
RunValidation is a convenience function that runs validation and returns the result. Returns (result, nil) even if status is DOWN - that's a valid result.
func ValidateKernel ¶
func ValidateKernel(ctx context.Context) (*ValidationResult, error)
ValidateKernel performs complete kernel state validation. This is the main entrypoint for the validator.
CRITICAL: This function is PURE - it only reads, never modifies.
func (*ValidationResult) ExitCode ¶
func (r *ValidationResult) ExitCode() int
ExitCode returns the appropriate exit code for the status. 0 = PROTECTED or IDLE, 1 = DEGRADED, 2 = DOWN Per M81-4: IDLE is exit 0 (not an error).
func (*ValidationResult) PrintSummary ¶
func (r *ValidationResult) PrintSummary()
PrintSummary prints a human-readable summary to stdout.
func (*ValidationResult) StatusString ¶
func (r *ValidationResult) StatusString() string
StatusString returns a human-readable status.
func (*ValidationResult) ToJSON ¶
func (r *ValidationResult) ToJSON() ([]byte, error)
ToJSON converts the validation result to the frozen M81-6 JSON schema. Uses MapToHealthOutput to enforce the schema contract — never serializes the internal ValidationResult directly.
func (*ValidationResult) ToJSONLegacy ¶ added in v1.81.0
func (r *ValidationResult) ToJSONLegacy() ([]byte, error)
ToJSONLegacy serializes the raw ValidationResult for backward compat. Used by rebuild safety checks that parse the old schema. TODO(v1.82): migrate consumers to frozen schema then remove this.
type WhitelistCountersJSON ¶ added in v1.190.0
type WhitelistCountersJSON struct {
Changes FamilyCounts `json:"changes"` // IP-backed (also by op add/remove/expire in Prometheus)
}
WhitelistCountersJSON — populated v1.191.0 (today only gauges exist).