Modern Warrior School

Securing Our Server – Uncomplicated Firewall & Fail2Ban

One thing we can do off the bat is set up our firewall which is exactly what we are going to do in this third part of our portable server set up guide. If you haven’t checked out our previous two articles, we’ve already covered the basic design and purpose as well as flashing the OS to our SD card using the RPi-Imager tool. 

Uncomplicated Firewall

For this we will be using Uncomplicated Firewall, which is installed by default on Ubuntu. By default, it should be disabled, but you can verify this using:

sudo ufw status

We will start by setting our defaults to block incoming traffic and allow outgoing traffic.

sudo ufw default allow outgoing

sudo ufw default dent incoming

Now we can open up the ports we want to use. For now, let’s allow Port 22 (SSH) so we can access the Pi remotely.

sudo ufw allow port 22

We can continue to open up other ports as we need, and when we are ready, we can activate the changes using the following command:

sudo ufw enable

Setting Up OpenSSH

But we aren’t done yet. We just opened up Port 22, but it doesn’t do us any good if we don’t set up SSH. Open a terminal on the server side and execute the following command to install openssh-server on your Pi.

sudo apt install openssh-server

Now jump over to your client side device and open another terminal.

ssh-keygen

Ssh-copy-id -i ~/.ssh/id_rsa.pub [username@ipaddress of server]

This will generate a key for your computer to use to access the server and add it to the server’s list of keys. Next we will want to verify that this worked.

ssh [username@ipaddress of server]

Now, lets disable the password login so that only the key can be used to log in. 

sudo nano /etc/ssh/sshd_config
Change PermitRootLogin to no
Change PasswordAuthentication to no
[ctrl + o] [enter] [ctrl + x]

And check that everything is still working:

sudo systemctl restart ssh
ssh [username@ipaddress of server]

Fail2Ban

Fail2Ban is a utility that blocks IP addresses that show signs of malicious activity (like multiple failed login attempts). It is a great addition to UFW and is a generally lightweight utility When it detects suspicious activity, it dynamically changes firewall rules to effectively stop the attack.

To get started we can install it with:

sudo apt install fail2ban 

The configuration file for fail to ban is located at /etc/fail2ba/jail.conf, but for custom edits, its best to copy this to jail.local.

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

sudo nano /etc/fail2ban/jail.local

Now we can start making our edits. We will keep this one simple and enable ssh protection. The following lines set the maximum number of login attempts to 5 before banning that IP for 10 minutes.

[sshd]

enabled = true

port = ssh

maxretry = 5

bantime = 10m

Finally, we will want to start fail2ban and enable it to start at boot.

sudo stystemctl start fail2ban

sudo systemctl enable fail2ban

Optional: Custom Python Monitoring

While firewall rules are a great way to improve the security of our server, they don’t alert us when suspicious activity is detected. For this, I wrote a simple python script that will check for signs of a potential breach (such as failed sudo attempts or priveledge escalation.

This script can be run in 3 modes: -t for test, -p for passive, and -a for active. Test mode simulates a breach by writing “breach” to a file. Another script I wrote will check this file periodically and show a message on my oled status displays of the suspicious activity. Passive mode monitors for signs of a breach and displays this to the oleds, while active will take actions to actively stop the breach (though this part is still in development).

import os
import time
import sys
import psutil # Requires installation: pip install psutil

# Function to check for breaches
def check_for_breach():
breach_detected = False
# Placeholder for additional breach conditions
# Example: Check privilege escalation or file tampering
# breach_detected = True # Uncomment to simulate a breach

# Check resource usage
resource_breach = check_resources()
if resource_breach:
breach_detected = True

if breach_detected:
with open(“/tmp/security_status”, “w”) as status_file:
status_file.write(“BREACH”)
else:
try:
os.remove(“/tmp/security_status”)
except FileNotFoundError:
pass # No file to remove if it’s not there

# Function to check resource usage
def check_resources():
high_usage_detected = False
cpu_threshold = 80 # CPU usage percentage threshold
memory_threshold = 90 # Memory usage percentage threshold

# Check CPU usage
cpu_usage = psutil.cpu_percent(interval=1)
if cpu_usage > cpu_threshold:
print(f”WARNING: High CPU usage detected: {cpu_usage}%”)
high_usage_detected = True

# Check memory usage
memory = psutil.virtual_memory()
memory_usage = memory.percent
if memory_usage > memory_threshold:
print(f”WARNING: High memory usage detected: {memory_usage}%”)
high_usage_detected = True

return high_usage_detected

# Function to run in test mode
def test_mode():
print(“Running in test mode. Checking for breaches…”)
check_for_breach()
time.sleep(5) # Wait a bit for testing purposes
print(“Test mode completed.”)

# Function to run in passive mode
def passive_mode():
print(“Running in passive mode. Monitoring system without active enforcement…”)
while True:
check_for_breach() # Check for breaches periodically
time.sleep(60) # Adjust the frequency as needed

# Function to run in active mode
def active_mode():
print(“Running in active mode. Monitoring system and taking actions for breaches…”)
while True:
check_for_breach() # Check for breaches periodically
if os.path.exists(“/tmp/security_status”): # If breach detected, show breach message
print(“BREACH DETECTED!”)
time.sleep(60) # Adjust the frequency as needed

# Main function to parse command-line arguments and run in the appropriate mode
def main():
mode = ‘passive’ # Default mode

if len(sys.argv) > 1:
flag = sys.argv[1].lower() # Get the flag from command line argument
if flag == ‘p’: # Passive mode
mode = ‘passive’
elif flag == ‘a’: # Active mode
mode = ‘active’
elif flag == ‘t’: # Test mode
mode = ‘test’
else:
print(f”Unknown flag: {flag}. Defaulting to passive mode.”)

# Run the appropriate mode
if mode == ‘test’:
test_mode()
elif mode == ‘active’:
active_mode()
elif mode == ‘passive’:
passive_mode()

if __name__ == “__main__”:
main()

Conclusion

Hopefully that wasn’t too mind numbing of an explanation on how to set up a firewall. It’s all pretty basic stuff once you dive into it, but I remember firewall configuration being a little intimidating when I was getting started as it was my first introduction to the command line.

Stay tuned for our fourth part where we continue our build both here and on YouTube.