Documentation
¶
Overview ¶
Package safety provides protection mechanisms to prevent self-lockout and ensure system stability during firewall operations.
Features ¶
The safety package implements several protection mechanisms:
- System IP Detection: Auto-detects IPs that must never be blocked (server IP, gateway, DNS servers, active SSH connections)
- Memory Limits: Monitors and enforces memory usage limits
- File Safety: Validates file operations to prevent data loss
- Resource Limits: Prevents runaway processes
System IP Detection ¶
The Detect functions analyze the system to find critical IPs:
ips, err := safety.DetectSystemIPs() // Returns: server IPs, gateway IP, DNS servers, active connections
These IPs are automatically added to the whitelist to prevent lockout.
Memory Protection ¶
Memory limits prevent the watchdog and feeds from consuming excessive RAM:
if safety.IsMemoryPressureHigh() {
// Trigger garbage collection or reduce operations
}
File Safety ¶
File operations are validated to ensure they target allowed paths:
if safety.IsPathSafe(filepath) {
// Proceed with write
}
Index ¶
- Constants
- Variables
- func CanAllocate(estimatedBytes int64) bool
- func ClearProtectionState() error
- func FormatBytes(bytes int64) string
- func GetEvictableBans(maxCount int) ([]string, error)
- func GetMaxCIDRsHard() int
- func GetMaxCIDRsHardWithTier() (limit int, tier string)
- func GetMemoryBudget() int64
- func GetPermanentBanStats() (total, protected, evictable int, err error)
- func GetProfileDescription() string
- func GetProtectionSummary() string
- func GetResourceLimits() (memBudget int64, maxWorkers int)
- func InitCPU(lim Limits)
- func InitMemory(lim Limits)
- func IsProtectionActive() bool
- func IsSymlink(path string) (bool, error)
- func RecordProtectionTriggered(feedsSkipped bool, geobanSkipped bool, feedsCIDRs int, geobanCIDRs int, ...) error
- func ReloadMemoryLimits()
- func RemovePermanentBan(ip string) error
- func SafeAppendFile(path string, data []byte, perm os.FileMode) error
- func SafeAppendFlags() int
- func SafeCreate(path string, perm os.FileMode) (*os.File, error)
- func SafeFileFlags() int
- func SafeMkdirAll(path string, perm os.FileMode) error
- func SafeOpenFile(path string, flag int, perm os.FileMode) (*os.File, error)
- func SafeWriteFile(path string, data []byte, perm os.FileMode) error
- func SavePermanentBans(state *PermanentBansState) error
- func SetBanProtected(ip string, protected bool) error
- func ShouldSkipFeeds() bool
- func ShouldSkipGeoban() bool
- func TrackPermanentBan(ip, reason, source string, protected bool) error
- func ValidateDirectory(dir string) error
- func ValidatePath(path string) error
- type CIDRLoadResult
- type Limits
- type MemAvail
- type MemoryLimits
- type PermanentBan
- type PermanentBansState
- type PressureLevel
- type ProtectionState
- type ServerProfile
- type SystemIPs
Constants ¶
const ( // MaxCIDRsWarning is the threshold for warning users about large CIDR sets MaxCIDRsWarning = 50000 // BytesPerCIDREstimate is the estimated memory per CIDR during merge operations // Includes string storage + parsing + temporary merge buffers BytesPerCIDREstimate = 300 // BanAgeThresholdDays is how long a permanent ban must be inactive before eviction BanAgeThresholdDays = 30 )
CIDR loading limits to prevent memory exhaustion
const ( // ProtectionStateDir is where protection state is stored ProtectionStateDir = "/var/lib/nftban/state" // ProtectionStateFile is the protection state JSON file ProtectionStateFile = "/var/lib/nftban/state/protection.json" )
const ( // PermanentBansDir is where permanent ban tracking is stored PermanentBansDir = "/var/lib/nftban/state" // PermanentBansFile tracks permanent bans with metadata PermanentBansFile = "/var/lib/nftban/state/permanent_bans.json" )
Variables ¶
var ErrInsecureDirectory = errors.New("target directory has insecure permissions (world-writable)")
ErrInsecureDirectory is returned when target directory has insecure permissions
var ErrPathTraversal = errors.New("path traversal detected")
ErrPathTraversal is returned when path traversal is detected
var ErrSymlinkDetected = errors.New("symlink detected in path (TOCTOU protection)")
ErrSymlinkDetected is returned when a symlink is detected in the path
Functions ¶
func CanAllocate ¶ added in v1.2.3
CanAllocate checks if allocating the given bytes is safe Returns false if allocation would exceed available memory budget
func ClearProtectionState ¶ added in v1.8.16
func ClearProtectionState() error
ClearProtectionState removes protection state (called when sync succeeds fully)
func FormatBytes ¶
FormatBytes converts bytes to human-readable format
func GetEvictableBans ¶ added in v1.8.16
GetEvictableBans returns IPs that can be evicted (old, unprotected) Returns at most maxCount IPs, oldest first
func GetMaxCIDRsHard ¶ added in v1.8.16
func GetMaxCIDRsHard() int
GetMaxCIDRsHard returns the dynamic CIDR limit based on server tier Small servers (≤4GB): 75,000 max CIDRs Medium servers (4-8GB): 100,000 max CIDRs Large servers (>8GB): 150,000 max CIDRs
func GetMaxCIDRsHardWithTier ¶ added in v1.8.16
GetMaxCIDRsHardWithTier returns both the limit and tier name for logging
func GetMemoryBudget ¶ added in v1.2.3
func GetMemoryBudget() int64
GetMemoryBudget calculates a safe memory budget based on server profile Takes into account CPU cores, total RAM, and control panel presence Returns a value that leaves headroom for the OS, panel, and other processes
func GetPermanentBanStats ¶ added in v1.8.16
GetPermanentBanStats returns stats about permanent bans
func GetProfileDescription ¶ added in v1.2.3
func GetProfileDescription() string
GetProfileDescription returns a human-readable description of the server profile
func GetProtectionSummary ¶ added in v1.8.16
func GetProtectionSummary() string
GetProtectionSummary returns a human-readable protection status
func GetResourceLimits ¶ added in v1.2.3
GetResourceLimits calculates appropriate resource limits based on server profile Returns memory budget and max concurrent workers
func InitCPU ¶
func InitCPU(lim Limits)
InitCPU sets GOMAXPROCS based on config This prevents the Go server from consuming all CPU cores
func InitMemory ¶
func InitMemory(lim Limits)
InitMemory sets memory limit based on server profile and optional overrides Uses runtime/debug SetMemoryLimit (Go 1.19+)
The memory budget is calculated by GetResourceLimits() which considers:
- Server RAM size (applies tier caps: ≤4GB→384MB, 4-8GB→512MB, >8GB→1GB)
- Control panel presence (20% for panel servers, 35% for non-panel)
- Minimum 64MB floor
Environment variable NFTBAN_MAX_MEMORY_BYTES can override this for special cases.
func IsProtectionActive ¶ added in v1.8.16
func IsProtectionActive() bool
IsProtectionActive returns true if memory protection is currently active
func RecordProtectionTriggered ¶ added in v1.8.16
func RecordProtectionTriggered(feedsSkipped bool, geobanSkipped bool, feedsCIDRs int, geobanCIDRs int, feedsMem int64, geobanMem int64) error
RecordProtectionTriggered writes protection state when feeds or geoban are skipped
func ReloadMemoryLimits ¶ added in v1.0.28
func ReloadMemoryLimits()
ReloadMemoryLimits reloads limits from environment (for testing/config changes)
func RemovePermanentBan ¶ added in v1.8.16
RemovePermanentBan removes a ban from tracking
func SafeAppendFile ¶ added in v1.0.28
SafeAppendFile appends data to a file with TOCTOU protection
func SafeAppendFlags ¶ added in v1.0.28
func SafeAppendFlags() int
SafeAppendFlags returns secure flags for appending to files
func SafeCreate ¶ added in v1.0.28
SafeCreate creates a file with TOCTOU protection - Validates path for symlinks and traversal - Uses O_NOFOLLOW to prevent symlink following - Validates parent directory permissions
func SafeFileFlags ¶ added in v1.0.28
func SafeFileFlags() int
SafeFileFlags returns secure flags for file creation O_NOFOLLOW prevents following symlinks (TOCTOU protection) O_EXCL ensures file is created (not opened if exists)
func SafeMkdirAll ¶ added in v1.0.28
SafeMkdirAll creates directories with TOCTOU protection Validates each path component is not a symlink
func SafeOpenFile ¶ added in v1.0.28
SafeOpenFile opens a file with TOCTOU protection
func SafeWriteFile ¶ added in v1.0.28
SafeWriteFile writes data to a file with TOCTOU protection Uses atomic write pattern: write to temp, then rename
func SavePermanentBans ¶ added in v1.8.16
func SavePermanentBans(state *PermanentBansState) error
SavePermanentBans saves the permanent bans state to disk
func SetBanProtected ¶ added in v1.8.16
SetBanProtected sets the protected flag for a permanent ban
func ShouldSkipFeeds ¶ added in v1.8.16
func ShouldSkipFeeds() bool
ShouldSkipFeeds returns true if memory pressure is critical
func ShouldSkipGeoban ¶ added in v1.8.16
func ShouldSkipGeoban() bool
ShouldSkipGeoban returns true if memory pressure is too high for geoban
func TrackPermanentBan ¶ added in v1.8.16
TrackPermanentBan adds or updates a permanent ban in tracking
func ValidateDirectory ¶ added in v1.0.28
ValidateDirectory checks if a directory is safe for file creation Returns error if directory is world-writable (o+w)
func ValidatePath ¶ added in v1.0.28
ValidatePath performs security checks on a file path - Rejects symlinks anywhere in the path - Rejects path traversal attempts (../) - Rejects world-writable directories
Types ¶
type CIDRLoadResult ¶ added in v1.8.16
type CIDRLoadResult struct {
Allowed bool // Whether to proceed with loading
Warning bool // Whether to show a warning
Message string // Human-readable message
EstimatedMem int64 // Estimated memory usage in bytes
}
CIDRLoadResult describes the result of ValidateCIDRLoad
func ValidateCIDRLoad ¶ added in v1.8.16
func ValidateCIDRLoad(ipv4Count, ipv6Count int) CIDRLoadResult
ValidateCIDRLoad checks if loading the given number of CIDRs is safe Returns result with recommendation and warning message if applicable
type Limits ¶
type Limits struct {
// GOMAXPROCS limit (CPU cores)
GoMaxProcs int // default: 2
// Connection limits
MaxConcurrentConns int // default: 100
MaxConnsPerIP int // default: 10
// Request limits
RequestTimeoutSec int // default: 30
MaxRequestBodyMB int // default: 10
MaxRequestBodyBytes int64 // computed from MB
// Rate limiting
RateLimitPerMin int // default: 60 requests per minute per IP
// Memory limits
MaxMemoryPercent int // default: 20% of available
MaxMemoryBytes int64 // default: 512 MiB
// Logging
EnableMetrics bool // default: true
}
Limits holds all safety thresholds for the GUI server
type MemAvail ¶
MemAvail holds available memory info (cgroup-aware) This matches the pattern from go-feeds/internal/safety/mem.go
func AvailableMem ¶
func AvailableMem() MemAvail
AvailableMem returns available memory (cgroup-aware for containers) This is critical for running in Docker/Kubernetes where cgroup limits apply
type MemoryLimits ¶ added in v1.0.28
type MemoryLimits struct {
// Scorer limits (pkg/suricata/scorer.go)
ScorerMaxIPs int // Max unique IPs tracked in scorer (default: 50000)
ScorerMaxEventsPerIP int // Max event timestamps per IP (default: 100)
// Analytics limits (pkg/analytics/state.go)
AnalyticsMaxIPOrigins int // Max IPs in ipOrigins map (default: 100000)
AnalyticsMaxIPsPerCountry int // Max IPs per country (default: 10000)
// Stats cache limits (pkg/suricata/stats/cache.go)
StatsMaxSIDs int // Max SIDs tracked (default: 10000)
StatsMaxSourcesPerSID int // Max unique sources per SID (default: 1000)
// Queue limits (cli/lib/nftban/helpers/nftban_task_queue.sh)
QueueMaxPending int // Max pending tasks (default: 10000)
QueueDLQAutoRetention int // Auto-purge DLQ entries older than N days (default: 7)
}
MemoryLimits holds caps and TTLs to prevent unbounded memory growth (CWE-400) All limits are configurable via environment variables with sane defaults.
func DefaultMemoryLimits ¶ added in v1.0.28
func DefaultMemoryLimits() MemoryLimits
DefaultMemoryLimits returns production-safe defaults
func GetMemoryLimits ¶ added in v1.0.28
func GetMemoryLimits() MemoryLimits
GetMemoryLimits returns the global memory limits
type PermanentBan ¶ added in v1.8.16
type PermanentBan struct {
IP string `json:"ip"`
AddedAt time.Time `json:"added_at"`
Reason string `json:"reason,omitempty"`
Source string `json:"source,omitempty"`
Protected bool `json:"protected"` // If true, NEVER evict this IP
}
PermanentBan represents a permanent ban with tracking metadata
type PermanentBansState ¶ added in v1.8.16
type PermanentBansState struct {
UpdatedAt time.Time `json:"updated_at"`
Bans map[string]*PermanentBan `json:"bans"` // keyed by IP
}
PermanentBansState holds all tracked permanent bans
func LoadPermanentBans ¶ added in v1.8.16
func LoadPermanentBans() (*PermanentBansState, error)
LoadPermanentBans loads the permanent bans state from disk
type PressureLevel ¶ added in v1.8.16
type PressureLevel int
PressureLevel represents the current memory pressure state
const ( // PressureNormal - below 70% of memory budget, full operations PressureNormal PressureLevel = iota // PressureWarning - 70-85% of budget, log warning but continue PressureWarning // PressureHigh - 85-95% of budget, skip geoban refresh PressureHigh // PressureCritical - >95% of budget, trim cold bans + skip feeds PressureCritical )
func GetMemoryPressureLevel ¶ added in v1.8.16
func GetMemoryPressureLevel() PressureLevel
GetMemoryPressureLevel returns the current memory pressure level
func (PressureLevel) String ¶ added in v1.8.16
func (p PressureLevel) String() string
String returns the pressure level name
type ProtectionState ¶ added in v1.8.16
type ProtectionState struct {
Timestamp string `json:"timestamp"`
FeedsSkipped bool `json:"feeds_skipped"`
GeobanSkipped bool `json:"geoban_skipped"`
FeedsCIDRCount int `json:"feeds_cidr_count,omitempty"`
GeobanCIDRCount int `json:"geoban_cidr_count,omitempty"`
FeedsMemNeeded int64 `json:"feeds_mem_needed,omitempty"`
GeobanMemNeeded int64 `json:"geoban_mem_needed,omitempty"`
AvailableMemory int64 `json:"available_memory"`
Reason string `json:"reason"`
}
ProtectionState tracks what was skipped due to memory limits
func GetProtectionState ¶ added in v1.8.16
func GetProtectionState() (*ProtectionState, error)
GetProtectionState reads current protection state
type ServerProfile ¶ added in v1.2.3
type ServerProfile struct {
CPUCores int // Number of CPU cores
TotalRAM int64 // Total RAM in bytes
AvailRAM int64 // Available RAM in bytes
HasControlPanel bool // Whether a control panel is detected
PanelType string // "cpanel", "directadmin", "plesk", "cyberpanel", "none"
}
ServerProfile describes the server's resource characteristics
func DetectServerProfile ¶ added in v1.2.3
func DetectServerProfile() ServerProfile
DetectServerProfile analyzes the server and returns its resource profile
type SystemIPs ¶
type SystemIPs struct {
ServerIPs []net.IP // All server interface IPs
CurrentUserIP net.IP // IP of current SSH connection
GatewayIPs []net.IP // Default gateway
DNSServers []net.IP // DNS servers from /etc/resolv.conf
LoopbackCIDRs []net.IPNet // 127.0.0.0/8, ::1/128
}
SystemIPs holds all critical IPs that must NEVER be blocked
func DetectSystemIPs ¶
DetectSystemIPs auto-detects all critical IPs that must be whitelisted
func (*SystemIPs) GetAllIPsWithCIDRs ¶
GetAllIPsWithCIDRs returns all IPs including loopback CIDRs
func (*SystemIPs) PrintSystemIPs ¶
func (s *SystemIPs) PrintSystemIPs()
PrintSystemIPs displays all detected IPs