Auth
Updated on GitHub a month ago (see history)

Table of contents

How to configure hardened Raspberry Pi

Requirements

  • Raspberry Pi 4
  • microSD card or external solid state drive (with USB-A connector)
  • microSD card reader or secure digital (SD) card reader with microSD to SD adapter (if using microSD card)
  • USB-C power adapter (minimum 3A)
  • Keyboard (with USB-A connector)
  • Micro HDMI to HDMI cable
  • macOS or Linux computer

Caveats

  • When copy/pasting commands that start with $, strip out $ as this character is not part of the command
  • When copy/pasting commands that start with cat << "EOF", select all lines at once (from cat << "EOF" to EOF inclusively) as they are part of the same (single) command

Guide

Step 1: create SSH key pair (on macOS)

When asked for file in which to save key, enter pi.

When asked for passphrase, use output from openssl rand -base64 24 (and store passphrase in password manager).

$ mkdir ~/.ssh

$ cd ~/.ssh

$ ssh-keygen -t ed25519 -C "pi"
Generating public/private ed25519 key pair.
Enter file in which to save the key (/Users/sunknudsen/.ssh/id_ed25519): pi
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in pi.
Your public key has been saved in pi.pub.
The key fingerprint is:
SHA256:U3hEUQC0GAyCOPaks1Xv04ouoN9ezwtfK4CnUxKqAms pi
The key's randomart image is:
+--[ED25519 256]--+
|... .o..oo=+.    |
|+. o ..o +       |
|..+ . o o o      |
| o o.  . o       |
|  +. o. S        |
|.o. o +o o       |
|oo.  =+.o .      |
|=E ooo *.. .     |
|o...=o  =o.      |
+----[SHA256]-----+

$ cat pi.pub
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHLwQ2fk5VvoKJ6PNdJfmtum6fTAIn7xG5vbFm0YjEGY pi

Step 2: generate heredoc (the output of following command will be used at step 11)

cat << EOF
cat << "_EOF" > ~/.ssh/authorized_keys
$(cat ~/.ssh/pi.pub)
_EOF
EOF

Step 3: download latest version of 64-bit Raspberry Pi OS Lite

Step 4: copy “Raspberry Pi OS Lite” to microSD card or external solid state drive (follow these steps instead of step 4 if on Linux)

WARNING: BE VERY CAREFUL WHEN RUNNING DD AS DATA CAN BE PERMANENTLY DESTROYED (BEGINNERS SHOULD CONSIDER USING BALENAETCHER INSTEAD).

Heads-up: run diskutil list to find disk ID of microSD card or external solid state drive to overwrite with “Raspberry Pi OS Lite” (disk4 in the following example).

Heads-up: replace diskn and rdiskn with disk ID of microSD card or external solid state drive (disk4 and rdisk4 in the following example) and 2022-04-04-raspios-bullseye-arm64-lite with current image.

$ diskutil list
/dev/disk0 (internal):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:      GUID_partition_scheme                         500.3 GB   disk0
   1:             Apple_APFS_ISC                         524.3 MB   disk0s1
   2:                 Apple_APFS Container disk3         494.4 GB   disk0s2
   3:        Apple_APFS_Recovery                         5.4 GB     disk0s3

/dev/disk3 (synthesized):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:      APFS Container Scheme -                      +494.4 GB   disk3
                                 Physical Store disk0s2
   1:                APFS Volume Macintosh HD            15.3 GB    disk3s1
   2:              APFS Snapshot com.apple.os.update-... 15.3 GB    disk3s1s1
   3:                APFS Volume Preboot                 412.4 MB   disk3s2
   4:                APFS Volume Recovery                807.3 MB   disk3s3
   5:                APFS Volume Data                    384.5 GB   disk3s5
   6:                APFS Volume VM                      2.1 GB     disk3s6

