UNIX / Linux keyboard.

How to Set Up and Use SSH
Step 2 — Access Control

SSH Access Control

If you are reachable from the Internet, attackers using hosts all over the world will be making SSH connections and trying to guess passwords.

You might want to consider some access control, in which hosts or blocks of IP addresses in specific lists are blocked out or are the only ones allowed to connect. Be aware that this can only provide limited protection, the recommendations on the previous page for using cryptographic authentication and disabling root access are crucial first lines of defense.

If you keep your server patched and configured carefully, and you have strong passwords and SSH key pass phrases on all accounts, you can feel reasonably comfortable. You might want to use access control just to more quickly frustrate the attackers. They will move on to what appear to be weaker targets and they won't clutter up your logs. However, if your primary interest is in reducing the number of attempted captured in your logs, you will find that most forms of access control will not accomplish that. Packet filtering is the only method that won't show up in your logs, unless, of course, you specifically configure it to do so.

SSH access control applies in the sequence shown in the following diagram. Notice the multiple opportunities for Syslog messages: from the kernel, from the SSH daemon (at multiple times during the connection or attempted connection), from the PAM modules, and from the shell or command executed through the SSH connection.

SSH access control flow of events.
  1. Packet filtering within IP module of kernel (e.g., iptables, IP Filter, pf, etc)
    • This is obviously the first step — packet filtering happens in the kernel before the datagram can be passed to the user-space service.
  2. TCP Wrapper control with /etc/hosts.allow and /etc/hosts.deny
    • Denied users will not be prompted for a password, there will be a timeout followed by a message:
      ssh_exchange_identification: Connection closed by remote host
  3. PAM and Allow/Deny rules in /etc/ssh/sshd_config
    • The point of PAM is to control what users have to do to authenticate. Type UNIX passwords? Offer Kerberos tickets? In what order and combination?
    • The point of the Allow/Deny rules for both users and groups in sshd_config is to control which users and/or group members are allowed to authenticate, and possibly from which client hosts.
    • It doesn't really matter whether the PAM rules are applied before the Allow/Deny rules or vice-versa, because unless you specified something illogical, the result is the same — multiple ways to deny access.
    • The denied user will be prompted multiple times for a password, but even if they know the password they will be refused with a vague message similar to:
      Permission denied (publickey,password,keyboard-interactive).

The syslog messages on the server should indicate why the connection was refused. Note that a single connection may generate multiple syslog messages, one from the SSH daemon and one from each PAM module it uses. Access control rules lead to more log messages, not less.

Packet filtering

This is a big topic. For Linux, see the Netfilter/iptables documents at netfilter.org.

You must be very careful with packet filtering rules, as you can easily make your own server inaccessible. But as an example of something you might find useful, as long as you don't want to do certain legitimate things, you could add these two iptables rules to Linux, placing them near the top of the INPUT chain. The rules use two modules, state and recent, the second of which is used for connection rate limiting:

# The "recent" module dynamically creates a list of IP addresses.
# This rule creates a list named "SSH" containing those clients
# that have sent a TCP/SYN packet to the SSH port.
iptables -A INPUT -p tcp --dport ssh \
	-m state --state NEW -m recent --name SSH --set

# This rule specifies that if the client has attempted to establish
# at least 5 SSH connection attempts within the past 120 seconds, the
# packet is rejected with a TCP/RST packet, which is what happens
# when you try to connect to a host not running that service at all.
iptables -A INPUT -p tcp --dport ssh \
	-m state --state NEW -m recent --name SSH \
	--rcheck --seconds 120 --hitcount 5 \
	--jump REJECT --reject-with tcp-reset

See the ipt_recent module web page for more details.

A big problem with automatically adding packet filtering rules is that you may keep yourself from doing something that you want to do. Packet filtering works down at the IP layer, and while it is aware of TCP/UDP things like ports and flags and even connection state, it has no clue about application-layer issues like SSH authentication failures. The above two rules would prevent anyone from making more than 5 connections every 120 seconds. Some times I do something like the following when I plan to edit a number of files, let's say anywhere under my home directory and within the collection of web pages. I expect this to take some time, and some interruptions will occur, so I can't reliably keep track of the list of files that need to be copied to the server when I'm done. No problem, I just do this:

% touch /tmp/TIMESTAMP

  .... edit lots of files under /home/cromwell and /var/www ....

% foreach F ( `find /home/cromwell /var/www -type f -newer /tmp/TIMESTAMP` )
> scp $F server:$F
> end

  .... or, if you use Bash or ksh for your interactive shell ....

