SSH

Securing SSH with cryptographic keys.

Since our Debian server is headless, we probably access it through SSH, which stands for Secure SHell. Now it’s time to make it even more secure.

The best practice says you should set up SSH keys and disable the password login. This way the authentication is done with public-private cryptography. Which is pretty secure. And disabling password authentication makes it immune to the bruteforce login attempts.

To generate the private-public key pair, on your Linux (or a Mac) computer, open the terminal and type ssh-keygen -o -a 100 -t ed25519 -f ~/.ssh/<SERVER_HOSTNAME>, where <SERVER_HOSTNAME> is the server name. You can name it whatever you want.

Now, we copy the key to our server with the following command, where <SERVER_USERNAME> is the username of the user you’d be logging in to your server with, and <SERVER_IP> is your server’s IP address:

ssh-copy-id -i ~/.ssh/<SERVER_HOSTNAME>.pub <SERVER_USERNAME>@<SERVER_IP>

Now to access your server with the keys, we use: ssh -i ~/.ssh/<SERVER_HOSTNAME> <SERVER_USERNAME>@<SERVER_IP> .

Next step is to disable password authentication for SSH. To accomplish this, we edit the /etc/ssh/sshd_config file with a command sudo nano /etc/ssh/sshd_config. Now make sure the file has the following lines:

PasswordAuthentication no
UsePAM no
PermitRootLogin no

Or with a single command:

sudo sed -i'.bak' -e '/^PermitRootLogin/c PermitRootLogin no' -e '/^UsePAM/c UsePAM no' -e '/^#PasswordAuthentication/c PasswordAuthentication no' /etc/ssh/ssd_config

You can change the port SSH listens on to something different. To do so, uncomment this line #Port 22 (by removing the # symbol) and change the number to something else. I recommend using a port number in the range 49152–65535. This way, somebody trying port 22 won’t be able to access your server through SSH because SSH is listening on another port. This is what’s called security by obscurity. It is not a best practice as it doesn’t necessarily make your system more secure but may stop script kiddies from trying. But they should also be stopped by all the other actions we’re taking.

And now reload SSH with sudo systemctl reload ssh.

ufw – uncomplicated firewall

Now secure the server with a firewall. You can use iptables, but I prefer ufw, as it’s pretty uncomplicated, and it’s just a front end for iptables anyways. To install ufw we run

sudo apt update && sudo apt install ufw -y

Now allow SSH through the ufw firewall (where AAA.BBB.CCC.DDD is the ip you want to allow through ufw, or put any, if you want to open the port to any ip. You can use ssh in port if you kept it default, or change it to the value you change the SSH server listening port to). Make sure you do this step before enabling ufw as doing otherwise will lock you out and will have to figure out a way to console into your server. ufw blocks all incoming connections by default and without punching this hole first you won’t have any access from the network:

sudo ufw allow from AAA.BBB.CCC.DDD to any port ssh proto tcp
sudo ufw enable

We could stop here. But why? Let’s install fail2ban and ban those IPs trying to login to our secure server without our authorization.

fail2ban

Install fail2ban and make sure it’s enabled on start with:

sudo apt update && sudo apt install fail2ban -y
sudo systemctl enable --now fail2ban

After it’s done, let’s configure it. First, backup the config file

sudo cp /etc/fail2ban/jail.jail /etc/fail2ban/jail.local

And now edit it with sudo nano /etc/fail2ban/jail.local.

When you get to the line ignoreip = make sure you include your IP so you won’t be locked out.

Fort the ban times adjust the default parameters how you see fit:

# "bantime" is the number of seconds that a host is banned.
bantime  = 1d

# A host is banned if it has generated "maxretry" during the last "findtime"
# seconds.
findtime  = 30m

# "maxretry" is the number of failures before a host get banned.
maxretry = 3

Next makes sure backend says backend = systemd. This is required for the systems with systemd, such as Debian (since Debian 9 “stretch”).

Next scroll down to the # JAILS section and make sure your [sshd] is as follows:

[sshd]
enabled = true
port    = ssh
logpath = %(sshd_log)s
backend = %(sshd_backend)s

To permanently ban repeat offenders we edit the [recidive] section as such:

[recidive]
enable=true
logpath  = /var/log/fail2ban.log
banaction = %(banaction_allports)s
bantime  = -1
findtime = 1d
maxretry = 4

It permanently bans (bantime = -1) the IPs if they try and fail to access our server 4 times (maxretry = 4) within time period of 1 day (findtime = 1d).

To check the status of fail2ban and sshd jail use the commands:

sudo fail2ban-client status
sudo fail2ban-client status sshd

We could stop here. But why? Let’s enable 2-factor authentication with TOTP.1

Multi-factor Authentication with time-based one-time password

First, install the necessary packages.

sudo apt update && sudo apt install libpam-oath oathtool qrencode -y

Next, generate a long random HEX number:

dd if=/dev/random bs=1M count=1 status=none | sha256sum | cut -b 1-30

Now, edit the file /etc/users.oath with the user (<SERVER_USERNAME>) who should have 2FA enabled, use the number generated with the command above (18c34c49adb7fdacf67d53d0bb7339 in our example, make sure you use your output). You can have multiple users in this file, but each user needs to have its own line.

sudo nano /etc/users.oath
HOTP/T30        <SERVER_USERNAME>  -       18c34c49adb7fdacf67d53d0bb7339

Make sure to put the actual username in place of <SERVER_USERNAME>.

Let’s generate the QR code.

qrencode --type=ANSIUTF8 otpauth://totp/<SERVER_USERNAME>?secret=$( oathtool --verbose --totp 18c34c49adb7fdacf67d53d0bb7339 --digits=6 -w 1 | grep Base32 | cut -d ' ' -f 3 )\&digits=6\&issuer=<SERVER_HOSTNAME>\&period=30

A few comments and explanation of the command parameters:

<SERVER_USERNAME> – username of the user you’re enabling 2FA for

18c34c49adb7fdacf67d53d0bb7339 – string from above

--digits=6 – number of digits for the 2FA code. You can use 6,7, or 8, but even after about an hour of trying, I couldn’t make it work with anything other than 6. 6 is perfectly adequate.

issuer=<SERVER_HOSTNAME> – not required, but it’s useful to see in the Authenticator app.

Scan the QR code with your authenticator app on your phone.

Now edit /etc/ssh/sshd_config by running sudo nano /etc/ssh/sshd_config and change the lines to:

KbdInteractiveAuthentication yes
UsePAM yes
AuthenticationMethods publickey,keyboard-interactive
#ChallengeResponseAuthentication yes

Next, edit /etc/pam.d/sshd by running sudo nano /etc/pam.d/sshd. Comment out @include common-auth by putting a # symbol in front of it (otherwise SSH would ask you for the password in addition to the SSH keys and the second factor time code), and include auth requisite pam_oath.so usersfile=/etc/users.oath window=5 digits=6 below @include common-account.

Now restart sshd and you should be all set:

sudo systemctl restart sshd

It was a lot of text and a lot of steps. We installed a few applications and configured our server to use SSH keys, disabled password authentication, installed and enabled firewall, installed fail2ban to ban IPs trying to access our server, and added another layer of security by utilizing 2-factor authentication. It was a lot of work. There’s no simple wizard that would do it for us. But hey. It’s free, at least…

  1. I used this website for 2FA setup. But I needed to adjust it to make it work on Debian 12. ↩︎