/dev/disk4 (external, physical):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:     FDisk_partition_scheme                        *500.1 GB   disk4
   1:               Windows_NTFS Untitled                500.1 GB   disk4s1

$ sudo diskutil unmount /dev/diskn
disk4 was already unmounted or it has a partitioning scheme so use "diskutil unmountDisk" instead

$ sudo diskutil unmountDisk /dev/diskn (if previous step fails)
Unmount of all volumes on disk4 was successful

$ sudo dd bs=1m if=$HOME/Downloads/2022-04-04-raspios-bullseye-arm64-lite.img of=/dev/rdiskn
1908+0 records in
1908+0 records out
2000683008 bytes transferred in 6.420741 secs (311596910 bytes/sec)

$ sudo diskutil unmountDisk /dev/diskn
Unmount of all volumes on disk4 was successful

Step 5: log in as pi (using keyboard) and change password using passwd

Heads-up: current password is raspberry.

$ passwd
Changing password for pi.
Current password:
New password:
Retype new password:
passwd: password updated successfully

Step 6: configure Wi-Fi (if not using ethernet)

sudo raspi-config

Select “System Options”, then “Wireless LAN”, choose country, then select “OK”, enter “SSID”, enter passphrase.

Step 7: enable SSH

sudo raspi-config

Select “Interface Options”, then “SSH”, then “Yes”, then “OK” and, finally, select “Finish”.

When asked if you wish to reboot, select “No”.

Step 8: find IP of Raspberry Pi (see eth0 if using ethernet or wlan0 if using Wi-Fi)

ip a

Step 9: log in to Raspberry Pi over SSH

Heads-up: replace 10.0.1.181 with IP of Raspberry Pi.

Heads-up: when asked for passphrase, enter passphrase from step 5.

ssh pi@10.0.1.181

Step 10: disable pi Bash history

sed -i -E 's/^HISTSIZE=/#HISTSIZE=/' ~/.bashrc
sed -i -E 's/^HISTFILESIZE=/#HISTFILESIZE=/' ~/.bashrc
echo "HISTFILESIZE=0" >> ~/.bashrc
history -c; history -w
source ~/.bashrc

Step 11: configure pi SSH authorized keys

Create .ssh directory

mkdir ~/.ssh

Create ~/.ssh/authorized_keys using heredoc generated at step 2

cat << "_EOF" > ~/.ssh/authorized_keys
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHLwQ2fk5VvoKJ6PNdJfmtum6fTAIn7xG5vbFm0YjEGY pi
_EOF

Step 12: log out

exit

Step 13: log in

Heads-up: replace 10.0.1.181 with IP of Raspberry Pi.

Heads-up: when asked for passphrase, enter passphrase from step 1.

ssh -i ~/.ssh/pi pi@10.0.1.181

Step 14: switch to root

sudo su -

Step 15: disable root Bash history

echo "HISTFILESIZE=0" >> ~/.bashrc
history -c; history -w
source ~/.bashrc

Step 16: disable pi sudo nopassword “feature”

rm /etc/sudoers.d/010_*

Step 17: set root password

When asked for password, use output from openssl rand -base64 24 (and store password in password manager).

$ passwd
New password:
Retype new password:
passwd: password updated successfully

Step 18: disable root login and password authentication

sed -i -E 's/^(#)?PermitRootLogin (prohibit-password|yes)/PermitRootLogin no/' /etc/ssh/sshd_config
sed -i -E 's/^(#)?PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
systemctl restart ssh

Step 19: disable Bluetooth and Wi-Fi

Heads-up: step will take effect after reboot.

Disable Bluetooth

echo "dtoverlay=disable-bt" >> /boot/config.txt

Disable Wi-Fi (if using ethernet)

echo "dtoverlay=disable-wifi" >> /boot/config.txt

Step 20: configure sysctl (if network is IPv4-only)

Heads-up: only run following if network is IPv4-only.

