2020-12-07 10:03:03 -05:00
<!--
2021-02-23 14:02:20 -05:00
Title: How to configure hardened Raspberry Pi
Description: Learn how to configure hardened Raspberry Pi.
2020-12-07 10:03:03 -05:00
Author: Sun Knudsen < https: / / github . com / sunknudsen >
Contributors: Sun Knudsen < https: / / github . com / sunknudsen >
Reviewers:
Publication date: 2020-11-27T10:00:26.807Z
Listed: true
-->
2021-02-23 14:02:20 -05:00
# How to configure hardened Raspberry Pi
2020-12-07 10:03:03 -05:00
2022-10-31 08:43:01 -04:00
[](https://www.youtube.com/watch?v=6R8uKdstnts "How to configure hardened Raspberry Pi")
2020-12-11 08:39:56 -05:00
2020-12-07 10:03:03 -05:00
## Requirements
2022-04-16 11:53:28 -04:00
- [Raspberry Pi 4 ](https://www.raspberrypi.com/products/raspberry-pi-4-model-b/ )
- 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
2022-04-10 09:24:48 -04:00
- macOS or Linux computer
2020-12-07 10:03:03 -05:00
## 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
2021-03-02 12:58:46 -05:00
### Step 1: create SSH key pair (on macOS)
2020-12-07 10:03:03 -05:00
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).
2020-12-25 10:08:11 -05:00
```console
2021-05-10 08:25:40 +02:00
$ mkdir ~/.ssh
2020-12-07 10:03:03 -05:00
$ cd ~/.ssh
2022-01-08 07:42:53 -05:00
$ 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
2020-12-07 10:03:03 -05:00
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:
2022-01-08 07:42:53 -05:00
SHA256:U3hEUQC0GAyCOPaks1Xv04ouoN9ezwtfK4CnUxKqAms pi
2020-12-07 10:03:03 -05:00
The key's randomart image is:
2022-01-08 07:42:53 -05:00
+--[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. |
2020-12-07 10:03:03 -05:00
+----[SHA256]-----+
$ cat pi.pub
2022-01-08 07:42:53 -05:00
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHLwQ2fk5VvoKJ6PNdJfmtum6fTAIn7xG5vbFm0YjEGY pi
2020-12-07 10:03:03 -05:00
```
2022-06-07 07:17:22 -04:00
### Step 2: generate heredoc (the output of following command will be used at [step 13](#step-13-configure-pi-ssh-authorized-keys))
2020-12-07 10:03:03 -05:00
```shell
cat < < EOF
2021-02-23 14:02:20 -05:00
cat < < "_EOF" > ~/.ssh/authorized_keys
2020-12-07 10:03:03 -05:00
$(cat ~/.ssh/pi.pub)
_EOF
EOF
```
2022-04-10 09:24:48 -04:00
### Step 3: download latest version of 64-bit [Raspberry Pi OS Lite](https://www.raspberrypi.com/software/operating-systems/#raspberry-pi-os-64-bit)
2020-12-07 10:03:03 -05:00
2022-04-27 09:30:49 -04:00
### Step 4: copy “Raspberry Pi OS Lite” to microSD card or external solid state drive (follow [these](./misc/how-to-copy-raspberry-pi-os-lite-to-microsd-card-or-external-solid-state-drive-on-linux) steps instead of step 4 if on Linux)
2020-12-07 10:03:03 -05:00
2022-04-10 08:34:52 -04:00
> **WARNING: BE VERY CAREFUL WHEN RUNNING `DD` AS DATA CAN BE PERMANENTLY DESTROYED (BEGINNERS SHOULD CONSIDER USING [BALENAETCHER](https://www.balena.io/etcher/) INSTEAD).**
2020-12-07 10:03:03 -05:00
2022-04-10 09:24:48 -04:00
> 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).
2020-12-07 10:03:03 -05:00
2022-06-07 07:17:22 -04:00
> 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.
2020-12-07 10:03:03 -05:00
```console
$ diskutil list
2022-01-08 07:42:53 -05:00
/dev/disk0 (internal):
2020-12-07 10:03:03 -05:00
#: TYPE NAME SIZE IDENTIFIER
2022-01-08 07:42:53 -05:00
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
2020-12-07 10:03:03 -05:00
2022-01-08 07:42:53 -05:00
/dev/disk3 (synthesized):
2020-12-07 10:03:03 -05:00
#: TYPE NAME SIZE IDENTIFIER
2022-01-08 07:42:53 -05:00
0: APFS Container Scheme - +494.4 GB disk3
2020-12-07 10:03:03 -05:00
Physical Store disk0s2
2022-01-08 07:42:53 -05:00
1: APFS Volume Macintosh HD 15.3 GB disk3s1
2: APFS Snapshot com.apple.os.update-... 15.3 GB disk3s1s1
2022-04-10 09:24:48 -04:00
3: APFS Volume Preboot 412.4 MB disk3s2
4: APFS Volume Recovery 807.3 MB disk3s3
5: APFS Volume Data 384.5 GB disk3s5
2022-01-08 07:42:53 -05:00
6: APFS Volume VM 2.1 GB disk3s6
/dev/disk4 (external, physical):
2020-12-07 10:03:03 -05:00
#: TYPE NAME SIZE IDENTIFIER
2022-06-07 07:17:22 -04:00
0: FDisk_partition_scheme *15.9 GB disk4
1: Windows_NTFS Untitled 15.9 GB disk4s1
2020-12-07 10:03:03 -05:00
$ sudo diskutil unmount /dev/diskn
2022-01-08 07:42:53 -05:00
disk4 was already unmounted or it has a partitioning scheme so use "diskutil unmountDisk" instead
2020-12-07 10:03:03 -05:00
$ sudo diskutil unmountDisk /dev/diskn (if previous step fails)
2022-01-08 07:42:53 -05:00
Unmount of all volumes on disk4 was successful
2020-12-07 10:03:03 -05:00
2022-04-10 09:24:48 -04:00
$ 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
2022-06-14 08:37:45 -04:00
2000683008 bytes transferred in 38.390485 secs (52114033 bytes/sec)
2020-12-07 10:03:03 -05:00
$ sudo diskutil unmountDisk /dev/diskn
2022-01-08 07:42:53 -05:00
Unmount of all volumes on disk4 was successful
2020-12-07 10:03:03 -05:00
```
2022-06-07 07:17:22 -04:00
### Step 5: configure keyboard
2020-12-07 10:03:03 -05:00
2022-06-07 07:17:22 -04:00
### Step 6: create user
2021-02-23 14:02:20 -05:00
2022-06-07 07:17:22 -04:00
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)
```shell
sudo raspi-config
2021-02-23 14:02:20 -05:00
```
2020-12-07 10:03:03 -05:00
2022-06-07 07:17:22 -04:00
Select “System Options”, then “Wireless LAN”, choose country, then select “OK”, enter “SSID” and, finally, enter passphrase.
### Step 8: disable auto login
2020-12-07 10:03:03 -05:00
```shell
sudo raspi-config
```
2022-06-07 07:17:22 -04:00
Select “System Options”, then “Boot / Auto Login” and, finally, select “Console”.
2020-12-07 10:03:03 -05:00
2022-06-07 07:17:22 -04:00
### Step 9: enable SSH
2020-12-07 10:03:03 -05:00
```shell
sudo raspi-config
```
2022-02-18 10:41:53 -05:00
Select “Interface Options”, then “SSH”, then “Yes”, then “OK” and, finally, select “Finish”.
2020-12-07 10:03:03 -05:00
When asked if you wish to reboot, select “No”.
2022-06-07 07:17:22 -04:00
### Step 10: find IP of Raspberry Pi (see `eth0` if using ethernet or `wlan0` if using Wi-Fi)
2020-12-07 10:03:03 -05:00
```shell
ip a
```
2022-06-07 07:17:22 -04:00
### Step 11: log in to Raspberry Pi over SSH
2020-12-07 10:03:03 -05:00
2022-06-07 07:17:22 -04:00
> Heads-up: replace `10.0.1.94` with IP of Raspberry Pi.
2020-12-07 10:03:03 -05:00
2022-06-07 07:17:22 -04:00
> Heads-up: when asked for passphrase, enter passphrase from [step 5](#step-6-create-user).
2020-12-07 10:03:03 -05:00
```shell
2022-06-07 07:17:22 -04:00
ssh pi-admin@10 .0.1.94
2022-01-08 07:42:53 -05:00
```
2022-06-07 07:17:22 -04:00
### Step 12: disable pi Bash history
2022-01-08 07:42:53 -05:00
```shell
sed -i -E 's/^HISTSIZE=/#HISTSIZE =/' ~/.bashrc
sed -i -E 's/^HISTFILESIZE=/#HISTFILESIZE =/' ~/.bashrc
echo "HISTFILESIZE=0" >> ~/.bashrc
history -c; history -w
source ~/.bashrc
2020-12-07 10:03:03 -05:00
```
2022-06-07 07:17:22 -04:00
### Step 13: configure pi SSH authorized keys
2020-12-07 10:03:03 -05:00
2021-05-21 13:21:41 -04:00
#### Create `.ssh` directory
2020-12-07 10:03:03 -05:00
```shell
2021-05-10 08:25:40 +02:00
mkdir ~/.ssh
2020-12-07 10:03:03 -05:00
```
2022-06-07 07:17:22 -04:00
#### Create `~/.ssh/authorized_keys` using heredoc generated at [step 2](#step-2-generate-heredoc-the-output-of-following-command-will-be-used-at-step-13)
2020-12-07 10:03:03 -05:00
```shell
2021-02-23 14:02:20 -05:00
cat < < "_EOF" > ~/.ssh/authorized_keys
2022-01-08 07:42:53 -05:00
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHLwQ2fk5VvoKJ6PNdJfmtum6fTAIn7xG5vbFm0YjEGY pi
2020-12-07 10:03:03 -05:00
_EOF
```
2022-06-07 07:17:22 -04:00
### Step 14: log out
2020-12-07 10:03:03 -05:00
```shell
exit
```
2022-06-07 07:17:22 -04:00
### Step 15: log in
2020-12-07 10:03:03 -05:00
2022-06-07 07:17:22 -04:00
> Heads-up: replace `10.0.1.94` with IP of Raspberry Pi.
2020-12-07 10:03:03 -05:00
2022-02-16 16:07:46 -05:00
> Heads-up: when asked for passphrase, enter passphrase from [step 1](#step-1-create-ssh-key-pair-on-macos).
2020-12-07 10:03:03 -05:00
```shell
2022-06-07 07:17:22 -04:00
ssh -i ~/.ssh/pi pi-admin@10 .0.1.94
2020-12-07 10:03:03 -05:00
```
2022-06-07 07:17:22 -04:00
### Step 16: switch to root
2020-12-07 10:03:03 -05:00
```shell
2022-01-08 07:42:53 -05:00
sudo su -
2021-02-23 14:02:20 -05:00
```
2022-06-07 07:17:22 -04:00
### Step 17: disable root Bash history
2020-12-07 10:03:03 -05:00
```shell
2022-01-08 07:42:53 -05:00
echo "HISTFILESIZE=0" >> ~/.bashrc
history -c; history -w
source ~/.bashrc
2020-12-07 10:03:03 -05:00
```
2022-06-07 07:17:22 -04:00
### Step 18: disable pi sudo `nopassword` “feature”
2021-05-15 09:02:35 -04:00
```shell
rm /etc/sudoers.d/010_*
```
2022-06-07 07:17:22 -04:00
### Step 19: set root password
2020-12-07 10:03:03 -05:00
When asked for password, use output from `openssl rand -base64 24` (and store password in password manager).
2021-02-23 14:02:20 -05:00
```console
$ passwd
New password:
Retype new password:
passwd: password updated successfully
2020-12-07 10:03:03 -05:00
```
2022-06-07 07:17:22 -04:00
### Step 20: disable root login and password authentication
2020-12-07 10:03:03 -05:00
```shell
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
```
2022-06-07 07:17:22 -04:00
### Step 21: disable Bluetooth and Wi-Fi
2020-12-07 10:03:03 -05:00
2022-01-08 07:42:53 -05:00
> Heads-up: step will take effect after reboot.
2020-12-07 10:03:03 -05:00
#### Disable Bluetooth
```shell
2021-02-23 14:02:20 -05:00
echo "dtoverlay=disable-bt" >> /boot/config.txt
2020-12-07 10:03:03 -05:00
```
#### Disable Wi-Fi (if using ethernet)
```shell
2021-02-23 14:02:20 -05:00
echo "dtoverlay=disable-wifi" >> /boot/config.txt
2020-12-07 10:03:03 -05:00
```
2022-06-07 07:17:22 -04:00
### Step 22: configure sysctl (if network is IPv4-only)
2020-12-07 10:03:03 -05:00
2022-01-08 07:42:53 -05:00
> Heads-up: only run following if network is IPv4-only.
2020-12-07 10:03:03 -05:00
```shell
2022-01-08 07:42:53 -05:00
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
2020-12-07 10:03:03 -05:00
```
2022-06-07 07:17:22 -04:00
### Step 23: enable nftables and configure firewall rules
2021-02-23 14:02:20 -05:00
2022-01-08 07:42:53 -05:00
#### Enable nftables
2021-02-23 14:02:20 -05:00
```shell
2022-01-08 07:42:53 -05:00
systemctl enable nftables
systemctl start nftables
2021-02-23 14:02:20 -05:00
```
2022-01-08 07:42:53 -05:00
#### Configure firewall rules
2021-04-19 09:54:52 -04:00
2020-12-07 10:03:03 -05:00
```shell
2022-01-08 07:42:53 -05:00
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
2020-12-07 10:03:03 -05:00
```
2022-01-08 07:42:53 -05:00
If network is IPv4-only, run:
2021-02-23 14:02:20 -05:00
```shell
2022-01-08 07:42:53 -05:00
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 \; }
2021-02-23 14:02:20 -05:00
```
2022-01-08 07:42:53 -05:00
If network is dual stack (IPv4 + IPv6) run:
2020-12-07 10:03:03 -05:00
```shell
2022-01-08 07:42:53 -05:00
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
2020-12-07 10:03:03 -05:00
```
2022-06-07 07:17:22 -04:00
### Step 24: log out and log in to confirm firewall is not blocking SSH
2022-01-08 07:42:53 -05:00
#### Log out
2020-12-07 10:03:03 -05:00
2022-01-10 10:30:12 -05:00
```console
$ exit
$ exit
2020-12-07 10:03:03 -05:00
```
2022-01-08 07:42:53 -05:00
#### Log in
2020-12-07 10:03:03 -05:00
2022-06-07 07:17:22 -04:00
> Heads-up: replace `10.0.1.94` with IP of Raspberry Pi.
2020-12-07 10:03:03 -05:00
```shell
2022-06-07 07:17:22 -04:00
ssh -i ~/.ssh/pi pi-admin@10 .0.1.94
2020-12-07 10:03:03 -05:00
```
2022-06-07 07:17:22 -04:00
### Step 25: switch to root
2020-12-07 10:03:03 -05:00
```shell
2022-01-08 07:42:53 -05:00
sudo su -
2020-12-07 10:03:03 -05:00
```
2022-06-07 07:17:22 -04:00
### Step 26: make firewall rules persistent
2020-12-07 10:03:03 -05:00
```shell
2022-01-08 07:42:53 -05:00
cat < < "EOF" > /etc/nftables.conf
#!/usr/sbin/nft -f
2020-12-07 10:03:03 -05:00
2022-01-08 07:42:53 -05:00
flush ruleset
2020-12-07 10:03:03 -05:00
2022-01-08 07:42:53 -05:00
EOF
2020-12-07 10:03:03 -05:00
```
```shell
2022-01-08 07:42:53 -05:00
nft list ruleset >> /etc/nftables.conf
```
2020-12-07 10:03:03 -05:00
2022-06-07 07:17:22 -04:00
### Step 27: set timezone
2022-01-08 07:42:53 -05:00
See https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
2020-12-07 10:03:03 -05:00
```shell
2022-01-08 07:42:53 -05:00
timedatectl set-timezone America/Montreal
2020-12-07 10:03:03 -05:00
```
2022-06-07 07:17:22 -04:00
### Step 28: disable swap
2020-12-07 10:03:03 -05:00
```shell
2022-01-08 07:42:53 -05:00
systemctl disable dphys-swapfile
2020-12-07 10:03:03 -05:00
```
2022-06-07 07:17:22 -04:00
### Step 29: update APT index and upgrade packages
2022-01-08 07:42:53 -05:00
2022-01-10 10:48:52 -05:00
```console
$ apt update
2020-12-07 10:03:03 -05:00
2022-01-10 10:48:52 -05:00
$ apt upgrade -y
2020-12-07 10:03:03 -05:00
```
👍