Skip to content

Docker hosted openssh-server with fail2ban

This is how I setup and secured an openssh server which allows me access via ssh to my network while securing it against the usual attacks. I also log these attempts into InfluxDB for information. The firewall blocking done with fail2ban is done on my internet router instead of at the server.

You should be able to modify these instructions to work with your router or use the server's firewall.

Setup

My setup consists of this.

  • EdgeRouter 4 as router and firewall.
  • Configured with vlan 192.168.5.0/24 for servers.
  • Configured with a user, that can connect via ssh, using key-pairs that have no password.
  • Raspberry Pi 4, with 8gb ram, 120gb USB-3 M2 SSD. Raspbian 64bit o/s.
  • Docker installed and docker-compose
  • Connected to the vlan
  • User configured with docker access. See Serving Wordpress with Caddy 2 and Docker on how to set this up.

Directory Structure

This is the directory structure that we will use from the users home directory.

docker
  conf
    fail2ban
      action.d
      db
      filter.d
      jail.d
    influxdb2
  data
    influxdb2
  docker-image-sources
    fail2ban-ssh-client
    openssh-server
  scripts
  logs

Make these paths with.

mkdir ~/docker #or some other base
cd ~/docker
mkdir -p conf/fail2ban/action.d
mkdir -p conf/fail2ban/db
mkdir -p conf/fail2ban/filter.d
mkdir -p conf/fail2ban/jail.d
mkdir -p conf/influxdb2
mkdir -p docker-image-sources/fail2ban-ssh-client
mkdir -p docker-image-sources/openssh-server
mkdir -p data/openssh-server/.ssh
mkdir -p data/influxdb2
mkdir -p scripts
mkdir -p logs

Docker images

Fail2ban and openssh both require custom docker images which add extra packages.

openssh-server

The following docker file is required for openssh server.

docker-image-sources/openssh-server/Dockerfile

FROM linuxserver/openssh-server
RUN apk add --no-cache --upgrade openssh-client rsyslog rsync

fail2ban

The following docker file is required for fail2ban.

docker-image-sources/fail2ban-ssh-client/Dockerfile

FROM crazymax/fail2ban:latest
RUN apk add --no-cache --upgrade openssh-client 
RUN wget https://bootstrap.pypa.io/get-pip.py
RUN python3 get-pip.py
RUN pip --no-cache-dir -q install influxdb-client requests

Docker containers

The 3 docker containers will be configured via docker-compose. This is the docker-compose.yaml you will need I suggest you make changes per your individual needs.

docker-compose.yaml
  version: '3.8'

  services:
    influxdb:
      image: influxdb:2.0
      hostname: influxdb
      container_name: influxdb
      restart: unless-stopped
      user:
        1001:1001 # change to your User's UID.
      env_file:
        - "./conf/influxdb.env"
      networks:
        backend:
      ports:
        - "8086:8086"
      volumes:
        - "./conf/influxdb2:/etc/influxdb2"
        - "./data/influxdb2:/var/lib/influxdb2"

    openssh-server:
      build:
        context: ./docker-image-sources/openssh-server
        dockerfile: Dockerfile
      container_name: openssh-server
      hostname: openssh-server
      networks:
        backend:
      env_file:
        - "./conf/openssh-server.env"
      volumes:
        - ./data/openssh-server:/config
        - ./logs:/config/logs
      ports:
        - 2222:2222
      restart: unless-stopped

    fail2ban:
      build:
        context: ./docker-image-sources/fail2ban-ssh-client
        dockerfile: Dockerfile
      container_name: fail2ban
      networks:
        backend:
      depends_on:
        - openssh-server
      volumes:
        - "./conf/fail2ban:/data"
        - "./logs:/var/log"
        - "./data/fail2ban/ssh:/root/.ssh"
        - "./scripts:/scripts"
      env_file:
        - "./conf/fail2ban.env"
      restart: unless-stopped
  networks:
    backend:

InfluxDB Files

conf/influxdb.env

InfluxDB requires some environment variables to initialise the database and create a user. Set DOCKER_INFLUXDB_INIT_USERNAME and DOCKER_INFLUXDB_INIT_PASSWORD to suitable values. Set DOCKER_INFLUXDB_INIT_ORG to an org name, for example example.org.