cp /etc/sysctl.conf /etc/sysctl.conf.backup
cat << "EOF" >> /etc/sysctl.conf
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
net.ipv6.conf.lo.disable_ipv6 = 1
EOF
sysctl -p

Step 21: enable nftables and configure firewall rules

Enable nftables

systemctl enable nftables
systemctl start nftables

Configure firewall rules

nft flush ruleset
nft add table ip firewall
nft add chain ip firewall input { type filter hook input priority 0 \; policy drop \; }
nft add rule ip firewall input iif lo accept
nft add rule ip firewall input iif != lo ip daddr 127.0.0.0/8 drop
nft add rule ip firewall input tcp dport ssh accept
nft add rule ip firewall input ct state established,related accept
nft add chain ip firewall forward { type filter hook forward priority 0 \; policy drop \; }
nft add chain ip firewall output { type filter hook output priority 0 \; policy drop \; }
nft add rule ip firewall output oif lo accept
nft add rule ip firewall output tcp dport { http, https } accept
nft add rule ip firewall output udp dport { domain, ntp } accept
nft add rule ip firewall output ct state established,related accept

If network is IPv4-only, run:

nft add table ip6 firewall
nft add chain ip6 firewall input { type filter hook input priority 0 \; policy drop \; }
nft add chain ip6 firewall forward { type filter hook forward priority 0 \; policy drop \; }
nft add chain ip6 firewall output { type filter hook output priority 0 \; policy drop \; }

If network is dual stack (IPv4 + IPv6) run:

nft add table ip6 firewall
nft add chain ip6 firewall input { type filter hook input priority 0\; policy drop\; }
nft add rule ip6 firewall input iif lo accept
nft add rule ip6 firewall input iif != lo ip6 daddr ::1 drop
nft add rule ip6 firewall input meta l4proto ipv6-icmp icmpv6 type { destination-unreachable, packet-too-big, time-exceeded, parameter-problem } accept
nft add rule ip6 firewall input meta l4proto ipv6-icmp icmpv6 type { nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert, nd-redirect } ip6 hoplimit 255 accept
nft add rule ip6 firewall input tcp dport ssh accept
nft add rule ip6 firewall input ct state established,related accept
nft add chain ip6 firewall forward { type filter hook forward priority 0\; policy drop\; }
nft add chain ip6 firewall output { type filter hook output priority 0\; policy drop\; }
nft add rule ip6 firewall output oif lo accept
nft add rule ip6 firewall output meta l4proto ipv6-icmp icmpv6 type { destination-unreachable, packet-too-big, time-exceeded, parameter-problem } accept
nft add rule ip6 firewall output meta l4proto ipv6-icmp icmpv6 type { nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert } ip6 hoplimit 255 accept
nft add rule ip6 firewall output tcp dport { http, https } accept
nft add rule ip6 firewall output udp dport { domain, ntp } accept
nft add rule ip6 firewall output ct state related,established accept

Step 22: log out and log in to confirm firewall is not blocking SSH

Log out

$ exit

$ exit

Log in

Heads-up: replace 10.0.1.181 with IP of Raspberry Pi.

ssh -i ~/.ssh/pi pi@10.0.1.181

Step 23: switch to root

sudo su -

Step 24: make firewall rules persistent

cat << "EOF" > /etc/nftables.conf
#!/usr/sbin/nft -f

flush ruleset

EOF
nft list ruleset >> /etc/nftables.conf

Step 25: set timezone

See https://en.wikipedia.org/wiki/List_of_tz_database_time_zones

timedatectl set-timezone America/Montreal

Step 26: disable swap

systemctl disable dphys-swapfile

Step 27: update APT index and upgrade packages

$ apt update

$ apt upgrade -y

👍

Contributors:Sun KnudsenSun Knudsen

Wish to contribute or need help? Read the docs.
This website is not tracking you. PGP public key fingerprint: E786 274B C92B 47C2 3C1C  F44B 8C9C A674 C47C A060