Skip to content

Spiral Two: Secure remote access

The goal of Spiral 2 is to prepare Piawake for exposure to the Internet. This includes hardening the operating system, reducing the attack surface, and creating a secure method of remote access over SSH.

Overview

  • Configuration
    • Change Hostname
    • Configure Router
  • Hardening
    • Update and Establish a Trusted Software Source
    • Harden SSH
    • Disable Password Authentication and Root Login
    • Configure the Firewall
    • Install Fail2ban
    • Reduce the Attack Surface
    • Secure VNC
  • Resilience
    • Hardware Watchdog
    • Persistent System Logs
  • Audit

    • Test SSH Tunnel on Local Network
    • Test SSH Tunnel on Other Networks

    The sections are organized by topic rather than implementation order.

Configuration

This section covers the initial system and network steps I took to establish the infrastructure for secure remote access.

System

Change the Hostname

Since the Project was developing I wanted to call the first connection point pi@piawake

The pi had a nice TUI to change settings:

sudo raspi-config
System Options
    ↓
Hostname

piconfig

Network

Configure the Router

I did this step after hardening the piawake.

Port forwarding

On the router I added a port forwarding ruleI made sure to pick a random port that is not common or coliding with other ports. These are the things I configured.

  • Active: Enable
  • Name: can be anything
  • Start and endpoint external Port: a random numbered port.
  • Internal (translation) port: the Raspberry Pi SSH port (22 is default it's a good idea to change it).
  • Server IP address: the static IP address assigned to the Raspberry Pi.

Static DHCP reservation

I created a static DHCP reservation for the Raspberry Pi. This binds the Pi's MAC address to a fixed IP address, ensuring it always receives the same local IP address from the DHCP server.

The flow looks like this after doing this <public IP> => <external port> => <Pi local IP> => <SSH port>

Hardening

This section goes trough the security measures implemented to reduce the system's attack surface and protect against unauthorized access.

System

Update and establish a trusted software source

First, I updated the operating system and all installed APT packages:

sudo apt update
sudo apt full-upgrade
sudo apt autoremove

To reduce the attack surface, this project will only use software installed through the APT package manager. The following will not be used:

  • Flatpak applications
  • Snap packages
  • Docker containers
  • Software compiled from source
  • Software installed from GitHub releases or tarballs

Enable automatic security updates

unattended-upgrades automatically installs security updates released through the APT repositories.

Install:

sudo apt install unattended-upgrades

enable it:

sudo dpkg-reconfigure unattended-upgrades

A text-based configuration dialog (TUI) will appear. I Selected Yes to enable automatic security updates.

autoup

Authentication

Harden SSH

I was connecting with ssh to the pi but with a password that was "1234" not so safe.

On my laptop I created a dedicated key:

ssh-keygen -t ed25519 -a 100 -f ~/.ssh/piawake

Explanation:

  • -t ed25519 → modern, recommended key type.
  • -a 100 → increases the key derivation work factor for better passphrase protection.

For a real system, I would protect the private key with a passphrase. I skipped it here to keep the demonstration simple.

Next step is to copy it to the pi.

ssh-copy-id -i ~/.ssh/piawake.pub pi@<ip-address>

Then I tested the connection using only that key. The account password should no longer be required, only the SSH key passphrase if one was set.

ssh -i ~/.ssh/piawake pi@<ip-address>

quality of life improvements

Instead of typing -i every time, I added a entry to my local ~/.ssh/config:

Host piawake
    HostName <ip-address>
    User pi
    IdentityFile ~/.ssh/piawake

Now I can simply connect with ssh piawake

Disable assword authentication & No Root login

Now it's important to do this so I don't accidentally lock my self out so I followed these steps:

a) Verify the SSH key works

ssh piawake

Don't close this existing SSH session until everything is confirmed.

b) Edit the SSH configuration

sudo vi /etc/ssh/sshd_config

Find (or add) these lines:

PermitRootLogin no
PubkeyAuthentication yes
PasswordAuthentication no
UsePAM yes

c) test the configuration:

sudo sshd -t

If the command produces no output, the configuration is valid and safe to restart the ssh.

sudo systemctl restart ssh

d) Test the connection in another terminal window without closing current session:

ssh piawake

This should now work I also tested to see that the password was disabled:

 ssh \
  -o PreferredAuthentications=password \
  -o PubkeyAuthentication=no \
  pi@<ip-address>

Password authentication still worked. After checking the effective SSH configuration, I discovered that 50-cloud-init.conf was overriding my settings.

This highlighted the importance of checking the effective SSH configuration rather than assuming changes to sshd_config are the final settings.

sudo vi /etc/ssh/sshd_config.d/50-cloud-init.conf

change to no:

PasswordAuthentication no

I then restarded the ssh and I could use grep to check it:

sudo sshd -T | grep -E 'passwordauthentication|kbdinteractiveauthentication|usepam|permitrootlogin|pubkeyauthentication'

I confirmed the hardening was successful by testing both authentication methods:

  • SSH key authentication succeeded.
  • Password authentication was denied with: "Permission denied (publickey)."

Network

Configure the Firewall

Next, I configured the firewall to allow only SSH connections.

Install UFW:

sudo apt install ufw

