Selectively disabling password authentication with OpenSSH



You have a server you'd like to manage remotely, so you open up a port in the router to log in directly. However, you don't want your server exposed, so you set up your SSH daemon to only accept keys, not passwords. Yet - dilemma - for local access you still want to be able to log in with a plain password if need be. Some people might be thinking multiple SSH instances now - but you don't need that. OpenSSH's conditional blocks allow exactly for this.

Setting up the SSH server

Here's how it works. On Debian, password authentication is enabled by default:

$ grep PasswordAuthentication /etc/ssh/sshd_config 
#PasswordAuthentication no

Note that you can tighten security, but not loosen it; it won't work to block password authentication system-wide, yet enable it for a match.

Now, a conditional block starts with Match. The SSH server will consider everything that follows part of the block, until it meets another Match definition. Either you put the block at the very end of your configuration file, or you add an empty block after your first one.

The easiest way to filter remote from local logins is by defining the subnet. The Match directive is a bit peculiar if you define a negation. First you need to specify a wildcard, then the address or range to be excluded:

Match Address *,!192.168.1.0/24
  AllowUsers admin
  PasswordAuthentication no
  X11Forwarding no
  AllowTcpForwarding no

This stanza will effectively disable remote password authentication, and only allow the admin user to log in.

Testing

To exclude any syntax errors, test the configuration:

$ sudo sshd -t
$

No feedback means no errors, so that's good. Let's see if SSH behaves as expected, first with a key:

$ ssh -i .ssh/id_rsa-danaides -p 768 admin@eros.danaides.net -v
OpenSSH_6.7p1 Debian-5, OpenSSL 1.0.1k 8 Jan 2015
debug1: Reading configuration data /home/admin/.ssh/config
debug1: Reading configuration data /etc/ssh/ssh_config
[...]
debug1: Host '[eros.danaides.net]:768' is known and matches the ECDSA host key.
debug1: Found key in /home/admin/.ssh/known_hosts:42
[...]
debug1: Authentications that can continue: publickey
debug1: Next authentication method: publickey
debug1: Offering RSA public key: .ssh/id_rsa-danaides
debug1: Server accepts key: pkalg ssh-rsa blen 535
debug1: Authentication succeeded (publickey).
Authenticated to eros.danaides.net ([91.29.xxx.xx]:768).
[...]
admin@danaides:~$

As you can see, authenticating with the private key works neatly. Now we try without:

$ ssh -p 768 admin@eros.danaides.net -v
OpenSSH_6.7p1 Debian-5, OpenSSL 1.0.1k 8 Jan 2015
debug1: Reading configuration data /etc/ssh/ssh_config
debug1: /etc/ssh/ssh_config line 19: Applying options for *
debug1: Connecting to eros.no-ip.net [91.29.xxx.xx] port 768.
debug1: Connection established.
[...]
debug1: Authentications that can continue: publickey
debug1: Next authentication method: publickey
[...]
debug1: No more authentication methods to try.
Permission denied (publickey).

VoilĂ . As you can see, OpenSSH denies acces to the remote host because it tries to log in with a plain password. So no need for separate SSH instances, extra init and configuration files, or other complex solutions!

Logging

To make SSH log to /var/log/auth.log, which registers authentication attempts, change the logging level in /etc/ssh/sshd_config:

LogLevel VERBOSE

Blocking repeated login attempts

With key authentication mandatory, security is beefier than with regular passwords and a lot harder to crack. However, you might want to limit the amount of login attempts from script kiddies or persistent crackers. People routinely turn to Denyhosts or Fail2ban for this. Denyhosts is unmaintained upstream, has already several security issues, and has been dropped by Debian as well. That leaves Fail2ban, which is more of an all-round intrusion prevention framework that supports SSH - among others. If all you want is just some basic blacklisting, iptables does the job as well. I have added the following to /etc/iptables.rules:

-N SSH
-A INPUT ! -s 10.0.0.0/24 -p tcp --dport 22 -m state --state NEW -j SSH
-A SSH -m recent --set --name Blacklist --rsource
-A SSH -m recent --update --seconds 1800 --hitcount 11 --name Blacklist --rsource -j LOG --log-prefix "Login attempt: " --log-level 5
-A SSH -m recent --update --seconds 1800 --hitcount 11 --name Blacklist --rsource -j DROP
-A SSH -j ACCEPT

This accomplishes the following:

  • Filter all remote connection attempts.
  • Allow 10 attempts, then block any further attempts for 30 minutes. Any new attempts within that 30 minute window will reset the blacklist window.
  • Drop all further attempts and log them.

Keep in mind this does not differentiate between failed and successful login attempts. So if you e.g. are scp'ing a lot of stuff back and forth, you need something else, but for the occasional SSH connection, this is just fine.