$ for F in `find /home/cromwell /var/www -type f -newer /tmp/TIMESTAMP`
> do
>    scp $F server:$F
> done

I would not be allowed to do that if the connection rate throttling were in place! Yes, I know, I probably should make a tar file, then copy it over and extract it, but I prefer to do it the above way. Some times many files have been changed, so many SSH connections would be made for all those scp commands.

Other rules could be inserted before those rules to allow arbitrarily many fast connections from specified IP address blocks. This would be fine to allow connections within an organization, but it could not be used for users working from home or on the road (hotels, Internet cafes) as you could not predict their IP addresses.

# Allow many fast connections from inside, where we use the RFC 1918
# IP address blocks.  These rules MUST appear BEFORE filtering rules.
iptables -A INPUT -p tcp --dport ssh -s     --jump ACCEPT
iptables -A INPUT -p tcp --dport ssh -s  --jump ACCEPT
iptables -A INPUT -p tcp --dport ssh -s --jump ACCEPT

# Now throttle everyone else with those two rules shown earlier:
iptables -A INPUT -p tcp --dport ssh \
	-m state --state NEW -m recent --name SSH --set
iptables -A INPUT -p tcp --dport ssh \
	-m state --state NEW -m recent --name SSH \
	--rcheck --seconds 120 --hitcount 5 \
	--jump REJECT --reject-with tcp-reset

See the ipt_recent module web page for more details.

TCP Wrapper by example

To allow SSH access for only one block of IP addresses while allowing all connection requests on all other network services:

# cat /etc/hosts.allow
sshd : 192.168. 
# cat /etc/hosts.deny
sshd : ALL  

To allow SSH access for only one block of IP addresses, but allow no other connection requests for network services aware of TCP Wrapper access control:

# cat /etc/hosts.allow
sshd : 192.168.
# cat /etc/hosts.deny

To allow only specific user@host or user@IP-block SSH access and deny all other network services:

# cat /etc/hosts.allow
sshd : joe@ , jane@host1.example.com , jane@host2.example.com
# cat /etc/hosts.deny


Denyhosts is a script to watch the sshd logs and automatically add attacking hosts to a black-list maintained through entries added to the TCP Wrapper control file /etc/hosts.deny.

A big advantage of Denyhosts over the packet filtering techniques shown above is that it uses authentication failures, as shown by the logs, instead of simple TCP connection rates. This means that while it is more complex, you are less likely to unintentionally lock yourself out.

You can run Denyhosts as a daemon, constantly monitoring the Syslog contents to add a host to the black-list as soon as it reaches the threshold of SSH authentication failures per unit time.

Alternatively, you can run Denyhosts every so often via cron.

Get Denyhosts and far more information about it from http://denyhosts.sourceforge.net/

Automatic Black-List with PAM

The pam_abl PAM module can automatically add a password-guessing host to a black-list.

You will have to experiment to verify whether your SSH daemon uses PAM by default. Hint: see if the syslog messages from sshd are associated with any from PAM rules. If the SSH daemon does not already use PAM by default, you should be able to add the following line to your /etc/ssh/sshd_config configuration file.

However, check the manual pages for sshd and sshd_config and also experiment to verify what's really going on with SSH and PAM:


Next, add a line like the following in the file /etc/pam.d/sshd before the existing auth lines:

auth   required   pam_abl.so config=/etc/security/pam_abl.conf 

Then add something like the following to that configuration file /etc/security/pam_abl.conf. Change the numbers as you see fit:

# Black-list any remote host with 10 consecutive authentication failures
# in one hour, or 30 in one day.  Keep them in the black-list for two days
# and then purge them.
# Black-list any local user other than root for which there are 10
# consecutive authentication failures in one hour, or 30 in one day.
# Keep them in the black-list for two days and then purge them.
# Note that this means that non-root users may be subjected to denial of
# service attacks caused by remote password guessing.

Allow/Deny rules in /etc/ssh/sshd_config

See the sshd_config manual page for the details on this. The short version is that you add lines like the following to sshd_config and then send a HUP signal to the daemon. Once you add one AllowUsers rule, then the only users allowed to login via SSH are the listed ones:

AllowUsers joe
AllowUsers jane 

You can also do this by client hosts:

AllowUsers *@host1.example.com
AllowUsers *@host2.example.com 

Or even by domains:

AllowUsers *@*.example.com