Allow only SSH:

sudo ufw allow 22/tcp
sudo ufw enable

Verify the configuration:

sudo ufw status verbose

Result:

Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), disabled (routed)
New profiles: skip

To                         Action      From
--                         ------      ----
22/tcp                     ALLOW IN    Anywhere
22/tcp (v6)                ALLOW IN    Anywhere (v6)

This means all incoming connections are blocked by default except SSH. VNC will later be accessed through an SSH tunnel, so no additional ports need to be opened.

Install Fail2ban

Next I setup Fail2ban it's a intrusion prevention software that automatically blocks IP addresses after repeated failed authentication attempts.

Install:

sudo apt install fail2ban

Enable it:

sudo systemctl enable --now fail2ban

This blocks repeated failed login attempts.

Reduce the Attack Surface

Check what is listening:

sudo ss -tulpn

Every listening service represents another potential entry point, so unnecessary services should either be removed or restricted. This is what I will keep,restrict and disable:

  • Keep
  • SSH (22/tcp): Remote access.
  • **Restrict **
  • WayVNC (5900/tcp): bind it to 127.0.0.1 only accessible through an SSH tunnel.
  • Disable
  • rpcbind (111/tcp and 111/udp): NFS/RPC services.
  • Avahi (5353/udp): .local hostname discovery.

Disable rpcbind and Avahi

sudo systemctl disable --now rpcbind
sudo systemctl disable --now avahi-daemon
sudo systemctl disable --now rpcbind.socket
sudo systemctl disable --now avahi-daemon.socket

Restrict WayVNC

First I checked how it was started with:

systemctl status wayvnc

I saw that there was a config file:

/etc/wayvnc/config

I inspected the file and changed the address line:

address=127.0.0.1

Then restart WayVNC:

sudo systemctl restart wayvnc

Verify:

sudo ss -tulpn

The listening services were much cleaner and easier to keep track of. The only externally reachable service is SSH. The goal is to access the graphical desktop by tunneling the VNC connection through SSH.

Resilience

This section focuses on improving system reliability and ensuring continued operation and recovery in the event of failures.

Reliability

Hardware Watchdog

Install:

sudo apt install watchdog

Enable the hardware watchdog:

sudo systemctl enable watchdog
sudo systemctl start watchdog

Verify the watchdog :

sudo systemctl status watchdog --no-pager
ls -l /dev/watchdog*
lsmod | grep bcm2835_wdt
dmesg | grep -i watchdog

That confired:

  • That ehe BCM2835 hardware watchdog is detected.
  • systemd is using the hardware watchdog.
  • /dev/watchdog0 is active.
  • If the kernel or userspace stops kicking the watchdog for about 60 seconds, the Pi will automatically reboot.

Persistent System Logs

By default, Raspberry Pi OS stores system logs in RAM, causing them to be lost after a reboot. Enabling persistent logging stores the logs on disk instead, making it easier to troubleshoot issues and investigate crashes or system freezes after the device comes back online.

Create the journal directory:

sudo mkdir -p /var/log/journal

Restart systemd-journald and flush the logs to enable persistent storage:

sudo systemctl restart systemd-journald
sudo journalctl --flush

Verify that the logs are stored on disk:

sudo journalctl --header | grep "File path"

Example output:

/var/log/journal/.../system.journal
/var/log/journal/.../user-1000.journal

Audit

This section verifies that the implemented security measures function as intended through testing and validation.

Verification

Test ssh tunnel on local network

I've never done ssh tunnel before so before I open the pi to the internet I tested it out I had restikted the VNC. I tried to conect like I would do normaly and it was closed.

ssh -L 5901:localhost:5900 piawake
tiger VNC
    │
localhost:5901
    │
SSH tunnel
    │
Pi localhost:5900
    │
WayVNC

ssh tun

Test the ssh tunnel on other networks.

I tested it on other networks and it worked there as well.

Troubleshooting

Good commands

  • Pi Temperature monitoring:
vcgencmd measure_temp
  • Pi Power protection:
vcgencmd get_throttled
  • Monitor memory :
free -h
  • Check journar logs:

  • Check the previous boot:

  • bash sudo journalctl -b -1 -e --no-pager

  • Previous boot, kernel messages only:

  • bash sudo journalctl -b -1 -k --no-pager

  • List all recorded boots:

  • bash sudo journalctl --list-boots

Piawake freezing

Piawake would occasionally freeze, making it difficult to diagnose because it happened randomly. After enabling persistent system logs, I was able to investigate the previous boot and look for clues.

Test 1: Disable Wi-Fi power saving

I found this in the system log:

brcmfmac: power save enabled
systemd-rfkill.service: start operation timed out

To check whether Wi-Fi power saving was enabled, I ran:

pi@piawake:~ $ iw dev wlan0 get power_save
Power save: on

Since the piawake is used as an always-on remotly, I disabled Wi-Fi power saving as a test:

sudo iw dev wlan0 set power_save off

To make it perminite:

sudo vi /etc/NetworkManager/conf.d/wifi-powersave.conf
[connection]
wifi.powersave = 2

Restart or reboot:

sudo systemctl restart NetworkManager

The system will now be monitored to determine whether disabling Wi-Fi power saving prevents the random freezes.