TZ=Pacific/Auckland
# set these values for initial setup.
DOCKER_INFLUXDB_INIT_MODE=setup
DOCKER_INFLUXDB_INIT_USERNAME=<username>
DOCKER_INFLUXDB_INIT_PASSWORD=<password>
DOCKER_INFLUXDB_INIT_ORG=<your org>
DOCKER_INFLUXDB_INIT_BUCKET=default

No extra files are required for InfluxDB.

OpenSSH Server Files

conf/openssh-server.env

Change <your username> to the username you want to use to login to the ssh server. Change PUID and UID to the user who is running the docker containers.

PUID=1001
PGID=1001
TZ=Pacific/Auckland
SUDO_ACCESS=false 
PASSWORD_ACCESS=false
USER_NAME=<your username>
LOGLEVEL=VERBOSE

data/openssh-server/.ssh/authorized_keys

This file should be populated with the pubic key(s) you will use to access the ssh server.

Fail2Ban Files

Configuration

conf/fail2ban.env

This file should be customised to suit your settings

# change to your timezone
TZ=Pacific/Auckland

F2B_LOG_TARGET=STDOUT
F2B_LOG_LEVEL=INFO
F2B_DB_PURGE_AGE=1d
# required if firewalling on host.
F2B_IPTABLES_CHAIN=DOCKER-USER
F2B_LOG_LEVEL=INFO

# smtp configuration for fail2ban
SSMTP_HOST=smtp.gmail.com
SSMTP_PORT=465
SSMTP_HOSTNAME=<smpthostname>
SSMTP_USER=<smtpusername>
SSMTP_PASSWORD=<smtppassword>
SSMTP_TLS=YES

GEO_IP_API_KEY=<geoipkey>

#influx2
INFLUX_BUCKET=fail2ban
# this value is what you set in DOCKER_INFLUXDB_INIT_ORG in the docker-compose.yaml
INFLUX_ORG=<influxorg>
# you will need to populate this after starting and setting up influxdb
INFLUX_TOKEN=<influxtoken> 
INFLUX_URL=http://influxdb:8086

conf/fail2ban/jail.d/jail.local

This sets a list of ip address to ignore.

[DEFAULT]
ignoreip = 127.0.0.0/8 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 155.63.200.55 155.63.200.3

conf/fail2ban/jail.d/sshd.conf

sshd jail configuration.

[sshd]
enabled = true
port = 2222,22
filter = sshd[mode=aggressive]
logpath = /var/log/openssh/current
maxretry = 1
bantime = 86400
# custom ban action, leave blank for default
banaction = edgerouter
# banaction = nftables
conf/fail2ban/filter.d/common.conf
# Generic configuration items (to be used as interpolations) in other
# filters  or actions configurations
#

[INCLUDES]

# Load customizations if any available
after = common.local


[DEFAULT]

# Type of log-file resp. log-format (file, short, journal, rfc542):
logtype = file

# Daemon definition is to be specialized (if needed) in .conf file
_daemon = \S*

#
# Shortcuts for easier comprehension of the failregex
#
# PID.
# EXAMPLES: [123]
__pid_re = (?:\[\d+\])

# Daemon name (with optional source_file:line or whatever)
# EXAMPLES: pam_rhosts_auth, [sshd], pop(pam_unix)
__daemon_re = [\[\(]?%(_daemon)s(?:\(\S+\))?[\]\)]?:?

# extra daemon info
# EXAMPLE: [ID 800047 auth.info]
__daemon_extra_re = \[ID \d+ \S+\]

# Combinations of daemon name and PID
# EXAMPLES: sshd[31607], pop(pam_unix)[4920]
__daemon_combs_re = (?:%(__pid_re)s?:\s+%(__daemon_re)s|%(__daemon_re)s%(__pid_re)s?:?)

# Some messages have a kernel prefix with a timestamp
# EXAMPLES: kernel: [769570.846956]
__kernel_prefix = kernel:\s?\[ *\d+\.\d+\]:?

__hostname = \S+

# A MD5 hex
# EXAMPLES: 07:06:27:55:b0:e3:0c:3c:5a:28:2d:7c:7e:4c:77:5f
__md5hex = (?:[\da-f]{2}:){15}[\da-f]{2}

