logretention

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: 15 Imported by: 0

Documentation

Overview

SPDX-License-Identifier: MPL-2.0 SPDX-FileCopyrightText: Copyright (c) 2024-2026 Antonios Voulvoulis <contact@nftban.com> meta:name="logretention-disk" meta:type="lib" meta:owner="Antonios Voulvoulis <contact@nftban.com>" meta:created_date="2026-07-19" meta:description="Reads /var/log filesystem capacity + non-root-available bytes via syscall.Statfs (Linux). This is the ONE side-effecting input; it is deliberately separate from the pure Calculate() so the calculator stays deterministic. Mirrors the installer preflight statfs convention (Bavail for non-root available, Blocks for capacity, safeconv for the block-size conversion)." meta:depends="syscall,github.com/itcmsgr/nftban/internal/safeconv" meta:inventory.files="internal/logretention/disk.go" meta:inventory.binaries="" meta:inventory.env_vars="" meta:inventory.config_files="" meta:inventory.systemd_units="" meta:inventory.network="" meta:inventory.privileges="none"

SPDX-License-Identifier: MPL-2.0 SPDX-FileCopyrightText: Copyright (c) 2024-2026 Antonios Voulvoulis <contact@nftban.com> meta:name="logretention-generate" meta:type="lib" meta:owner="Antonios Voulvoulis <contact@nftban.com>" meta:created_date="2026-07-19" meta:description="Phase-5 generated-policy transaction (Model B). Treats the logrotate files as DERIVED STATE, never operator config. Acquires an exclusive generation lock; renders all candidates into the target filesystem; validates the COMBINED effective policy with `logrotate -d --state <tmp>` (debug=no rotation; temp state=real state untouched; explicit file args=active policy never co-loaded); writes a DURABLE activation journal (candidate+backup paths, prev+candidate hashes) BEFORE touching any target; then a two-file ATOMIC activation with in-process ROLLBACK on error; writes the generated-state record only after a fully successful activation; preserves the previous valid policy on any failure; removes temporaries; records prior+active SHA-256 hashes. Z1: a crash between the per-file renames is NOT left to the next regeneration — Recover() (recover.go) deterministically rolls the set forward or back to a uniform state at every consumer (generate, status, maintenance-before-logrotate)." meta:depends="crypto/sha256,encoding/json,os,os/exec,syscall" meta:inventory.files="internal/logretention/generate.go" meta:inventory.env_vars="" meta:inventory.systemd_units="" meta:inventory.network="" meta:inventory.binaries="logrotate" meta:inventory.config_files="install/config/nftban.logrotate,install/config/nftban-suricata.logrotate" meta:inventory.privileges="root (writes /etc/logrotate.d + /var/lib/nftban/generated)"

SPDX-License-Identifier: MPL-2.0 SPDX-FileCopyrightText: Copyright (c) 2024-2026 Antonios Voulvoulis <contact@nftban.com> meta:name="logretention-inventory" meta:type="lib" meta:owner="Antonios Voulvoulis <contact@nftban.com>" meta:created_date="2026-07-19" meta:description="Canonical NFTBan log-family inventory for the retention calculator + generated-policy renderer. Mirrors the shipped baseline install/config/nftban.logrotate + nftban-suricata.logrotate (grouping, cadence, rotation mechanism). BaseSizeBytes/BaseRotate are the ceilings the calculator will not exceed; the calculator scales the effective size/rotate down to fit the capacity budget. A parity test (inventory_parity_test.go) asserts this table stays in sync with the shipped templates and logs.go LogInventory()." meta:depends="none (stdlib)" meta:inventory.files="internal/logretention/inventory.go" meta:inventory.binaries="" meta:inventory.env_vars="" meta:inventory.systemd_units="" meta:inventory.network="" meta:inventory.config_files="install/config/nftban.logrotate,install/config/nftban-suricata.logrotate" meta:inventory.privileges="none"

