Documentation
¶
Index ¶
- Constants
- Variables
- type BanAction
- type Detector
- type FTPDetector
- type IPState
- type MailDetector
- type PanelDetector
- type Registry
- type SSHDetector
- type Scorer
- func (s *Scorer) Cleanup() int
- func (s *Scorer) DecayScores()
- func (s *Scorer) GetIPState(ip netip.Addr) (IPState, bool)
- func (s *Scorer) GetStats() StatsSnapshot
- func (s *Scorer) RecordVerdict(v Verdict) *BanAction
- func (s *Scorer) Reset()
- func (s *Scorer) SubnetStateFor(prefix netip.Prefix) (SubnetState, bool)
- func (s *Scorer) TrackedIPs() int
- func (s *Scorer) TrackedSubnets() int
- type ScorerConfig
- type Stats
- type StatsSnapshot
- type SubnetState
- type Verdict
Constants ¶
const ( ReasonSSHFailedPassword uint16 = 1001 ReasonSSHInvalidUser uint16 = 1002 ReasonSSHPreauth uint16 = 1003 ReasonSSHTooManyFailures uint16 = 1004 ReasonSSHRootAttempt uint16 = 1005 ReasonDovecotAuthFail uint16 = 2001 ReasonPostfixSASL uint16 = 2002 ReasonEximAuthFail uint16 = 2003 ReasonDovecotPamFail uint16 = 2004 ReasonFTPAuthFail uint16 = 3001 ReasonDirectAdminLogin uint16 = 4001 ReasonCPanelLogin uint16 = 4002 ReasonWordPressXMLRPC uint16 = 5001 ReasonWordPressWPLogin uint16 = 5002 ReasonPleskLogin uint16 = 4003 ReasonGenericAuthFail uint16 = 9001 )
Reason codes for different detection types (stable enum values)
Variables ¶
var ReasonName = map[uint16]string{ ReasonSSHFailedPassword: "ssh_failed_password", ReasonSSHInvalidUser: "ssh_invalid_user", ReasonSSHPreauth: "ssh_preauth_disconnect", ReasonSSHTooManyFailures: "ssh_too_many_failures", ReasonSSHRootAttempt: "ssh_root_attempt", ReasonDovecotAuthFail: "dovecot_auth_fail", ReasonPostfixSASL: "postfix_sasl_fail", ReasonEximAuthFail: "exim_auth_fail", ReasonDovecotPamFail: "dovecot_pam_fail", ReasonFTPAuthFail: "ftp_auth_fail", ReasonDirectAdminLogin: "directadmin_login_fail", ReasonCPanelLogin: "cpanel_login_fail", ReasonWordPressXMLRPC: "wordpress_xmlrpc", ReasonWordPressWPLogin: "wordpress_wp_login", ReasonPleskLogin: "plesk_login_fail", ReasonGenericAuthFail: "generic_auth_fail", }
ReasonName maps reason codes to human-readable names
Functions ¶
This section is empty.
Types ¶
type BanAction ¶
type BanAction struct {
IP netip.Addr
Prefix netip.Prefix // v1.113: set for subnet-aggregation bans; zero value otherwise
Duration time.Duration
Reason string
Score int32
Service string
IsNew bool // true if this is a new ban, false if escalation
IsSubnet bool // v1.113: true when this ban targets a CIDR via subnet aggregation
}
BanAction represents a ban decision.
For per-IP bans (the original LoginMon path), IP is set and Prefix.IsValid() returns false. For subnet-aggregation bans (v1.113), Prefix is set to the /24 (IPv4) or /64 (IPv6) target and IsSubnet is true; IP still carries the triggering IP (the one whose event tripped the threshold) for audit-log attribution.
type Detector ¶
type Detector interface {
// Name returns the detector identifier
Name() string
// Detect checks if a log line matches this detector's patterns
// Returns (verdict, true) if matched, (zero, false) if not
// The line is passed as []byte to enable zero-copy processing
Detect(line []byte) (Verdict, bool)
}
Detector is the interface for all login failure detectors Each detector implements signal-based detection: 1. Cheap prefilter (bytes.Contains) to skip non-matching lines 2. Precise extraction only on matching lines
type FTPDetector ¶
type FTPDetector struct {
// contains filtered or unexported fields
}
FTPDetector detects FTP authentication failures
type IPState ¶
type IPState struct {
Score int32 // Current score (scaled by 100)
Detections int32 // Total detection count
BanCount int32 // Number of times banned
LastSeen time.Time // Last detection time
LastBan time.Time // Last ban time
FirstSeen time.Time // First detection time
LastService string // Last service that detected this IP
LastReason uint16 // Last reason code
}
IPState tracks state for a single IP
type MailDetector ¶
type MailDetector struct {
// contains filtered or unexported fields
}
MailDetector detects mail authentication failures (Dovecot, Postfix, Exim)
func NewMailDetector ¶
func NewMailDetector() *MailDetector
NewMailDetector creates a new mail detector
func (*MailDetector) Detect ¶
func (d *MailDetector) Detect(line []byte) (Verdict, bool)
Detect checks if a log line is a mail authentication failure
func (*MailDetector) Name ¶
func (d *MailDetector) Name() string
Name returns the detector identifier
type PanelDetector ¶
type PanelDetector struct {
// contains filtered or unexported fields
}
PanelDetector detects control panel authentication failures
func NewPanelDetector ¶
func NewPanelDetector() *PanelDetector
NewPanelDetector creates a new panel detector
func (*PanelDetector) Detect ¶
func (d *PanelDetector) Detect(line []byte) (Verdict, bool)
Detect checks if a log line is a panel authentication failure
func (*PanelDetector) Name ¶
func (d *PanelDetector) Name() string
Name returns the detector identifier
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry holds all registered detectors
func NewRegistry ¶
func NewRegistry() *Registry
NewRegistry creates a new detector registry with default detectors
func (*Registry) Detect ¶
Detect runs all registered detectors on a line Returns on first match (detectors are ordered by likelihood)
func (*Registry) DetectAll ¶
DetectAll runs all detectors and returns all matches Useful for hybrid mode where multiple signals may be present
type SSHDetector ¶
type SSHDetector struct {
// contains filtered or unexported fields
}
SSHDetector detects SSH authentication failures
func NewSSHDetector ¶
func NewSSHDetector() *SSHDetector
NewSSHDetector creates a new SSH detector with precomputed signals
type Scorer ¶
type Scorer struct {
// contains filtered or unexported fields
}
Scorer tracks IP scores and triggers bans
func NewScorerDefault ¶
func NewScorerDefault() *Scorer
NewScorerDefault creates a scorer with default config
func (*Scorer) DecayScores ¶
func (s *Scorer) DecayScores()
DecayScores reduces all IP scores (call periodically)
func (*Scorer) GetIPState ¶
GetIPState returns the current state for an IP (thread-safe copy)
func (*Scorer) GetStats ¶
func (s *Scorer) GetStats() StatsSnapshot
GetStats returns current statistics snapshot
func (*Scorer) RecordVerdict ¶
RecordVerdict records a detection verdict and returns ban action if threshold reached
func (*Scorer) SubnetStateFor ¶ added in v1.113.0
func (s *Scorer) SubnetStateFor(prefix netip.Prefix) (SubnetState, bool)
SubnetStateFor returns a copy of the subnet state for the given prefix. Returns (zero, false) when the prefix is not currently tracked. Thread-safe. Useful for tests and audit-log emission from module.go.
func (*Scorer) TrackedIPs ¶
TrackedIPs returns the number of currently tracked IPs
func (*Scorer) TrackedSubnets ¶ added in v1.113.0
TrackedSubnets returns the number of currently tracked subnet prefixes. Thread-safe.
type ScorerConfig ¶
type ScorerConfig struct {
// Thresholds (score * 100 for precision)
ThresholdTempBan int32 // Score to trigger temp ban (default: 45 = 0.45)
ThresholdEscalate int32 // Score to escalate ban (default: 65 = 0.65)
ThresholdPermanent int32 // Score for permanent ban (default: 100 = 1.00)
// Ban durations
TempBanDuration time.Duration // Initial temp ban (default: 15m)
EscalateDurations []time.Duration // Escalation ladder (2h, 4h, 12h, 24h)
// Decay
ScoreDecayInterval time.Duration // How often to decay scores (default: 5m)
ScoreDecayAmount int32 // Points to decay per interval (default: 5)
// Cleanup
IPRetentionDuration time.Duration // How long to keep IP data (default: 24h)
// Deduplication (exponential backoff)
RecentBanWindow time.Duration // Initial suppress window (default: 10s)
RecentBanMaxWindow time.Duration // Maximum suppress window (default: 5m)
SubnetAggEnabled bool // Master toggle (default: false)
SubnetAggMode string // "observe" or "enforce" (default: "observe")
SubnetWindow time.Duration // Rolling-window duration (default: 5m)
SubnetUniqueIPsMin int // Minimum distinct IPs in window (default: 5)
SubnetMinTotalEvents int // Minimum total events in window (default: 10)
SubnetIPv4Prefix int // IPv4 aggregation prefix (default: 24)
SubnetIPv6Prefix int // IPv6 aggregation prefix (default: 64)
SubnetAction string // "ban_cidr" (initial release); reserved: "pressure_score", "dynamic_threshold"
SubnetCIDRBanDuration time.Duration // Duration for subnet ban (0 = permanent; default: 24h)
SubnetMaxTracked int // Max simultaneously-tracked subnets (default: 10000; LRU eviction)
SubnetTrustedAllowlist []netip.Prefix // Operator-curated trusted-provider subnets (skip aggregation)
SubnetTriggerReasonOnly bool // Restrict aggregation to EXIM_AUTH_FAIL only (default: true)
}
ScorerConfig holds scoring thresholds
func DefaultScorerConfig ¶
func DefaultScorerConfig() ScorerConfig
DefaultScorerConfig returns production defaults
type Stats ¶
type Stats struct {
TotalDetections atomic.Int64
TotalBans atomic.Int64
TotalEscalations atomic.Int64
TotalPermanent atomic.Int64
UniqueIPs atomic.Int64
// IPv4/IPv6 tracking
DetectionsIPv4 atomic.Int64
DetectionsIPv6 atomic.Int64
BansIPv4 atomic.Int64
BansIPv6 atomic.Int64
UniqueIPv4 atomic.Int64
UniqueIPv6 atomic.Int64
// Per-service detection counts
DetectionsByService sync.Map // map[string]*atomic.Int64
// Per-service ban counts
BansByService sync.Map // map[string]*atomic.Int64
// Per-reason detection counts
DetectionsByReason sync.Map // map[uint16]*atomic.Int64
// Per-reason ban counts
BansByReason sync.Map // map[uint16]*atomic.Int64
// v1.113 subnet-aggregation counters
SubnetPressureCount atomic.Int64 // Cumulative observe-mode pressure events emitted across all prefixes.
SubnetBansTotal atomic.Int64 // Cumulative CIDR bans triggered in enforce mode.
SubnetWatchActive atomic.Int64 // Current number of prefixes being actively watched (gauge).
}
Stats holds global statistics
type StatsSnapshot ¶
type StatsSnapshot struct {
TotalDetections int64
TotalBans int64
TotalEscalations int64
TotalPermanent int64
UniqueIPs int64
// IPv4/IPv6 breakdown
DetectionsIPv4 int64
DetectionsIPv6 int64
BansIPv4 int64
BansIPv6 int64
UniqueIPv4 int64
UniqueIPv6 int64
// By service
DetectionsByService map[string]int64
BansByService map[string]int64
// By reason
DetectionsByReason map[string]int64
BansByReason map[string]int64
// v1.113 subnet aggregation
SubnetPressureCount int64
SubnetBansTotal int64
SubnetWatchActive int64
}
StatsSnapshot is a point-in-time copy of statistics
type SubnetState ¶ added in v1.113.0
type SubnetState struct {
// UniqueIPs maps source IPs seen in this prefix to their last-seen
// timestamp. Used both for the unique-IP-count threshold and for
// per-IP timestamp eviction when the rolling window expires.
UniqueIPs map[netip.Addr]time.Time
TotalEvents int // Total events in this prefix within the rolling window.
FirstSeen time.Time // Anchor for the rolling window (advances on window-expiry reset).
LastSeen time.Time // Most recent event timestamp.
Banned bool // Set true once the subnet has been banned (in enforce mode).
BannedAt time.Time // Timestamp of the subnet ban.
PressureHits int64 // Cumulative observe-mode pressure events emitted (audit counter).
}
SubnetState tracks per-prefix aggregation state for the v1.113 subnet aggregation primitive. Each entry represents one /24 (IPv4) or /64 (IPv6) prefix being observed for rotating-brute-force patterns. Values are in-memory only; the rolling window is bounded by ScorerConfig.SubnetWindow and the map size is bounded by ScorerConfig.SubnetMaxTracked.
type Verdict ¶
type Verdict struct {
IP netip.Addr // Parsed IP address (IPv4 or IPv6)
Reason uint16 // Stable enum code for the detection reason
ScoreDelta int16 // Points to add to the IP's risk score (scaled by 100)
User string // Optional: username involved (empty if not extracted)
Service string // Service name (ssh, dovecot, etc.)
}
Verdict represents the result of a detection match