How to protect your server on Ubuntu with Fail2Ban with email alerts and GeoIP filter
Introduction
This tutorial describes the shortcut to gaining an essential security level for your fresh Ubuntu server. The reason why you should do so is that almost every IP address in the world is being brute-forced 24/7 by large botnets and amount of bots and sophistication of attacks are increasing dramatically. Nowadays the cybersecurity is paramount.
Contents
This tutorial includes three parts:
- Disabling root login remotely (essential)
- Add cryptographic key authentication (recommended)
- Limit your SSH logins using GeoIP (optional)
Let’s start with disabling root login remotely. This requires to log in, create a new user which will be used for remote login and configuring ssh service.
Note: If you don’t know how to copy/paste in the terminal, use the usual commands (CTRL+V, CTRL+C
on Linux and Windows) with SHIFT
pressed. Read this answer on StackOverflow for more information.
Disabling root login remotely (essential)
Login to your server
This command will help you to log in to your server as aroot
user. Replace the server with your server IP address or domain name.
ssh root@server
As a result, you now must be connected to the server and logged in as root. You must see something like this:
Welcome to Ubuntu 16.04.3 LTS (GNU/Linux 4.4.0-042stab120.18 x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
Last login: Wed Sep 13 03:00:23 2017 from 12.345.67.890
[root@server ~]#
Create a new user
Then add a new user to the system which will be used instead of root for login. Replace the username in the examples below with a name that you like. After executing the command adduser
you will be asked for a new user’s password. Enter a strong password and, optionally, fill in any of the additional information if you would like. This is not required and you can just hit ENTER
in any field you wish to skip.
adduser username
Now, we have a new user account with regular account privileges. However, we may sometimes need to do administrative tasks.
To avoid having to log out of our normal user and log back in as the root account, we can set up what is known as “superuser” or root privileges for our normal account. This will allow our normal user to run commands with administrative privileges by putting the word sudo
before each command.
usermod -aG sudo username
Now your user can run commands with superuser privileges! For more information about how this works, check out this sudoers tutorial.
To disable root SSH login, edit /etc/ssh/sshd_config
with your favorite text editor.
The most common terminal text editor amongst developers is Vim. It’s a bit complicated but can save you a lot of time. So I provided a few tips for you who didn’t use it before.
Run the following command (vi is an alias for vim):
vi /etc/ssh/sshd_config
As you press enter you will see the Vim text editor. What you should know about Vim is that:
- Vim has several modes available. Just keep in mind that there are command mode and other modes.
- You can use only command and input modes to do basic text editing.
- By default, Vim is in command mode. To return to command mode from any press
ESC
. - Vim has its commands. Most commands start with colon (
:
key). - To exit Vim run
:q
command. - To edit a file you should use input mode. Press
i
key to enter the mode from the command mode. - To write to a file use command
:w
. - You can combine commands, i.e., fire
:wq
and you will save the file on exit. - To search for a string hit
/
in command mode and then input the string hit enter.
Vim command reference
save: :w
save and exit: :wq
exit: :q
force: !
(example :w!
, :q!
)
vertical split: open a document and then type :vsplit /path-to-document/document
and this will open the specified document and split the screen so you can see both documents.
copy: y
copy a line: yy
paste: p
cut: d
cut a line: dd
Here is vim tutorial. You can find a lot of helpful video tutorials on youtube.
Enter the input mode, edit the line, and save on exit.
Change this line:
#PermitRootLogin yes
Edit to this:
PermitRootLogin no
Ensure that you are logged into the box with another shell before restarting sshd to avoid locking yourself out of the server.
[root@server ~]# /etc/init.d/sshd restart
Stopping sshd: [ OK ]
Starting sshd: [ OK ]
[root@server ~]#
That’s it. Now you can logout using exit
command and try to login with new user credentials.
If you wish your server to be more secured, follow the next step of the tutorial.
Add cryptographic key authentication (recommended)
If you already have a key that you want to use, skip to the Copy the Public Key step.
If you do not already have an SSH key pair, which consists of a public and private key, you need to generate one. You will need this key to work with such services as bitbucket or github.
Generate key pair
Run this command on your local machine to generate the key pair.
ssh-keygen
You will be asked few questions after that the key pair will be generated.
Note: you will be asked for a passphrase, which if you provide one will be asked when login into the server. If not, then you won’t have to type the passphrase every time you log in.
This generates a private key, id_rsa, and a public key, id_rsa.pub, in the .ssh directory of the local user's home directory. Remember that the private key should not be shared with anyone who should not have access to your servers!
Copy the public key
Option 1: Use ssh-copy-id
If your local machine has the ssh-copy-id
script installed, you can use it to install your public key to any user that you have login credentials for.
Run the ssh-copy-id
script by specifying the user and IP address of the server that you want to install the key on, like this:
ssh-copy-id username@server
After providing your password at the prompt, your public key will be added to the remote user’s .ssh/authorized_keys
file. The corresponding private key can now be used to log into the server.
Option 2: Manually Install the Key
Assuming you generated an SSH key pair using the previous step, use the following command at the terminal of your local machine to print your public key (id_rsa.pub
):
cat ~/.ssh/id_rsa.pub
This should print your public SSH key, which should look something like the following:
id_rsa.pub contents
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDBGTO0tsVejssuaYR5R3Y/i73SppJAhme1dH7W2c47d4gOqB4izP0+fRLfvbz/tnXFz4iOP/H6eCV05hqUhF+KYRxt9Y8tVMrpDZR2l75o6+xSbUOMu6xN+uVF0T9XzKcxmzTmnV7Na5up3QM3DoSRYX/EP3utr2+zAqpJIfKPLdA74w7g56oYWI9blpnpzxkEd3edVJOivUkpZ4JoenWManvIaSdMTJXMy3MtlQhva+j9CgguyVbUkdzK9KKEuah+pFZvaugtebsU+bllPTB0nlXGIJk98Ie9ZtxuY3nCKneB+KjKiXrAvXUPCI9mWkYS/1rggpFmu3HbXBnWSUdf user@machine.local
Select the public key, and copy it to your clipboard.
To enable the use of the SSH key to authenticate as the new remote user, you must add the public key to a special file in the user’s home directory.
On the server, as the root user, enter the following command to temporarily switch to the new user (substitute your own user name):
su - username
Now you will be in your new user’s home directory.
Create a new directory called .ssh and restrict its permissions with the following commands:
mkdir ~/.ssh
chmod 700 ~/.ssh
Now open a file in .ssh
called authorized_keys
with a text editor. We will use vim
to edit the file:
vim ~/.ssh/authorized_keys
Hit i
, insert your public key (which should be in your clipboard) by pasting it into the editor (CTRL+SHIFT+V
) and save on exit hitting ESC
, then :wq
.
Now restrict the permissions of the authorized_keys file with this command:
chmod 600 ~/.ssh/authorized_keys
Type this command once to return to the root
user:
exit
Now your public key is installed, and you can use SSH keys to log in as your user.
Disable password authentication
To disable password authentication on your server, follow these steps.
As root or your new sudo user, open the SSH daemon configuration:
vim /etc/ssh/sshd_config
Find the line that specifies PasswordAuthentication, uncomment it by deleting the preceding #, then change its value to “no”. It should look like this after you have made the change:
PasswordAuthentication no
Here are two other settings that are important for key-only authentication and are set by default. If you haven’t modified this file before, you do not need to change these settings:
PubkeyAuthentication yes
ChallengeResponseAuthentication no
Type this to reload the SSH daemon:
systemctl reload sshd
Password authentication is now disabled. Your server is now only accessible with SSH key authentication.
Set up firewall
Ubuntu 16.04 servers can use the UFW firewall to make sure only connections to certain services are allowed. We can set up a basic firewall very easily using this application.
Different applications can register their profiles with UFW upon installation. These profiles allow UFW to manage these applications by name. OpenSSH, the service allowing us to connect to our server now, has a profile registered with UFW.
You can see this by typing:
sudo ufw app list
We need to make sure that the firewall allows SSH connections so that we can log back in next time. We can allow these connections by typing:
sudo ufw allow OpenSSH
Afterward, we can enable the firewall by typing:
sudo ufw enable
Type “y” and press ENTER to proceed. You can see that SSH connections are still allowed by typing:
sudo ufw status
If you install and configure additional services, you will need to adjust the firewall settings to allow acceptable traffic in. You can learn some common UFW operations in this guide.
Set up fail2ban
A service called fail2ban can mitigate this problem by creating rules that can automatically alter your iptables firewall configuration based on a predefined number of unsuccessful login attempts. This will allow your server to respond to illegitimate access attempts without intervention from you.
Install and configure fail2ban
Type the following commands to install fail2ban:
sudo apt-get update
sudo apt-get install fail2ban
The fail2ban service keeps its configuration files in the /etc/fail2ban
directory. There is a file with defaults called jail.conf
.
Since this file can be modified by package upgrades, we should not edit this file in-place, but rather copy it so that we can make our changes safely. For these two files to operate together successfully, it is best to only include the settings you wish to override in the jail.local
file. All default options will be taken from the jail.conf
file.
awk '{ printf "# "; print; }' /etc/fail2ban/jail.conf | sudo tee /etc/fail2ban/jail.local
You will want to evaluate the destemail
, sendername
and mta
settings if you wish to configure email alerts. The destemail
parameter sets the email address that should receive ban messages. The sendername
sets the value of the "From" field in the email. The mta
parameter configures what mail service will be used to send mail. Again, add these to the jail.local file, under the [DEFAULT] header and set to the proper values if you wish to adjust them.
Note: the mail service (mta
) must be configured properly to send emails. This tutorial can help you configure the sendmail
service. It is good practice to configure email alerts for most people do not check /var/log/auth.log
file even once a month.
[DEFAULT]
. . .
action = $(action_)s
. . .
This parameter configures the action that fail2ban takes when it wants to institute a ban. The value action_ is defined in the file shortly before this parameter. The default action is to simply configure the firewall to reject traffic from the offending host until the ban time elapses.
If you would like to configure email alerts, add or uncomment the action item to the jail.local file and change its value from action_ to action_mw. If you want the email to include the relevant log lines, you can change it to action_mwl. Make sure you have the appropriate mail settings configured if you choose to use mail alerts.
We’re going to configure an auto-banning policy for SSH and Nginx, just as we described above. We want fail2ban to email us when an IP is banned.
If you don’t already have it, you’ll need nginx, since we’re going to be monitoring its logs, and you’ll need sendmail to mail us notifications. We’ll also grab iptables-persistent to allow the server to automatically set up our firewall rules at boot. These can be acquired from Ubuntu’s default repositories:
sudo apt-get update
sudo apt-get install nginx sendmail iptables-persistent
Stop the fail2ban service for a moment so that we can establish a base firewall without the rules it adds:
sudo service fail2ban stop
When that is finished, we should implement a default firewall.
We’re going to tell it to allow established connections, traffic generated by the server itself, traffic destined for our SSH and web server ports. We will drop all other traffic. We can set this basic firewall up by typing:
sudo iptables -A INPUT -i lo -j ACCEPT
sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT
sudo iptables -A INPUT -p tcp -m multiport --dports 80,443 -j ACCEPT
sudo iptables -A INPUT -j DROP
These commands will implement the above policy. We can see our current firewall rules by typing:
sudo iptables -S
You can save the firewalls so that they survive a reboot by typing:
sudo dpkg-reconfigure iptables-persistent
Afterward, you can restart fail2ban to implement the wrapping rules:
sudo service fail2ban start
We can see our current firewall rules by typing:
sudo iptables -S
If you want to read more on how to set up a firewall with fail2ban go here.
The next step is optional.
Limit your SSH logins using GeoIP (optional)
As I mentioned before, you should take some actions to protect the server (primarily, against botnets, which scan almost all available ips and try to break into the server and take control). Most often you will see incoming brute-force attacks from Chinese bots.
Install
Install GeoIP using the following command:
sudo apt update
sudo apt install geoip-bin geoip-database
Make sure it works by doing a simple test:
geoiplookup 8.8.8.8
Create the script to filter requests and cut unneeded.
On Ubuntu, you can see messages from sshd service in /var/log/syslog
file.
Configure
Save this file in /usr/local/bin/sshfilter.sh
:
!/bin/bash
UPPERCASE space-separated country codes to ACCEPT
ALLOW_COUNTRIES="NZ AU"
if [ $# -ne 1 ]; then
echo "Usage: basename $0 " 1>&2
exit 0 # return true in case of config issue
fi
COUNTRY=/usr/bin/geoiplookup $1 | awk -F ": " '{ print $2 }' | awk -F "," '{ print $1 }' | head -n 1
[[ $COUNTRY = "IP Address not found" || $ALLOW_COUNTRIES =~ $COUNTRY ]] && RESPONSE="ALLOW" || RESPONSE="DENY"if [ $RESPONSE = "ALLOW" ] then exit 0 else logger "$RESPONSE sshd connection from $1 ($COUNTRY)" exit 1 fi
Edit the ALLOW_COUNTRIES to include a list of country codes you want your SSH server to accept connections from. Here you can find country codes.
The last thing we need to do is tell the ssh daemon (sshd) to deny all connections (by default) and to run this script to determine whether the connection should be allowed or not.
In /etc/hosts.deny add the line:
sshd: ALL
and in /etc/hosts.allow add the line
sshd: ALL: aclexec /usr/local/bin/sshfilter.sh %a
Testing
Obviously, you want to test that this is working before you are accidentally logged out of your system. On the terminal, I can do a test with the 8.8.8.8 which I happen to know is from the US, and in my case should be DENIED access. So:
/usr/local/bin/sshfilter.sh 8.8.8.8
The script will not return anything visible, however in /var/log/messages I have the result:
Jun 26 17:02:37 pi root: DENY sshd connection from 8.8.8.8 (US)
Updating GeoIP
To make sure you are up-to-date with your GeoIP (free) country database, you will need to write another script that you can run as a monthly root cron job.
Please note that if you just installed the GeoIP database, or you have never manually updated it (it does not auto-update), then you should run the following script manually after installing it! The default database that gets installed is several years old and very inaccurate.
#!/bin/bashcd /tmp
wget -q https://geolite.maxmind.com/download/geoip/database/GeoLiteCountry/GeoIP.dat.gz
if [ -f GeoIP.dat.gz ]
then
gzip -d GeoIP.dat.gz
rm -f /usr/share/GeoIP/GeoIP.dat
mv -f GeoIP.dat /usr/share/GeoIP/GeoIP.dat
else
echo "The GeoIP library could not be downloaded and updated"
fi