SPDX-License-Identifier: MPL-2.0 SPDX-FileCopyrightText: Copyright (c) 2024-2026 Antonios Voulvoulis <contact@nftban.com> meta:name="logretention-loadconf" meta:type="lib" meta:owner="Antonios Voulvoulis <contact@nftban.com>" meta:created_date="2026-07-19" meta:description="Reads the operator override authority /etc/nftban/conf.d/logs.conf (shell KEY=\"value\" convention) into the Overrides model. An absent file means fully automatic (safe default). Only uncommented keys are treated as overrides. Malformed numeric values and out-of-contract values FAIL (via Overrides.Validate) — they are never silently ignored. The generated logrotate file is NOT read as config; this file is the sole operator authority." meta:depends="os,strconv" meta:inventory.files="internal/logretention/loadconf.go" meta:inventory.binaries="" meta:inventory.env_vars="" meta:inventory.config_files="etc/nftban/conf.d/logs.conf" meta:inventory.systemd_units="" meta:inventory.network="" meta:inventory.privileges="none"

============================================================================= NFTBan Gate B — Log-retention capacity budget calculator ============================================================================= SPDX-License-Identifier: MPL-2.0 SPDX-FileCopyrightText: Copyright (c) 2024-2026 Antonios Voulvoulis <contact@nftban.com> meta:name="logretention-calculator" meta:type="lib" meta:owner="Antonios Voulvoulis <contact@nftban.com>" meta:created_date="2026-07-19" meta:description="Deterministic, side-effect-free calculator that derives a bounded log-retention policy from /var/log filesystem CAPACITY (the stable basis) + detected profile + validated operator overrides. Produces per-family effective size/rotate/retention with the invariants UNBOUNDED_STANZAS=0 and THEORETICAL_MAX<=BUDGET, preserving a minimum forensic floor. Available free space is surfaced as headroom/health only, never as the permanent budget basis. Calculate() mutates nothing and never invokes logrotate." meta:input="DiskFacts (capacity+avail), Profile, validated Overrides, LogFamily inventory" meta:output="EffectivePolicy (budget, per-family caps, theoretical max, fit verdict, source+version)" meta:depends="none (stdlib)" meta:inventory.files="internal/logretention/logretention.go,internal/logretention/disk.go,internal/logretention/render.go,internal/logretention/generate.go" meta:inventory.binaries="" meta:inventory.env_vars="" meta:inventory.config_files="install/config/nftban.logrotate,install/config/nftban-suricata.logrotate" meta:inventory.systemd_units="" meta:inventory.network="" meta:inventory.privileges="none" =============================================================================

SCOPE (Gate B / v1.222.0): the calculator + generated-policy contract. As of Phase 6-7 this package IS imported by the nftban-core CLI (nftban logs retention status), reads operator overrides from conf.d/logs.conf (LoadOverrides), and the LOG_RETENTION_* keys are declared in config-schema.json. It is NOT imported by the nftband daemon. Runtime PACKAGING wiring of the generator (install/%post + timer + config-change) is pending (audit R2); until then the shipped static install/config/nftban.logrotate is the active authority.

SPDX-License-Identifier: MPL-2.0 SPDX-FileCopyrightText: Copyright (c) 2024-2026 Antonios Voulvoulis <contact@nftban.com> meta:name="logretention-readiness" meta:type="lib" meta:owner="Antonios Voulvoulis <contact@nftban.com>" meta:created_date="2026-07-19" meta:description="DELTA-L1/L2/L3 post-generation policy-readiness invariant. After install-time generation + fallback handling, an install may report COMMITTED ONLY if a VALID ACTIVE policy exists: the generated set (every applicable target's live hash matches the generated-state transaction => READY_GENERATED) OR the APPROVED BOUNDED fallback (the active main policy is BYTE-IDENTICAL to the shipped template and bounded, with no generated-state claim => READY_FALLBACK). DELTA-L2: READY_FALLBACK is NOT syntax-only — it requires sha256(active)==sha256(shipped template) (the fallback is a verbatim template copy) AND unbounded_stanzas==0, so an arbitrary/unbounded/hand-edited valid policy can never masquerade as the fallback. DELTA-L3: READY_GENERATED covers the WHOLE generated set — when the state records a suricata policy (applicability = authoritative state key), the live /etc/logrotate.d/nftban-suricata must exist, validate, and hash-match state; a missing/drifted/stale suricata policy => NOT_READY. Single authority: the CLI, the installer assertion, and the acceptance collector all consume one Readiness() result. Pure + deterministic given an injected Validator." meta:depends="crypto/sha256,encoding/hex,encoding/json,os,strings" meta:inventory.files="internal/logretention/readiness.go" meta:inventory.env_vars="" meta:inventory.systemd_units="" meta:inventory.network="" meta:inventory.binaries="logrotate" meta:inventory.config_files="/etc/logrotate.d/nftban,/etc/logrotate.d/nftban-suricata,/etc/nftban/templates/nftban.logrotate,/var/lib/nftban/generated/logrotate/nftban-effective.state.json" meta:inventory.privileges="reads the generated policies + state + template (root in the install path)"

