Cannot correlate authentication failure logs (username only) with defender ban logs (IP only)
Is your feature request related to a problem? Please describe.
When investigating a ban, there is no way to tell which username's login attempts caused it, because the two relevant log records have disjoint identifying fields.
The authentication failure warning carries the username but no client IP — internal/dataprovider/sqlcommon.go, bolt.go, memory.go:
providerLog(logger.LevelWarn, "error authenticating user %q: %v", username, err)The defender ban record carries the client IP but no username — internal/common/defender.go#L167:
func (d *baseDefender) logBan(ip, protocol string) {
logger.GetLogger().Info().
Str("sender", "defender").
Str("client_ip", ip).
Str("protocol", protocol).
Str("event", "banned").
Send()
}The same is true of logEvent, which records the score increases leading up to the ban: client_ip, protocol, event, score — no username. The defender subsystem never receives a username at all; AddDefenderEvent(ip, protocol, event) is keyed purely on IP.
So the two halves of the story cannot be joined:
WARN sender=dataprovider error authenticating user "backup-svc": no such user <- who, not where
INFO sender=defender client_ip=203.0.113.5 event=banned <- where, not whoWhy connection_failed does not close the gap
logger.ConnectionFailedLog does emit both username and client_ip, and is explicitly intended for fail2ban-style integration. But it is logged at Debug level, while the two records above are at Warn and Info.
Any deployment that raises the log level above debug — common once SFTPGo is handling real traffic volume — keeps the un-correlatable pair and loses the only record that joins them. The correlation ability silently disappears exactly when log volume forces you to turn the level up.
There is also an internal inconsistency worth noting on its own: connection_failed, login, and the defender records all include client_ip, but the sender=dataprovider authentication warning does not. Anyone alerting on that warning has a username with no origin.
Describe the solution you'd like
Log the source IP alongside the username in the authentication failure messages, so a failed-login record is self-sufficient and joins to the defender records on client_ip:
error authenticating user "backup-svc" from IP "203.0.113.5": no such userThat makes the trail complete at any log level:
WARN error authenticating user "backup-svc" from IP "203.0.113.5"
INFO sender=defender client_ip=203.0.113.5 event=bannedand answers the operational question directly: did this service account's misconfigured credentials get its host banned, or is this an unrelated attacker?
I have opened #2286 implementing this. The password-based paths already had ip in scope. The TLS-certificate and public-key paths did not receive it at the provider layer even though CheckUserAndTLSCert and CheckUserAndPubKey already hold it, so the PR threads the parameter through the Provider interface and its five implementations. No public API or configuration change.
Describe alternatives you've considered
- Raise
ConnectionFailedLogto Warn — one-line change, but it broadens what is emitted at Warn for reasons beyond this problem (it also fires on client aborts and login timeouts), and it leaves thesender=dataproviderwarning inconsistent with every other auth-related record. - Pass the username into the defender so bans name the offender — arguably the more complete fix, but a much larger change: the defender is deliberately IP-keyed, a single ban can follow attempts against many usernames, and it would mean reworking
AddDefenderEventand both defender backends. - Correlate externally by timestamp — unreliable under concurrency, and impossible when several sources fail against different usernames in the same window.
Adding the IP to the existing warning seemed the smallest change that makes each record independently actionable, which is why I went that way in #2286 — happy to take a different direction if you prefer one of the above.
Additional context
Relevant code:
internal/common/defender.go#L148—logEvent, IP onlyinternal/common/defender.go#L167—logBan, IP onlyinternal/logger/logger.go#L261—ConnectionFailedLog, both fields, Debug levelinternal/dataprovider/{sqlcommon,bolt,memory}.go— auth warnings, username only
Source: drakkan/sftpgo