# bsdverbose is where syslogd is started with -v or -vv and results in <4.3> or
# <auth.info> appearing before the host as per testcases/files/logs/bsd/*.
__bsd_syslog_verbose = <[^.]+\.[^.]+>

__vserver = @vserver_\S+

__date_ambit = (?:\[\])

# Common line prefixes (beginnings) which could be used in filters
#
#      [bsdverbose]? [hostname] [vserver tag] daemon_id spaces
#
# This can be optional (for instance if we match named native log files)
__prefix_line = <lt_<logtype>/__prefix_line>

# PAM authentication mechanism check for failures, e.g.: pam_unix, pam_sss,
# pam_ldap
__pam_auth = pam_unix

# standardly all formats using prefix have line-begin anchored date:
datepattern = <lt_<logtype>/datepattern>

[lt_file]
# Common line prefixes for logtype "file":
__prefix_line = %(__date_ambit)s?\s*(?:%(__bsd_syslog_verbose)s\s+)?(?:%(__hostname)s\s+)?(?:%(__kernel_prefix)s\s+)?(?:%(__vserver)s\s+)?(?:%(__daemon_combs_re)s\s+)?(?:%(__daemon_extra_re)s\s+)?
datepattern = {^LN-BEG}

[lt_short]
# Common (short) line prefix for logtype "journal" (corresponds output of formatJournalEntry):
__prefix_line = \s*(?:%(__hostname)s\s+)?(?:%(_daemon)s%(__pid_re)s?:?\s+)?(?:%(__kernel_prefix)s\s+)?
datepattern = %(lt_file/datepattern)s
[lt_journal]
__prefix_line = %(lt_short/__prefix_line)s
datepattern = %(lt_short/datepattern)s

