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.