Back to Blog

Raspberry Pi Security Setup

Nach der kompletten Neuinstallation wollte ich den Raspberry Pi 5 nicht direkt als Experimentiermaschine nutzen. Erst sollte die Basis stimmen: klarer SSH-Zugang, kein Passwort-Login, Firewall, automatische Updates und nur das Netzwerk, das wirklich gebraucht wird.

Der Pi soll mein lokales Lab für KI-Experimente und Agent-Harnesses werden. Die Website selbst wird weiter lokal auf der Workstation gebaut. Der Raspberry Pi ist dafür die Experimentierumgebung, nicht der Webserver für newwaves.dev.

Ausgangspunkt

Nach der Neuinstallation hatte der Pi den Hostnamen raspberrypi5 und eine feste lokale IP über die DHCP-Reservierung im Router. Gearbeitet wird über einen normalen Benutzer mit sudo, nicht direkt als Root.

hostname: raspberrypi5
ip:       feste DHCP-Reservierung im Heimnetz
user:     normaler sudo-Benutzer
system:   Debian/Raspberry Pi OS, aarch64

Zuerst habe ich geprüft, ob der Benutzer wirklich der Admin-User ist und nicht direkt als Root gearbeitet wird.

whoami
id
groups
sudo whoami
sudo -l

Das Ergebnis war genau richtig: Der Benutzer ist ein normaler Account, Mitglied der Gruppe sudo und kann administrative Befehle über sudo ausführen.

Gleiche Checkliste, anderes Spielfeld

Für meinen VPS habe ich bereits ein Security Setup dokumentiert, und der Plan war: Checkliste rausholen, abarbeiten, fertig. Ganz so einfach war es nicht, denn das Bedrohungsmodell ist ein anderes. Ein VPS steht mit öffentlicher IP direkt im Internet und wird ab der ersten Minute gescannt. Der Pi steht hinter dem Router im Heimnetz: Solange dort keine Portfreigabe existiert, ist er von außen schlicht nicht erreichbar. Die dichteste Außengrenze ist der Router selbst. Deshalb übersetzt sich die VPS-Checkliste nicht eins zu eins, und der Router gehört mit auf die Liste, dazu unten mehr.

SSH-Keys statt Passwort

Bevor ich Passwort-Logins abschalte, brauche ich funktionierende SSH-Keys. Deshalb kamen zuerst zwei getrennte Zugänge in authorized_keys: einer für Termius auf der Workstation und einer für die Codex App.

raspberrypi5-workstation
raspberrypi5-codex

Termius ist mein manueller Zugriff. Die Codex App bekommt einen eigenen lokalen OpenSSH-Key, damit ich später Systeminformationen, Logs und Projektdateien direkt auslesen kann, ohne alles per Copy/Paste durch den Chat zu schieben.

Host raspberrypi5
  HostName <lokale-ip>
  User <pi-user>
  Port <ssh-port>
  IdentityFile ~/.ssh/raspberrypi5-codex
  IdentitiesOnly yes

SSH härten

Der frische SSH-Stand war noch zu offen: Standard-Port, Passwort-Login erlaubt und Root-Login nicht vollständig deaktiviert. Nach dem erfolgreichen Key-Test habe ich die SSH-Konfiguration schrittweise geändert. Die konkrete Portnummer ersetze ich hier bewusst durch einen Platzhalter.

PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
KbdInteractiveAuthentication no
Port <ssh-port>

Eine Besonderheit auf dem frischen Pi war die Datei /etc/ssh/sshd_config.d/50-cloud-init.conf. Dort stand zusätzlich PasswordAuthentication yes, wodurch meine erste Änderung in sshd_config überschrieben wurde. Erst nachdem diese Datei ebenfalls auf no stand, war SSH wirklich key-only. Der Hintergrund ist eine sshd-Eigenheit, die in kaum einer Anleitung steht: Bei doppelten Optionen gewinnt der zuerst eingelesene Wert, und die Dateien unter sshd_config.d werden vor der Hauptdatei geladen. Verlassen sollte man sich deshalb nie auf das Lesen der Config-Dateien, sondern nur auf sudo sshd -T, das die effektiven Werte zeigt.

sudo sshd -t
sudo systemctl reload ssh
sudo sshd -T | grep -Ei 'port|permitrootlogin|passwordauthentication|pubkeyauthentication|kbdinteractiveauthentication'

Der finale SSH-Zustand:

port <ssh-port>
permitrootlogin no
pubkeyauthentication yes
passwordauthentication no
kbdinteractiveauthentication no

Ob ein Login wirklich über den Key lief, verrät nicht das Bauchgefühl, sondern das Journal. Solange im SSH-Client Key und Passwort hinterlegt sind, sieht man dem Login von außen nicht an, welcher Weg benutzt wurde.

sudo journalctl -u ssh -n 30 --no-pager | grep -i accepted
→ Accepted publickey for <pi-user> ... ED25519 SHA256:...