SPDX-License-Identifier: MPL-2.0 SPDX-FileCopyrightText: Copyright (c) 2024-2026 Antonios Voulvoulis <contact@nftban.com> meta:name="logretention-recover" meta:type="lib" meta:owner="Antonios Voulvoulis <contact@nftban.com>" meta:created_date="2026-07-19" meta:description="Z1 crash-consistency recovery for the two-phase logrotate-policy activation. A crash between the main and suricata renames would leave the on-disk policy set SPLIT (one file new, one old). Recover() reads the durable activation journal and DETERMINISTICALLY drives the set to a uniform all-new (roll-forward, completing the already-validated intent) or all-old (roll-back, when candidates are no longer available) state — never eventual-consistency-via-next-regeneration. Idempotent and safe to call before every consumer: generate, status (must not report ACTIVE_MATCH over a pending journal), and the maintenance step that triggers logrotate. Because every file mutation is an atomic rename, each target is only ever fully-old or fully-new (no torn content), so the current on-disk hash classifies it unambiguously against the journal." meta:depends="encoding/json,os,path/filepath" meta:inventory.files="internal/logretention/recover.go" meta:inventory.env_vars="" meta:inventory.systemd_units="" meta:inventory.network="" meta:inventory.binaries="" meta:inventory.config_files="" meta:inventory.privileges="root to repair (renames under /etc/logrotate.d); read-only detection needs only read on the staging dir"

SPDX-License-Identifier: MPL-2.0 SPDX-FileCopyrightText: Copyright (c) 2024-2026 Antonios Voulvoulis <contact@nftban.com> meta:name="logretention-render" meta:type="lib" meta:owner="Antonios Voulvoulis <contact@nftban.com>" meta:created_date="2026-07-19" meta:description="Deterministically renders an EffectivePolicy + family inventory into logrotate stanza text, per file (main | suricata), reproducing the corrected shipped mechanism (copytruncate vs rename+create, delaycompress only where a writer holds the rotated inode, su/create/olddir/USR2). No timestamps or nondeterminism — byte-identical for identical inputs, which the DEB≡RPM parity requirement depends on." meta:depends="none (stdlib)" meta:inventory.files="internal/logretention/render.go" meta:inventory.binaries="" meta:inventory.env_vars="" meta:inventory.config_files="" meta:inventory.systemd_units="" meta:inventory.network="" meta:inventory.privileges="none"

Index

Constants

View Source
const (
	ClassEnforcementAudit = "ENFORCEMENT_AUDIT"
	ClassSecurityEvent    = "SECURITY_EVENT"
	ClassModuleHighVolume = "MODULE_HIGH_VOLUME"
	ClassLifecycle        = "LIFECYCLE_FORENSICS"
	ClassOperational      = "OPERATIONAL"
	ClassDebug            = "DEBUG"
)

Semantic classes (R11). The most-sensitive class of a family's member logs drives its treatment; it is surfaced in `nftban logs retention status` so the operator can see which retention families are enforcement/security records vs ordinary operational/debug logs.

View Source
const (
	KiB uint64 = 1024
	MiB uint64 = 1024 * KiB
	GiB uint64 = 1024 * MiB
)

Byte-size units.

View Source
const (
	DefaultMaxPercent = 15 // default NFTBan log budget = 15% of /var/log capacity
	DefaultMinDays    = 7  // minimum forensic retention floor per family (days)
	DefaultMaxDays    = 90 // retention ceiling per family (days)

)

