notesarchiveworkabout

Key-only SSH still accepts passwords if PAM is left on

Setting PasswordAuthentication no is not sufficient. With UsePAM yes and KbdInteractiveAuthentication left at default, sshd will still complete a keyboard-interactive password login.

The received wisdom is two lines in sshd_config:

PasswordAuthentication no
PermitRootLogin no

That closes the password authentication method. It does not close keyboard-interactive, which on most distributions is wired through PAM to the same pam_unix password check. If KbdInteractiveAuthentication is left at its default and UsePAM yes is set, which it is out of the box on Debian and Ubuntu, then a client that asks for keyboard-interactive can still authenticate with the account password.

Verify rather than assume. Ask the server what it will accept:

# Ask for the list of methods without attempting to log in.
ssh -o PreferredAuthentications=none -o StrictHostKeyChecking=no user@host 2>&1 | grep -i 'authentications'
# Permission denied (publickey,password,keyboard-interactive).

If keyboard-interactive appears in that list, the door is open.

The configuration that actually closes it

# /etc/ssh/sshd_config.d/10-hardening.conf
PasswordAuthentication no
KbdInteractiveAuthentication no
ChallengeResponseAuthentication no
PermitRootLogin prohibit-password
PermitEmptyPasswords no
AuthenticationMethods publickey
MaxAuthTries 3

AuthenticationMethods publickey is the belt-and-braces line: it states the complete set of methods that may satisfy authentication, so a method re-enabled somewhere else in the include chain still cannot be used on its own.

Two ordering details matter on modern OpenSSH. Drop-in files under sshd_config.d are read where the Include directive sits, near the top on Debian derivatives, and first value wins for most keywords. A setting you add at the bottom of the main file may lose to the drop-in. Check the effective config rather than reading the file:

# Print the config sshd will actually use, after includes and matches.
sshd -T | grep -Ei 'passwordauth|kbdinteractive|permitroot|authenticationmethods'

# Validate syntax before restarting; a typo here locks you out.
sshd -t && systemctl reload ssh

Keep a second session open while you reload. reload does not drop existing connections, which gives you a way back in if the new config rejects you.

Rate limiting is still worth having

Key-only auth makes brute force pointless, but the log noise is real and the connection churn is not free. fail2ban with the stock sshd jail is enough:

# /etc/fail2ban/jail.d/sshd.local
[sshd]
enabled  = true
backend  = systemd
maxretry = 4
findtime = 10m
bantime  = 1h
# Escalate repeat offenders rather than banning everyone for a week.
bantime.increment = true
bantime.factor    = 4

backend = systemd matters on any host that no longer writes /var/log/auth.log; with the default file backend the jail starts, finds nothing to read, and reports zero failures forever while the attempts continue.