[lt_rfc5424]
# RFC 5424 log-format, see gh-2309:
#__prefix_line = \s*<__hostname> <__daemon_re> \d+ \S+ \S+\s+
__prefix_line = \s*<__hostname> <__daemon_re> \d+ \S+ (?:[^\[\]\s]+|(?:\[(?:[^\]"]*|"[^"]*")*\])+)\s+
datepattern = ^<\d+>\d+\s+{DATE}

# Author: Yaroslav Halchenko, Sergey G. Brester (aka sebres)
conf/fail2ban/action.d/edgerouter.conf

This is the custom action, this setup is for logging to influxdb and also to set firewall rules on the edgerouter.

# Fail2Ban configuration file
[INCLUDES]
before = iptables-common.conf

[Definition]
actionstart = /scripts/fail2ban-totals.py

actionflush = <remote-command> ipset flush <ipmset>
              /scripts/fail2ban-totals.py

actionstop =  <actionflush>

actionban = <remote-command> ipset add <ipmset> <ip> -exist
            /scripts/fail2ban-to-influx.py <ip>
            /scripts/fail2ban-totals.py

actionunban = <remote-command> ipset del <ipmset> <ip> -exist
              /scripts/fail2ban-totals.py

[Init]
# make sure to use the router ip address
remote-command = ssh -i /data/fail2ban_id_rsa [email protected] sudo 

default-ipsettime = 0
ipsettime = 0
timeout-bantime = $([ "<bantime>" -le 2147483 ] && echo "<bantime>" || echo 0)

ipmset = f2b-<name>
familyopt =


[Init?family=inet6]

ipmset = f2b-<name>6
familyopt = family inet6

Note the remote-command, this will need to be set to your router's ip address and the user you will use to connect to it.

conf/fail2ban/fail2ban_id_rsa

Generate a key pair for fail2ban to use to ssh into the edgerouter. Do no add a passphrase.

> cd conf/fail2ban
> ssh-keygen -f fail2ban_id_rsa -t rsa -q
Enter passphrase (empty for no passphrase): 
Enter same passphrase again:

scripts

These scripts are used by fail2ban.

scripts/fail2ban-to-influx.py

This script sends information about each ban and location to influxdb.

#!/usr/bin/python3
import os
import sys

import requests
import influxdb_client
from influxdb_client.client.write_api import SYNCHRONOUS
from rx import for_in

INFLUX_BUCKET = None
INFLUX_ORG = None
INFLUX_TOKEN = None
INFLUX_URL = None
GEO_IP_API_KEY = None

def do(ipaddress):
    data = getipdata(ipaddress)
    print(data)
    sendtoinfluxdb(data)


def sendtoinfluxdb(data):
    iclient = influxdb_client.InfluxDBClient(
        url=INFLUX_URL,
        token=INFLUX_TOKEN,
        org=INFLUX_ORG
    )
    write_api = iclient.write_api(write_options=SYNCHRONOUS)

    point = influxdb_client.Point("fail2ban_ip_location") \
            .tag("latitude", data['latitude']) \
            .tag("longitude", data['longitude']) \
            .tag("location", data['city'] + ' ' + data['district'] + ' ' + data['state_prov'] + ' ' + data['country_name'])

    for i in data:
        point = point.field(i, data[i])
    print(point.to_line_protocol())
    write_api.write(bucket=INFLUX_BUCKET, org=INFLUX_ORG, record=point)


def getipdata(ipaddress):
    response = requests.get(
        f"https://api.ipgeolocation.io/ipgeo?apiKey={GEO_IP_API_KEY}&fields=geo&ip={ipaddress}")
    if response.status_code == requests.codes.OK:
        json = response.json()
        return json
    return None

if __name__ == '__main__':
  if os.environ.get('INFLUX_BUCKET'):
    INFLUX_BUCKET = os.environ.get('INFLUX_BUCKET')
  if os.environ.get('INFLUX_ORG'):
    INFLUX_ORG = os.environ.get('INFLUX_ORG')
  if os.environ.get('INFLUX_TOKEN'):
    INFLUX_TOKEN = os.environ.get('INFLUX_TOKEN')
  if os.environ.get('INFLUX_URL'):
    INFLUX_URL = os.environ.get('INFLUX_URL')
  if os.environ.get('GEO_IP_API_KEY'):
    GEO_IP_API_KEY = os.environ.get('GEO_IP_API_KEY')
  if not INFLUX_BUCKET or not INFLUX_ORG or not INFLUX_TOKEN or not INFLUX_URL or not GEO_IP_API_KEY:
    print("Provide environment")
    exit(1)

  do(sys.argv[1])
scripts/fail2ban-totals.py

This script sends information about total bans to influxdb.

#!/usr/bin/python3

import os
import sys
import subprocess
import re

import requests
import influxdb_client
from influxdb_client.client.write_api import SYNCHRONOUS

INFLUX_BUCKET = None
INFLUX_ORG = None
INFLUX_TOKEN = None
INFLUX_URL = None


def do():
  jails = getJails()

  for jail in jails:
    current_failed, current_banned, total_failed, total_banned = getTotal(jail)
    sendToInfluxDB(jail, current_failed, current_banned, total_failed, total_banned)


def getJails():
  out = runProcess(['fail2ban-client','status'])
  m = re.search(r"^.*Jail list:\s(.*)$", out, re.MULTILINE)
  jails = m.group(1).split(", ")
  print(jails)
  return jails;


def getTotal(jail):
  out = runProcess(['fail2ban-client','status',jail])
  m = re.search(r"^.*Currently failed:\s(.*)$", out, re.MULTILINE)
  current_failed = int(m.group(1))
  m = re.search(r"^.*Total failed:\s(.*)$", out, re.MULTILINE)
  total_failed = int(m.group(1))
  m = re.search(r"^.*Currently banned:\s(.*)$", out, re.MULTILINE)
  current_banned = int(m.group(1))
  m = re.search(r"^.*Total banned:\s(.*)$", out, re.MULTILINE)
  total_banned = int(m.group(1))

  return current_failed, current_banned, total_failed, total_banned

def runProcess(args):
  completed = subprocess.run(args, capture_output=True)
  return completed.stdout.decode('utf-8')


def sendToInfluxDB(jail, current_failed, current_banned, total_failed, total_banned):
    iclient = influxdb_client.InfluxDBClient(
        url=INFLUX_URL,
        token=INFLUX_TOKEN,
        org=INFLUX_ORG
    )
    write_api = iclient.write_api(write_options=SYNCHRONOUS)

    point = influxdb_client.Point("fail2ban_stats") \
            .tag("jail", jail) \
            .field("current_failed",current_failed) \
            .field("current_banned",current_banned) \
            .field("total_failed",total_failed) \
            .field("total_banned",total_banned)

    print(point.to_line_protocol())
    write_api.write(bucket=INFLUX_BUCKET, org=INFLUX_ORG, record=point)


if __name__ == '__main__':
  if os.environ.get('INFLUX_BUCKET'):
    INFLUX_BUCKET = os.environ.get('INFLUX_BUCKET')
  if os.environ.get('INFLUX_ORG'):
    INFLUX_ORG = os.environ.get('INFLUX_ORG')
  if os.environ.get('INFLUX_TOKEN'):
    INFLUX_TOKEN = os.environ.get('INFLUX_TOKEN')
  if os.environ.get('INFLUX_URL'):
    INFLUX_URL = os.environ.get('INFLUX_URL')

  if not INFLUX_BUCKET or not INFLUX_ORG or not INFLUX_TOKEN or not INFLUX_URL:
    print("Provide environment")
    exit(1)

  do()

Permissions

The python scripts should have execute permission.

chmod a+x scripts/*

Router Configuration - EdgeRouter 4

For the edgerouter, it will need some firewall rules and a user created for fail2ban to use. These steps will require use of the edgerouter CLI

ssh into your edgerouter using your normal user then follow these steps.

  1. Enable configure mode.

    bob@EdgeRouter-4:~$ configure
    
  2. Create an address group called f2b-sshd.

    set firewall group address-group f2b-sshd
    
  3. Modify WAN_IN to use the address group.

    set firewall name WAN_IN rule 60 action drop
    set firewall name WAN_IN rule 60 description 'Fail2Ban SSD'
    set firewall name WAN_IN rule 60 destination port 2222
    set firewall name WAN_IN rule 60 log enable
    set firewall name WAN_IN rule 60 protocol tcp_udp
    set firewall name WAN_IN rule 60 source group address-group f2b-sshd
    
  4. Create port forward rule for ssh.

    For this rule, you will need to know the host address of the server that docker is running on. I recommend a fixed ip address. Put that address in <hostaddress>.

    set port-forward rule 5 description ssh2
    set port-forward rule 5 forward-to address <hostaddress>
    set port-forward rule 5 forward-to port 2222           
    set port-forward rule 5 original-port 23    
    set port-forward rule 5 protocol tcp    
    
  5. Create a user for fail2ban to ssh into the router.

    For this step you will need the contents of conf/fail2ban/fail2ban_id_rsa.pub. You will need to put the base64 encoded portion the key in place of <publickeyhere> at the end of the following command. It also seems that the edge router wont allow the creation of a user without a password so you must supply one as well.

    set system login user fail2ban2 authentication plaintext-password <apassword>
    set system login user fail2ban authentication public-keys Fail2Ban type ssh-rsa
    set system login user fail2ban authentication public-keys key <publickeyhere>
    set system login user fail2ban level admin
    
  6. Commit and Save changes

    commit
    save
    exit
    

    You will now be back in the normal shell.

  7. Exit the ssh session to the edge router.

Test ssh connection

Now is a good time to make sure the user and ssh key are setup correctly on the edge router.

>  ssh -i conf/fail2ban/fail2ban_id_rsa [email protected]
Linux EdgeRouter-4 4.9.79-UBNT #1 SMP Tue May 11 13:21:10 UTC 2021 mips64
fail2ban@EdgeRouter-4:~$ 

Startup and Configure InfluxDB

First start influxdb container.

> docker up influxdb

Watch the logs, if everything is good within a minute or so you will see a line something like:

influxdb | ts=2021-09-02T03:57:16.531008Z lvl=info msg=Listening log_id=0WLAIywW000 service=tcp-listener transport=http addr=:8086 port=8086

This means influx started and is listening on the http port 8086, so you should now be able to browse to the website and see the influxdb login screen.

InfluxDB Login

Now we need to stop and restart influxdb in the background, so press Ctrl+C and edit /conf/influxdb.env to remove or comment out the following lines.

DOCKER_INFLUXDB_INIT_MODE=setup
DOCKER_INFLUXDB_INIT_USERNAME=username
DOCKER_INFLUXDB_INIT_PASSWORD=password
DOCKER_INFLUXDB_INIT_ORG=example.org
DOCKER_INFLUXDB_INIT_BUCKET=default

Then restart the influxdb container.

> docker up -d influxdb

After a minute re-connect to the influxdb site and Sign In with the username and password you entered into the env variables DOCKER_INFLUXDB_INIT_USERNAME and DOCKER_INFLUXDB_INIT_PASSWORD.

Once on the Getting Started screen follow these steps:

  1. Click the data icon from the left side bar.

    Data Icon

  2. Click Buckets from the tabs.

  3. Click + Create Bucket from the right side.
  4. Call the bucket fail2ban.

    Create Bucket

  5. Click Create

  6. Select Tokens from the tabs.
  7. Click + Generate Token from the right side.

    Generate Token

  8. Then click Read/Write Token.

  9. Give the token the description Fail2Ban and select fail2ban bucket under Read and Write.

    Generate ReadWrite Token

  10. Click Save.

  11. You will now see the token listed. Click the token name Fail2Ban

    Token

  12. The generated token will be created, copy this to the clipboard.

    Fail2Ban Token

  13. Now go and edit conf/fail2ban.env and set the INFLUX_TOKEN= to the token you copied to the clipboard.

InfluxDB is now configured ready to take data.

Startup and Test openssh-server

Starting the openssh-server container the first time will build the container.

> docker-compose up -d openssh-server

When it is finished, you can then test the ssh server, and check the logs were created properly. We can do this by testing a good login and a bad login.

  1. Bad user.

    > ssh somestupiduser@localhost -p 2222
    The authenticity of host '[localhost]:2222 ([::1]:2222)' can't be established.
    ECDSA key fingerprint is SHA256:eVjVn5Q2EW1tZWDqfK+5G5ypGLDwkOOf3mfowwUDuuA.
    Are you sure you want to continue connecting (yes/no)? yes
    Warning: Permanently added '[localhost]:2222' (ECDSA) to the list of known hosts.
    somestupiduser@localhost: Permission denied (publickey,keyboard-interactive).
    
  2. Good user. For this user you will need your USER_NAME from conf/openssh-server.env and you will need the private ssh key that matches the public one you put in data/openssh-server/.ssh/authorized_keys.

    > ssh USER_NAME@localhost -p 2222 -i ~/.ssh/id_rsa
    The authenticity of host '[localhost]:2222 ([192.168.1.81]:2222)' can't be established.
    ECDSA key fingerprint is SHA256:eVjVn5Q2EW1tZWDqfK+5G5ypGLDwkOOf3mfowwUDuuA.
    Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
    Warning: Permanently added '[localhost]:2222,[192.168.1.81]:2222' (ECDSA) to the list of known hosts.
    Welcome to OpenSSH Server
    
    openssh-server:~$ 
    
  3. Check the contents of logs/openssh/current you should see the following.

    logs/openssh/current

    2021-09-02 16:48:48.831029185  Server listening on 0.0.0.0 port 2222.
    2021-09-02 16:48:48.831199831  Server listening on :: port 2222.
    2021-09-02 16:50:41.820462317  Invalid user somestupiduser from 172.20.0.1 port 35518
    2021-09-02 16:50:41.837216587  Connection closed by invalid user somestupiduser 172.20.0.1 port 35518 [preauth]
    2021-09-02 16:55:25.667971842  Connection closed by authenticating user USER_NAME 172.20.0.1 port 35526 [preauth]
    2021-09-02 16:55:52.922482955  Connection closed by authenticating user USER_NAME 192.168.1.172 port 34908 [preauth]
    2021-09-02 16:56:39.099231147  Accepted publickey for USER_NAME from 192.168.1.172 port 34924 ssh2: RSA SHA256:Bx3jLYpJ6OOavQREjCDygOULKCl5w5ggtsLE/Viebr4
    2021-09-02 16:56:39.153657884  Attempt to write login records by non-root user (aborting)
    

    This is what fail2ban will use to block anyone but the allowed user.

Startup and Test Fail2Ban

Starting the fail2ban container the first time will build the container.

> docker-compose up -d fail2ban

When it is finished, you can then test fail2ban. Its best to also watch the fail2ban logs while testing, you can do this with docker.

> docker-compose logs -f fail2ban

Repeat the Good User test above, the fail2ban log should not show anything.

Repeat the Bad User test above, the fail2ban log should show something like.

INFO    [sshd] Ignore 192.168.1.172 by ip
INFO    [sshd] Ignore 192.168.1.172 by ip

This shows that fail2ban saw the incorrect login, but ignored it because it came from an internal address listed in conf/fail2ban/jail.d/jail.local.

Next repeat the above tests, but coming from outside the network. You should see something like when you enter an incorrect username.

INFO    [sshd] Found 104.236.72.182 - 2021-08-31 09:27:23
NOTICE  [sshd] Ban 104.236.72.182

Check the firewall, you should no-longer be able to attempt ssh connection your connection attempt will be blocked at the firewall.

Look at the blocked addresses in the EdgeRouter.

Login to the edge router via ssh, and issue the following command.

> sudo ipset list f2b-sshd

You should get the following output with the banned ip address listed.

Name: f2b-sshd
Type: hash:net
Revision: 6
Header: family inet hashsize 1024 maxelem 65536
Size in memory: 416
References: 4
Members:
104.236.72.182

Another view of the ip-addresses can be seen in the address group.

> show firewall group f2b-sshd
Name       : f2b-sshd
Type       : address
Family     : IPv4
Description: 
References : WAN_IN-20-source
Members    :
             104.236.72.182

Data in InfluxDB

Now we can check that InfluxDB has had the ban data recorded into it. Sign In to the influx web page and then:

  1. Click the Explore Icon on the left bar.
  2. Select Single Stat from the top left dropdown of panel types.
  3. Select fail2ban in the FROM list of buckets.
  4. Select fail2ban_stats, current_banned and sshd for filters.
  5. Click Submit
  6. See a number > 0.00 in the panel. InfluxDB Fail2ban Current Banned
  7. Now Select fail2ban_ip_location in the first filter list.
  8. Select country_name in the second filter list.
  9. Unselect anything selected in the third filter list.
  10. Select last under AGGREGATE FUNCTION.
  11. Click Submit.
  12. You should now see the country name where the most recent ban occurred. InfluxDB Fail2ban Location
  13. Try different fields to see the values recorded.

Conclusion

You will now see the count of bans adding up, i often see in excess of 500 in a 24 hour period.

Useful Fail2Ban commands

  • Show jail status. bash > docker-compose exec fail2ban fail2ban-client status sshd

    Status for the jail: sshd
    |- Filter
    |  |- Currently failed: 0
    |  |- Total failed:     2
    |  `- File list:        /var/log/openssh/current
    `- Actions
      |- Currently banned: 49
      |- Total banned:     49
      `- Banned IP list:   103.76.252.6 112.166.133.216 113.134.211.42 117.185.41.226 121.200.61.37 121.4.66.32 124.152.213.64 124.43.9.184 129.211.79.208 141.98.10.179 157.230.12.188
      159.65.36.44 165.22.3.210 165.22.49.42 165.22.98.186 175.6.35.202 176.111.173.156 178.128.144.227 178.128.212.164 178.128.232.28 180.235.135.181 181.30.35.202 186.122.149.6
      191.188.70.175 191.35.72.75 193.112.118.22 202.165.25.137 211.218.145.195 211.253.133.50 221.131.165.23 221.131.165.56 221.181.185.140 221.181.185.220 222.186.42.137 222.187.254.38
      222.187.254.41 24.147.208.110 36.67.197.52 37.59.52.19 41.207.252.122 41.76.175.129 45.144.225.231 49.235.78.105 5.44.79.122 5.88.135.45 73.155.50.124 89.97.218.142 90.152.142.197
      165.227.7.187
    
  • Unban an ip

    > docker-compose exec fail2ban fail2ban-client unban 41.207.252.122
    
  • Ban an ip manually

    > docker-compose exec fail2ban fail2ban-client set sshd banip 41.207.252.122
    
  • Unban all

    > docker-compose exec fail2ban fail2ban-client unban --all
    

Useful docker-compose commands

  • Stop container

    > docker-compose stop <containername>
    
  • Stop all containers

    > docker-compose stop
    
  • Rebuild Container

    > docker-compose build --pull <containername>
    
  • Update container

    > docker-compose pull <containername>
    
  • Update all containers

    > docker-compose pull
    
  • Restart container after update or build

    > docker-compose up -d <containername>
    
  • Restart container

    > docker-compose restart <containername>
    

Fail2Ban

Openssh Server

InfluxDB

Docker