Calculator defaults. The budget derives PRIMARILY from filesystem capacity; available space is health/headroom only (owner directive: free space is volatile and must not be the permanent policy basis).

View Source
const (
	FitFits         = "FITS"
	FitTight        = "TIGHT"
	FitOverHeadroom = "OVER_HEADROOM"
	FitUnknown      = "UNKNOWN"
)

Fit verdicts (health signal derived from available space, not policy basis).

View Source
const (
	CapAchievable        = "ACHIEVABLE"
	CapRaisedForFloor    = "CAP_RAISED_FOR_FLOOR"
	CapFloorOverOpCap    = "FLOOR_EXCEEDS_OPERATOR_CAP"
	CapInsufficientHead  = "INSUFFICIENT_HEADROOM"
	CapFloorOverCapacity = "FLOOR_EXCEEDS_CAPACITY"
)

Capacity verdicts (structural achievability, distinct from the headroom FitVerdict).

View Source
const (
	RecoverNone           = "none"          // no journal present — nothing to do
	RecoverAlreadyWhole   = "already_whole" // journal present but the set is already uniform
	RecoverRolledFwd      = "rolled_forward"
	RecoverRolledBack     = "rolled_back"
	RecoverDiscardedStale = "discarded_stale" // legacy/corrupt journal — safe to drop (see note)
)

Recovery action strings (stable; asserted by tests and surfaced in status).

View Source
const DefaultConfPath = "/etc/nftban/conf.d/logs.conf"

DefaultConfPath is the operator override authority for log retention.

View Source
const DefaultLogDir = "/var/log"

DefaultLogDir is the filesystem whose capacity backs the retention budget.

View Source
const GeneratorVersion = "1"

GeneratorVersion is the version of the generation transaction/state writer, distinct from PolicyVersion (the rendered-policy + state schema version).

View Source
const PolicyVersion = "1"

PolicyVersion is the schema version of the generated policy + state record. Bump when the generated logrotate structure or the state schema changes.

Variables

This section is empty.

Functions

func DefaultValidator

func DefaultValidator(candidatePaths []string) (string, error)