Und gegen das Aussperren gibt es eine einfache Routine: die alte Session offen lassen, die Änderung machen und den Login in einem neuen Tab testen. Erst wenn der neue Login sauber durchkommt, wird die alte Session geschlossen.

Firewall nur fürs Heimnetz

Der Pi steht im Heimnetz. Deshalb muss SSH nicht für beliebige Netze offen sein. UFW blockiert eingehend alles und erlaubt nur SSH aus dem lokalen Netzbereich.

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow from <heimnetz-cidr> to any port <ssh-port> proto tcp comment 'SSH LAN'
sudo ufw enable
sudo ufw status verbose

Damit bleibt der Pi im lokalen Netz erreichbar, aber die Firewall-Regel ist enger als ein pauschales allow <ssh-port>/tcp.

Updates und Fail2ban

Danach kamen automatische Sicherheitsupdates und Fail2ban. Beides ist keine Magie, aber eine solide Basisschicht: Updates werden nicht vergessen und wiederholte SSH-Fehlversuche landen im Fail2ban-Jail.

sudo apt install unattended-upgrades apt-listchanges -y
sudo dpkg-reconfigure -plow unattended-upgrades
apt-config dump APT::Periodic
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5
backend = systemd

[sshd]
enabled = true
port = <ssh-port>

Der Kontrollstatus war sauber: ein aktives SSH-Jail, keine fehlgeschlagenen Versuche und keine gebannten IPs.

Zwei Kleinigkeiten, die Einsteiger verunsichern können: Direkt nach dem Start meldet fail2ban-client unter Umständen einen Socket-Fehler. Das ist kein Defekt, der Dienst ist schlicht noch am Hochfahren, ein paar Sekunden später läuft alles. Und falls man sich doch einmal selbst bannt, hilft sudo fail2ban-client set sshd unbanip <ip>.

Unnötige Dienste rausnehmen

Beim Blick auf die offenen Dienste fiel avahi-daemon auf. Das ist für mDNS und Namen wie raspberrypi5.local praktisch. Ich nutze aber die feste DHCP-Reservierung im Router, also brauche ich Avahi aktuell nicht.

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

Danach war per TCP nur noch SSH auf dem konfigurierten Port sichtbar.

Ethernet-only

Der Pi hatte nach der Neuinstallation zwei aktive Interfaces im gleichen Netz: eth0 über Ethernet und wlan0 über WLAN. Da die feste IP im Router an der Ethernet-MAC hängt, habe ich WLAN deaktiviert.

sudo nmcli radio wifi off
sudo rfkill block wifi

Für den rebootfesten Zustand kam zusätzlich ein Overlay in die Raspberry-Pi-Boot-Config.

# Disable onboard Wi-Fi; Ethernet is used with a fixed DHCP reservation.
dtoverlay=disable-wifi

Nach dem Reboot war WLAN wirklich weg:

WIFI-HW  WIFI      WWAN-HW  WWAN
missing  disabled  missing  enabled

default via <gateway-ip> dev eth0 proto dhcp src <lokale-ip>

Der Router gehört zum Server

Der Abschnitt, den es im VPS-Artikel nicht gibt: Wenn die Außengrenze der Router ist, gehört er mit zur Härtung. Im Experten-Modus der Box habe ich vier Dinge geprüft. Portfreigaben: keine vorhanden, und das bleibt so. UPnP: war ab Werk deaktiviert, gut so, denn sonst könnten Geräte im Netz sich selbst Ports öffnen. Ping auf das WAN-Interface: war an und ist jetzt aus, damit der Anschluss für Scanner unsichtbar bleibt. Und das IPv6-Pendant zur Portfreigabe (bei meiner Box heißt es Host Exposure): leer. Dazu kommt die DHCP-Reservierung, damit der Pi seine feste Adresse behält.

Kontrolle statt Vertrauen

Härtung ist kein einmaliges Ritual. Etwa einmal im Monat laufen fünf Befehle, die den Zustand in zwei Minuten sichtbar machen: Firewall-Regeln, fehlgeschlagene Dienste, offene Ports, Fail2ban-Status und die letzten Logins.

sudo ufw status verbose
sudo systemctl --failed
sudo ss -tulpn
sudo fail2ban-client status sshd
last -20

Endzustand

Damit ist der Pi noch keine fertige KI-Maschine, aber eine gute Projektbasis. Genau das war das Ziel für diesen Schritt.

  • Hostname raspberrypi5
  • feste lokale IP per DHCP-Reservierung
  • SSH nur per Key
  • eigener SSH-Port
  • Root-SSH deaktiviert
  • Passwort-SSH deaktiviert
  • UFW aktiv, SSH nur aus dem Heimnetz
  • unattended-upgrades aktiv
  • Fail2ban aktiv
  • Avahi/mDNS deaktiviert
  • WLAN rebootfest deaktiviert
  • Ethernet-only über eth0
  • Router: keine Portfreigaben, UPnP aus, WAN-Ping aus

Roadmap

Nach dem Security Setup ging es um die eigentliche Agent-Arbeit, und die ist inzwischen dokumentiert: