Skip to content

Login Monitoring

Antonios Voulvoulis edited this page Apr 14, 2026 · 1 revision

Login Monitoring (LoginMon)

Type: Module Layer: L2 — Authentication brute-force detection Since: v1.36.0 (Go pipeline since v1.80.x) Config: conf.d/login_alert.conf (.local override) Daemon dependency: YES — daemon required for all module operation


Purpose

See also: Glossary | Health Model | Architecture | Known Limitations

LoginMon observes authentication log events via daemon file watchers for brute-force patterns, scores source IPs based on failure frequency and behavior, and issues kernel-level bans via the manual blacklist sets. Every step in the detection and response chain requires the Go daemon (nftband). If the daemon stops, LoginMon stops detecting, scoring, and banning entirely.


How It Works

LoginMon operates entirely in the Go daemon with kernel enforcement:

Log files → daemon file watchers → parser pipeline → scorer → ban → kernel set

Detection pipeline

  1. File watchers bind to authentication log paths at daemon startup
  2. Parsers extract failed login events from each log source:
    • SSH (/var/log/secure or /var/log/auth.log)
    • Dovecot (/var/log/maillog)
    • Exim (/var/log/exim/mainlog)
    • DirectAdmin (/var/log/directadmin/login.log, security.log)
    • FTP (via distroconf resolution)
  3. Scorer aggregates per-IP failure counts in a time window
  4. Ban decision: when score threshold is exceeded, the daemon adds the IP to blacklist_manual_ipv4/ipv6 with a timeout
  5. Kernel enforcement: the manual blacklist set is checked at pipeline phase 3 (Ban Enforcement) by the input_blacklist_manual_drop rule

Log path resolution

LoginMon uses distroconf to resolve log paths per distribution:

Source Typical path Resolver
SSH /var/log/secure (EL) or /var/log/auth.log (Debian) distroconf
Exim /var/log/exim/mainlog distroconf
Dovecot /var/log/maillog distroconf
DirectAdmin /var/log/directadmin/login.log distroconf

Path resolution is logged at daemon startup: [LOGINMON] <source>: <path> resolved_by=distroconf. A resolved_by=fallback or warning=hardcoded_probe indicates degraded path resolution.


Kernel Objects

No dedicated kernel objects

LoginMon does not have its own chains, sets, or counters. It produces bans that land in the shared manual blacklist infrastructure:

Object Owner Purpose
blacklist_manual_ipv4/ipv6 Base schema (always present) Ban enforcement set
input_blacklist_manual_drop Base schema (always present) Drop counter for manual blacklist matches

These objects are part of the base nftables schema — they exist regardless of whether LoginMon is enabled. LoginMon writes to them; it does not create them.

Shared counter attribution problem

input_blacklist_manual_drop counts drops from ALL sources that write to the manual blacklist:

  • LoginMon auto-bans (auth brute-force detection)
  • Operator manual bans (nftban ban <ip>)
  • Portscan detector bans

Kernel counters confirm enforcement at the blacklist family level. Attribution to LoginMon requires journal evidence ([EVENT] banned with source field). The kernel counter alone cannot attribute drops to LoginMon specifically.

Evidence hierarchy for this module

Priority Source What it proves
1 (primary) Journal events ([EVENT] banned, [EVENT] login_failed) LoginMon-specific detection and enforcement
2 (secondary) Kernel set presence (blacklist_manual_ipv4 elements) Bans exist (shared, not attributable)
3 (tertiary) Kernel counter (input_blacklist_manual_drop > 0) Family enforcement occurring (non-attributable)

Configuration

Key File Default Meaning
NFTBAN_LOGIN_ALERT_ENABLED conf.d/login_alert.conf "false" Master enable/disable
NFTBAN_LOGIN_ALERT_SSH conf.d/login_alert.conf "true" Enable SSH detection

Override via login_alert.conf.local (survives upgrades).

Per-source configuration controls which parsers activate. Sources are enabled individually (SSH, Dovecot, Exim, DirectAdmin, FTP).


State Model (v1.81)

This module follows the 4-axis model:

  • Config: ENABLED / DISABLED
  • Structural: PRESENT (inherited from base manual blacklist infrastructure; LoginMon has no dedicated kernel objects)
  • Runtime: RUNNING / STOPPED / ERROR (daemon required for all module operation)
  • Effective: ENFORCING / OBSERVING / IDLE (from journal evidence)

Effective state derivation

Evidence Effective state
[EVENT] banned entries in daemon journal ENFORCING (ban issued)
[EVENT] login_failed entries in journal OBSERVING (detecting failures, no ban yet)
No journal events in observation window IDLE (NEUTRAL — no auth attacks, valid state)

Validator limitation: The Go validator is a point-in-time snapshot tool. Journal event queries for ENFORCING/OBSERVING evidence are outside the current validator scope. The validator reports effective state as IDLE because it does not evaluate journal evidence. This is a limitation of the validator, not a statement about actual module activity. Real effective state is observable via nftban login status and journal queries.

Truth table

Config Structural Runtime Effective System Contribution
DISABLED skip
ENABLED PRESENT RUNNING ENFORCING PROTECTED
ENABLED PRESENT RUNNING OBSERVING PROTECTED
ENABLED PRESENT RUNNING IDLE IDLE
ENABLED PRESENT STOPPED DEGRADED (daemon required)
ENABLED PRESENT RUNNING + binding failure DEGRADED (consistency: source path missing)

Note on binding failure: Source binding status is a consistency/integrity check, not a runtime axis value. The daemon is RUNNING but one or more log sources could not be resolved. This is a separate finding from daemon state.

Daemon stopped behavior

When nftband stops:

  • Existing bans persist — entries in blacklist_manual_ipv4/ipv6 have kernel-managed timeouts and continue enforcing
  • No new bans possible — detection, scoring, and ban issuance halt entirely
  • System is DEGRADED, not DOWN — kernel enforcement of existing bans continues

Source binding failure

If a parser cannot find its log path:

  • resolved_by=fallback → RUNTIME_INTEGRITY warning (using non-preferred path)
  • resolved_by=hardcoded_probe → RUNTIME_INTEGRITY warning (guessing)
  • Path entirely missing → source is MISSING (DEGRADED for that source)

CLI Commands

nftban login status          # Show module state, sources, event counts
nftban login enable          # Enable login monitoring
nftban login disable         # Disable login monitoring
nftban login restart         # Restart the loginmon pipeline

Verification (MANDATORY)

# Check if daemon is running (runtime axis — required)
systemctl is-active nftband
# Expected: "active". If not → LoginMon is non-functional.

# Check source bindings
journalctl -u nftband | grep "LOGINMON.*resolved_by" | tail -5
# Expected: "resolved_by=distroconf" for each source
# "resolved_by=fallback" = degraded path resolution

# Check for recent detection events
journalctl -u nftband --since "1 hour ago" | grep "login_failed" | tail -5
# Events present = OBSERVING. No events = NEUTRAL (not failure).

# Check for recent bans
journalctl -u nftband --since "1 hour ago" | grep "EVENT.*banned" | tail -5
# Events present = ENFORCING. No events = NEUTRAL.

# Check shared manual blacklist set
nft list set ip nftban blacklist_manual_ipv4
# Elements > 0 may include LoginMon bans + operator bans + portscan bans
# Attribution requires journal evidence, not kernel alone

# Check shared counter
nft list counter ip nftban input_blacklist_manual_drop
# Counter > 0 = manual-blacklist-family enforcement (not LoginMon-specific)
# Counter = 0 = NEUTRAL

# Check module state via validator
nftban-validate --json | jq '.modules.loginmon'
# Expected: {"config":"enabled","structural":"present","runtime":"running","effective":"idle"}
# Note: effective is "idle" by default — validator does not query journal

Failure Modes

DEGRADED: Daemon STOPPED

Symptom: Validator reports runtime: "stopped". Impact: No new auth failure detection or banning. Existing bans persist. Fix: systemctl start nftband

DEGRADED: Source binding failure

Symptom: Journal shows resolved_by=fallback or source path missing. Impact: Some auth sources not monitored. Attackers targeting unmonitored services (e.g., Dovecot) will not be detected. Fix: Verify log paths match distribution defaults. Check distroconf resolution. May need manual path configuration.

Low event volume (not a failure)

1 login_failed event per hour on a low-traffic host is normal. This represents OBSERVING (activity detected), not IDLE. IDLE means zero events in the observation window.

Pipeline source reset after daemon restart

After daemon restart, pipeline source counters reset to 0. This is a transient IDLE state, not DEGRADED. Events begin accumulating as new auth failures occur.


Limitations

  • No dedicated kernel objects. LoginMon structural presence is inferred from base schema sets (always present) + daemon runtime + source bindings. The validator cannot verify LoginMon-specific kernel structure independently.
  • Shared enforcement counter. input_blacklist_manual_drop is shared with operator manual bans and portscan bans. LoginMon-specific enforcement proof requires daemon journal evidence ([EVENT] banned with source attribution).
  • Validator effective state is always IDLE. The validator uses point-in-time kernel snapshots. Journal queries for login events are outside its current scope. Real effective state is visible via nftban login status and journal queries.
  • Daemon required for everything. Unlike DDoS (kernel-only) or Portscan (kernel logging without daemon), LoginMon cannot function at all without nftband. File watchers, parsing, scoring, and ban issuance all require the daemon.
  • Log path dependency. If the authentication log file is rotated, moved, or its format changes, LoginMon loses visibility. The distroconf resolver handles standard distributions, but custom log configurations may require manual path setup.

Clone this wiki locally