DefaultValidator runs `logrotate -d --state <temp> <candidate>...`:

  • `-d` (debug): parse + simulate only, no file is rotated.
  • `--state <temp>`: a throwaway state file, so /var/lib/logrotate/logrotate.status is never read or written — the real logrotate state is untouched.
  • explicit candidate paths as the config args: logrotate reads ONLY those files and does not pull in /etc/logrotate.d/* (no `include` in our candidates), so the active policy is never co-loaded. Passing all candidates together validates the combined effective policy.

Missing logrotate returns an error → the caller fails safe and keeps the previous policy.

func FilesRendered

func FilesRendered(fams []LogFamily) []string

FilesRendered returns the sorted set of distinct File values in the inventory (e.g. ["main","suricata"]).

func FitVerdictFor

func FitVerdictFor(theoreticalMax, availBytes uint64) string

FitVerdictFor recomputes the headroom fit verdict from a LIVE available-space reading. The CLI uses this so `status` reflects the CURRENT filesystem, not the (possibly stale) value frozen into the generated-state record.

func PendingActivation

func PendingActivation(mainPath string) bool

PendingActivation reports, read-only, whether an interrupted activation is still pending (a journal exists). Callers that cannot write (non-root status) use this to refuse to report ACTIVE_MATCH over an unrepaired split.

func RenderFile

func RenderFile(policy EffectivePolicy, fams []LogFamily, file string) string

RenderFile renders all stanzas belonging to the given file ("main" or "suricata") as logrotate text, using the calculated per-family policy. Output is deterministic for identical (policy, fams) inputs.

Types

type DiskFacts

type DiskFacts struct {
	Path       string
	TotalBytes uint64 // filesystem capacity — the STABLE budget basis
	AvailBytes uint64 // non-root available — surfaced as headroom/health ONLY
}

DiskFacts is the /var/log filesystem capacity snapshot. It is gathered by DetectDiskFacts (which reads the filesystem) and passed to Calculate as a pure input, so the calculator itself stays deterministic and side-effect free.

func DetectDiskFacts

func DetectDiskFacts(path string) (DiskFacts, error)

DetectDiskFacts returns the capacity and non-root-available bytes of the filesystem containing path. It reads the filesystem (side effect) and is therefore kept out of Calculate, which takes DiskFacts as a pure input.

TotalBytes uses Blocks (fundamental block count) — the stable capacity basis. AvailBytes uses Bavail (blocks available to non-root) — surfaced only as headroom/health, never as the permanent budget basis.

type EffectivePolicy

type EffectivePolicy struct {
	BudgetBytes         uint64 // effective budget (may be raised to the min-achievable floor)
	TheoreticalMaxBytes uint64 // UNCOMPRESSED worst-case retained bytes (compression not modeled — conservative upper bound, not expected disk use)
	UnboundedCount      int
	FitVerdict          string // headroom vs live available space (FITS/TIGHT/OVER_HEADROOM) — volatile
	Families            []FamilyPolicy
	Profile             Profile
	Disk                DiskFacts
	PolicySource        string // "automatic" | "operator-override"
	PolicyVersion       string

	// R7 capacity/achievability (structural, vs total capacity + operator cap):
	RequestedBudgetBytes   uint64 // capacity*pct capped by MaxBytes, BEFORE the floor raise
	MinimumAchievableBytes uint64 // sum of per-family forensic floors (generations x minSize) + fixed worst-case
	CapRaisedForFloor      bool   // the forensic floor forced the budget above the requested budget
	OperatorCapExceeded    bool   // MaxBytes set but the min-achievable floor exceeds it
	Achievable             bool   // false => the minimum policy does not fit the filesystem
	CapacityVerdict        string // ACHIEVABLE | CAP_RAISED_FOR_FLOOR | FLOOR_EXCEEDS_OPERATOR_CAP | INSUFFICIENT_HEADROOM | FLOOR_EXCEEDS_CAPACITY

	// R6 forensic-floor honesty: the hard floor is expressed in GENERATIONS +
	// BYTES per family (rotate count x size cap). Elapsed retention DAYS are a
	// target/best-case only — under size-triggered rotation a busy log rotates
	// before its cadence, so real elapsed days can be shorter while the
	// generation/byte floor still holds.
	ForensicFloorKind string // "generations_and_bytes"
}

EffectivePolicy is the calculator's full output.

func Calculate

func Calculate(disk DiskFacts, prof Profile, o Overrides, fams []LogFamily) (EffectivePolicy, error)

Calculate derives the effective, bounded retention policy. It is a pure function of its inputs: it reads no files, touches no filesystem, and never invokes logrotate. Invariants guaranteed on success:

  • UnboundedCount == 0 (every family has a size cap)
  • TheoreticalMaxBytes <= BudgetBytes
  • each family retains at least its forensic floor (>= min(FloorDays,MinDays))
  • a valid operator override wins; an invalid one was already rejected by Validate

type FamilyPolicy

type FamilyPolicy struct {
	Key               string `json:"key"`
	RotateCount       int    `json:"rotate_count"` // retained generations (hard floor in generations)
	SizeCapBytes      uint64 `json:"size_cap_bytes"`
	PathCount         int    `json:"path_count"`                 // files this family governs; worst case scales with it
	RetentionDays     int    `json:"target_retention_days"`      // best-case elapsed days; size pressure may reduce actual days
	WorstCaseBytes    uint64 `json:"worst_case_bytes"`           // RotateCount * SizeCapBytes * len(Paths), uncompressed
	ForensicFloorDays int    `json:"forensic_floor_target_days"` // TARGET floor (NOT guaranteed under size pressure)
	CeilingDays       int    `json:"ceiling_days"`
	SizeTriggered     bool   `json:"size_triggered"` // a size cap exists -> may rotate before cadence

	// R11 semantic classification (surfaced by the CLI so enforcement/security
	// records are distinguishable from ordinary operational/debug logs).
	SemanticClass  string `json:"semantic_class"`
	AuthorityRole  string `json:"authority_role"`  // "authoritative" | "projection"
	WriterStrategy string `json:"writer_strategy"` // "copytruncate" | "rename+create"
	ReaderStrategy string `json:"reader_strategy"` // "same-inode" | "path-following"
}

FamilyPolicy is the calculated effective policy for one log family. The HARD forensic floor is RotateCount generations x SizeCapBytes = WorstCaseBytes; the day figures are TARGETS (best-case at/under the size cap), NOT guaranteed elapsed retention when size-triggered rotation fires before the cadence.

type GenerateOptions

type GenerateOptions struct {
	LogDir        string // filesystem whose capacity backs the budget (default /var/log)
	MainPath      string // e.g. /etc/logrotate.d/nftban (required)
	SuricataPath  string // e.g. /etc/logrotate.d/nftban-suricata ("" to skip)
	StatePath     string // generated-state JSON path
	LockPath      string // exclusive generation lock ("" -> derived next to StatePath/MainPath)
	Families      []LogFamily
	Overrides     Overrides
	Disk          DiskFacts // if zero-value, DetectDiskFacts(LogDir) is used
	Profile       Profile   // if empty Name, ClassifyProfile is used
	PanelPresent  bool      // input to profile classification when Profile is auto
	SourceVersion string    // NFTBan version/commit stamped into the state record
	Reason        string    // "install" | "config-change" | "timer" | "manual"
	Now           time.Time // injected clock (zero -> time.Now().UTC())
	Validator     Validator // injectable (nil -> DefaultValidator)
}

GenerateOptions parameterizes the transaction. Target paths are absolute; the temp/rename happens in each target's own directory so each rename is atomic on the same filesystem.

type GeneratedState

type GeneratedState struct {
	PolicyVersion          string            `json:"policy_version"`
	GeneratorVersion       string            `json:"generator_version"`
	GeneratedAt            string            `json:"generated_at"`
	SourceVersion          string            `json:"source_version"`
	Reason                 string            `json:"generation_reason"`
	Profile                Profile           `json:"profile"`
	Disk                   DiskFacts         `json:"filesystem"`
	Overrides              Overrides         `json:"operator_overrides"`
	PolicySource           string            `json:"policy_source"`
	BudgetBytes            uint64            `json:"budget_bytes"`
	RequestedBudgetBytes   uint64            `json:"requested_budget_bytes"`
	MinimumAchievableBytes uint64            `json:"minimum_achievable_bytes"`
	TheoreticalMaxBytes    uint64            `json:"theoretical_max_bytes_uncompressed"`
	UnboundedCount         int               `json:"unbounded_stanzas"`
	FitVerdict             string            `json:"fit_verdict"`
	CapacityVerdict        string            `json:"capacity_verdict"`
	Achievable             bool              `json:"achievable"`
	CapRaisedForFloor      bool              `json:"cap_raised_for_floor"`
	OperatorCapExceeded    bool              `json:"operator_cap_exceeded"`
	ForensicFloorKind      string            `json:"forensic_floor_kind"`
	Families               []FamilyPolicy    `json:"families"` // each carries ForensicFloorDays + CeilingDays
	ValidationCmd          string            `json:"validation_cmd"`
	ValidationOK           bool              `json:"validation_ok"`
	ActivePolicyHashes     map[string]string `json:"active_policy_hashes"`
	PreviousPolicyHashes   map[string]string `json:"previous_policy_hashes"`

	// Unchanged is a RETURN-ONLY signal (never persisted): Generate set it when it
	// detected the effective policy was already active and skipped all writes.
	Unchanged bool `json:"-"`
}

GeneratedState is the evidence record written after a successful activation.

func Generate

func Generate(opts GenerateOptions) (GeneratedState, error)

Generate runs the full transaction and returns the resulting state. On any error it guarantees the previously-active policy files are left intact (both files — no split-brain) and no state record is written.

type LogFamily

type LogFamily struct {
	Key           string
	Paths         []string
	File          string // "main" | "suricata"
	Cadence       string // "daily" | "weekly" | "monthly"
	Volume        VolumeClass
	Weight        int    // weighted share of the distributable size budget
	FloorDays     int    // minimum retention (forensic floor) for this family
	CeilingDays   int    // per-family safety ceiling on retention (0 = derive from volume)
	BaseRotate    int    // reference rotate (shipped-baseline value)
	BaseSizeBytes uint64 // ceiling size cap (shipped-baseline value)
	Fixed         bool   // keep base policy, do not budget-scale (report documents)

	// rotation mechanism (reproduced verbatim by the renderer)
	Copytruncate   bool
	Delaycompress  bool
	Create         string // e.g. "0640 nftban nftban" ("" = omit)
	Su             string // e.g. "suricata nftban" ("" = omit)
	Olddir         string // e.g. "/var/log/nftban/reports/archive" ("" = omit)
	CreateOlddir   string // e.g. "0750 nftban nftban" ("" = omit)
	UseMaxsize     bool   // emit `maxsize` instead of `size` (report documents)
	PostrotateUSR2 bool   // emit the Suricata USR2 postrotate reopen block

	// R11 (firewall-logs vs general-logs authority — Stream J minimum).
	SemanticClass string // ENFORCEMENT_AUDIT | SECURITY_EVENT | MODULE_HIGH_VOLUME | LIFECYCLE_FORENSICS | OPERATIONAL | DEBUG
	Primary       bool   // true = authoritative record; false = a projection/derived surface
}

LogFamily describes one logrotate stanza's identity, budget metadata, and rotation mechanism. The mechanism fields mirror the corrected shipped templates so the renderer reproduces them faithfully.

func DefaultFamilies

func DefaultFamilies() []LogFamily

DefaultFamilies returns the canonical family inventory mirroring the corrected shipped baseline templates (Gate B Phase 1).

type Overrides

type Overrides struct {
	Mode       string // "" or "auto" (default) | "manual"
	MaxPercent int    // cap budget at this %% of capacity (0 = default)
	MaxBytes   uint64 // absolute budget cap (0 = unset)
	MinDays    int    // per-family retention floor (0 = default)
	MaxDays    int    // per-family retention ceiling (0 = default)
	Profile    string // "" or "auto" | "small" | "standard" | "high-volume"
}

Overrides is the operator override model. In Phases 4-5 it is supplied directly; conf.d/logs.conf wiring is deferred. Zero value == fully automatic.

func LoadOverrides

func LoadOverrides(path string) (Overrides, error)

LoadOverrides reads logs.conf into an Overrides. An absent file yields the zero value (fully automatic). Present-but-uncommented keys become overrides. The result is validated; invalid values return an error (no silent fallback).

func (Overrides) Validate

func (o Overrides) Validate() error

Validate enforces the override contract. An invalid override FAILS — it is never silently ignored or defaulted (owner directive: INVALID_OVERRIDE fails validation; it does not silently fall back).

type Profile

type Profile struct {
	Name         string // "small" | "standard" | "high-volume"
	PanelPresent bool
}

Profile is the retention profile classification. It is derived from disk capacity (primary) and may be nudged by control-panel presence; an operator override can force it.

func ClassifyProfile

func ClassifyProfile(disk DiskFacts, panelPresent bool, override string) Profile

ClassifyProfile derives the retention profile from capacity (primary basis) and an operator override. Panel presence nudges the classification upward because control-panel hosts run more web/mail log producers.

type ReadinessOptions

type ReadinessOptions struct {
	MainPath     string    // active generated main policy (required)
	SuricataPath string    // active generated suricata policy ("" if not installed)
	StatePath    string    // generated-state record (may be absent -> fallback)
	TemplatePath string    // shipped bounded fallback template (identity source for READY_FALLBACK)
	Validator    Validator // nil -> DefaultValidator (real logrotate -d)
}

ReadinessOptions parameterizes the check (paths + injectable validator).

type ReadinessResult

type ReadinessResult struct {
	Verdict          ReadinessVerdict `json:"verdict"`
	PolicySource     string           `json:"policy_source"` // "generated" | "fallback" | "none"
	PolicyPath       string           `json:"policy_path"`
	FileMode         string           `json:"file_mode"`
	FileBytes        int64            `json:"file_bytes"`
	ValidationResult string           `json:"validation_result"` // "valid" | error text | "not_run"

	// main policy
	ActiveMainHash   string `json:"active_main_hash"`
	ExpectedMainHash string `json:"expected_main_hash,omitempty"` // from state (generated) or template (fallback)
	MainHashMatch    bool   `json:"main_hash_match"`

	// DELTA-L2: fallback identity + boundedness
	FallbackIdentityMatch bool   `json:"fallback_identity_match"`
	FallbackTemplateHash  string `json:"fallback_template_hash,omitempty"`
	UnboundedStanzas      int    `json:"unbounded_stanzas"`

	// DELTA-L3: suricata generated policy (part of the generated set)
	SuricataApplicable bool   `json:"suricata_applicable"`
	ActiveSuricataHash string `json:"active_suricata_hash,omitempty"`
	ExpSuricataHash    string `json:"expected_suricata_hash,omitempty"`
	SuricataHashMatch  bool   `json:"suricata_hash_match"`

	JournalRecovered string `json:"journal_recovered,omitempty"`
	Reason           string `json:"reason,omitempty"`
	Remediation      string `json:"remediation,omitempty"`
	SelfHealPending  bool   `json:"self_heal_pending"` // true for READY_FALLBACK
}

ReadinessResult is the structured, durable outcome; every consumer (CLI, installer assertion, acceptance collector) reads these fields so no consumer re-implements a weaker check.

func Readiness

func Readiness(opts ReadinessOptions) ReadinessResult

Readiness computes the post-generation policy-readiness verdict — the single authority for the CLI, the installer assertion, and the acceptance collector.

func (ReadinessResult) Ready

func (r ReadinessResult) Ready() bool

Ready reports whether the verdict allows a COMMITTED install (generated or a verified bounded fallback). Only NOT_READY blocks COMMITTED.

type ReadinessVerdict

type ReadinessVerdict string

ReadinessVerdict is the install-readiness classification of the active policy.

const (
	ReadyGenerated ReadinessVerdict = "READY_GENERATED" // whole generated set matches state
	ReadyFallback  ReadinessVerdict = "READY_FALLBACK"  // active main == approved bounded template
	NotReady       ReadinessVerdict = "NOT_READY"       // no valid active policy — install must be DEGRADED
)

type RecoverResult

type RecoverResult struct {
	JournalFound bool     // an activation journal existed
	Action       string   // one of the Recover* constants
	Targets      []string // targets named by the journal
	FromState    string   // "uniform_new" | "uniform_old" | "split" (as found)
}

RecoverResult reports what Recover observed and did.

func Recover

func Recover(mainPath string) (RecoverResult, error)

Recover completes or unwinds an interrupted activation so the on-disk policy set is uniform. It is a no-op (JournalFound=false) when no journal exists. Requires write access to the target directory + staging dir to repair.

type SelfBoundedLog

type SelfBoundedLog struct {
	Path          string // may contain <run_id> for per-run records
	SemanticClass string
	BoundedBy     string // the mechanism that bounds it (not logrotate)
}

SelfBoundedLog is a log NOT rotated by logrotate: it is bounded by its own writer/pruner. Z6 classifies these too, so the semantic-class coverage is complete across ALL nftban logs, not only the logrotate-managed families — the retention MECHANISM differs (prune vs rotate) but the SIEM/forensic class is still declared.

func SelfBoundedForensicLogs

func SelfBoundedForensicLogs() []SelfBoundedLog

SelfBoundedForensicLogs returns the self-bounded (non-logrotate) nftban logs with their semantic class. The per-update-run record under /var/log/nftban/update-runs/<run_id>/ — run.jsonl (machine JSONL stream), human.log (operator slice), installer.log (per-run installer copy) — is LIFECYCLE_FORENSICS, bounded by _forensic_prune (keeps a fixed number of recent runs) rather than logrotate. Classifying them here closes the Z6 gap where these logs had no semantic class.

type Validator

type Validator func(candidatePaths []string) (cmd string, err error)

Validator validates one or more candidate logrotate files TOGETHER without executing any rotation and without touching the real logrotate state or the active policy. It returns the exact command string (for the state record) and an error if any candidate is invalid or logrotate is unavailable.

type VolumeClass

type VolumeClass int

VolumeClass groups families by expected write rate; it drives budget weighting and the default retention window.

const (
	VolumeLow VolumeClass = iota
	VolumeMedium
	VolumeHigh
)

func (VolumeClass) String

func (v VolumeClass) String() string

Jump to

Keyboard shortcuts

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