Securing Ubuntu 18.04 ssh server with ufw and fail2ban

Install

sudo apt-get install -y openssh-server

sshd configuration

sudo vim /etc/ssh/sshd_config

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# change default port to keep away from the brute force port scanner
Port 1234

# Disable Root
# By default Ubuntu 18.04 Bionic Beaver installation comes with unset root password
# By default SSH root login is disabled[1]
# PermitRootLogin no

# limit the brute force attack rate[2][3][4]
MaxAuthTries 3

# Disconnect Idle Sessions(in seconds)
# the server will check on the client after 5 minutes of inactivity. It will do this twice then disconnect.
ClientAliveInterval 300
ClientAliveCountMax 2

# Disable Password Authentication and only use key
PubkeyAuthentication yes
PasswordAuthentication no
PermitEmptyPasswords no

sudo systemctl reload sshd

UFW

1
2
3
sudo ufw allow OpenSSH
# sudo ufw disable
# sudo ufw enable

fail2ban[5][6][7]

1
2
sudo apt-get install fail2ban
cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local

Note: “Fail2ban reads .conf configuration files first, then .local files overriding any settings. Because of this, all changes to the configuration are generally done in .local files, leaving the .conf files untouched.”

sudo vim /etc/fail2ban/jail.local

1
2
3
4
5
6
7
8
9
[DEFAULT]
banaction = ufw

[sshd]
enabled = true

maxretry = 3
findtime = 3600
bantime = 3600

1
2
3
4
sudo systemctl service enable fail2ban
sudo systemctl service start fail2ban
# fail2ban-client restart
# fail2ban-client status

Slack webhook notifications[8][9]

I use bot-user here so I can select different channel with same token. That means I can use different channel for each application with one bot-user. For now, I have shadowsocks and ssh to supervise. So the below is the same as Ubuntu-18-04-set-up-Shadowsocks-server-with-fail2ban except Slack channel.

Of course you can use webhook[10], which could be used only for one channel. In other word, if you want multiple channels, you have to create one webhook for each channel and then modify the related curl argument.

Generate a bot-user

just follow the official instructions

create action

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
sudo tee /etc/fail2ban/action.d/slack-notify.conf > /dev/null <<EOF
#
# Author: Yue (Zed) Yang
#

[Definition]

# Option: actionstart
# Notes.: command executed once at the start of Fail2Ban.
# Values: CMD
#
actionstart = curl -s -o /dev/null 'https://slack.com/api/chat.postMessage' -d 'token=<slack_api_token>' -d 'channel=#<slack_channel>' -d 'text=Fail2Ban (<name>) jail has started'

# Option: actionstop
# Notes.: command executed once at the end of Fail2Ban
# Values: CMD
#
actionstop = curl -s -o /dev/null 'https://slack.com/api/chat.postMessage' -d 'token=<slack_api_token>' -d 'channel=#<slack_channel>' -d 'text=Fail2Ban (<name>) jail has stopped'

# Option: actioncheck
# Notes.: command executed once before each actionban command
# Values: CMD
#
actioncheck =

# Option: actionban
# Notes.: command executed when banning an IP. Take care that the
# command is executed with Fail2Ban user rights.
# Tags: <ip> IP address
# <failures> number of failures
# <time> unix timestamp of the ban time
# Values: CMD
#

actionban = curl -s -o /dev/null 'https://slack.com/api/chat.postMessage' -d 'token=<slack_api_token>' -d 'channel=#<slack_channel>' -d 'text=Fail2Ban (<name>) banned IP *<ip>* for <failures> failure(s)'

# Option: actionunban
# Notes.: command executed when unbanning an IP. Take care that the
# command is executed with Fail2Ban user rights.
# Tags: <ip> IP address
# <failures> number of failures
# <time> unix timestamp of the ban time
# Values: CMD
#
actionunban = curl -s -o /dev/null 'https://slack.com/api/chat.postMessage' -d 'token=<slack_api_token>' -d 'channel=#<slack_channel>' -d 'text=Fail2Ban (<name>) unbanned IP *<ip>*'
EOF

update jail config

sudo vim /etc/fail2ban/jail.local

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#
# ACTIONS
#

slack_api_token = <slack_api_token>
slack_channel = general

action_with_slack_notification = %(banaction)s[name=%(__name__)s, bantime="%(bantime)s", port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"]
slack-notify[name=%(__name__)s, slack_api_token=%(slack_api_token), slack_channel=%(slack_channel)]

action = %(action_with_slack_notification)s

[sshd]
slack_channel = supervise-ssh

Just fill your token and channel

restart fail2ban

1
2
sudo systemctl restart fail2ban
sudo systemctl status fail2ban

Reference

[1] https://linuxconfig.org/allow-ssh-root-login-on-ubuntu-18-04-bionic-beaver-linux
[2] https://ubuntuforums.org/showthread.php?t=1253241
[3] https://superuser.com/questions/1148293/what-is-the-default-login-attempt-rate-limit
[4] https://unix.stackexchange.com/questions/26170/sshd-config-maxsessions-parameter
[5] https://www.tricksofthetrades.net/2018/05/18/fail2ban-installing-bionic/
[6] https://medium.com/@jasonrigden/hardening-ssh-1bcb99cd4cef
[7] https://www.fail2ban.org/wiki/index.php/MANUAL_0_8
[8] https://github.com/coleturner/fail2ban-slack-action
[9] https://api.slack.com/methods/chat.postMessage
[10] https://api.slack.com/incoming-webhooks
[11] https://github.com/shadowsocks/shadowsocks/issues/961