Auth
Updated on GitHub 4 months 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 13)

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.img 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                        *15.9 GB    disk4
   1:               Windows_NTFS Untitled                15.9 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 38.390485 secs (52114033 bytes/sec)

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

Step 5: configure keyboard

Step 6: create user

When asked for user, use pi-admin.

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

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

sudo raspi-config

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

Step 8: disable auto login

sudo raspi-config

Select “System Options”, then “Boot / Auto Login” and, finally, select “Console”.

Step 9: 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 10: find IP of Raspberry Pi (see eth0 if using ethernet or wlan0 if using Wi-Fi)

ip a

Step 11: log in to Raspberry Pi over SSH

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

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

ssh pi-admin@10.0.1.94

Step 12: 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 13: 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 14: log out

exit

Step 15: log in

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

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

ssh -i ~/.ssh/pi pi-admin@10.0.1.94

Step 16: switch to root

sudo su -

Step 17: disable root Bash history

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

Step 18: disable pi sudo nopassword “feature”

rm /etc/sudoers.d/010_*

Step 19: 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 20: 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 21: 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 22: 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 23: 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 24: log out and log in to confirm firewall is not blocking SSH

Log out

$ exit

$ exit

Log in

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

ssh -i ~/.ssh/pi pi-admin@10.0.1.94

Step 25: switch to root

sudo su -

Step 26: make firewall rules persistent

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

flush ruleset

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

Step 27: set timezone

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

timedatectl set-timezone America/Montreal

Step 28: disable swap

systemctl disable dphys-swapfile

Step 29: 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