2020-06-28
Boot a Raspberry Pi with no monitor, no keyboard and no insecure credentials!
The beauty of the Raspberry Pi lies in its community support - by being the de facto board for "tinkerers", it's really common that if you have an issue you'll find something on the internet which will help you solve it.
I wanted to write something to contribute to that community a little, so this is a guide to securely setting up your own Pi as a headless server for the home, all without the need to ever attach a monitor or keyboard to the Pi - even during setup!
The nature of a headless server is that you'll need to use the command line to do anything with it, so this guide does assume you're comfortable using a terminal. It's written assuming you're running Linux, and that your Pi will be networked entirely using wired Ethernet; if you want a wireless headless Pi you might need to tweak the attached setup script a little.
I should also say upfront that there are some tools to configure a headless Pi built right into the official tooling which can work great. Note however that that at the time of writing, following the official guide will potentially leave your Pi wide open on boot until you fix some settings; that's what this guide is intended to avoid!
It's easy to say we'll be creating a "secure" system, but what does that mean in practise? First and foremost, we don't want to ever be in a situation where our Pi is connected to the internet with SSH access enabled and with the default user - "pi" - accessible with its default password. Preventing this is our biggest goal.
That said, there are other changes we'll choose to make:
First and foremost we need to download our copy of Raspberry Pi OS from the official download page. Use the "lite" image, since we're targeting a headless machine, and be sure to verify the SHA-256 hash before unzipping the image. Your filename may differ but the idea is the same:
sha256sum 2020-05-27-raspios-buster-lite-armhf.zip
<hash-goes-here> 2020-05-27-raspios-buster-lite-armhf.zip
unzip 2020-05-27-raspios-buster-lite-armhf.zip
Next we'll mount the image file directly on our development machine so we can add some setup files. Mounting the image locally can seem slightly complicated, but it's worth the effort! We'll broadly be doing the same as is recommended in Azeria's guide to emulating the Raspberry Pi using QEMU.
We need to find the start point of the ext4 filesystem part of the image; that means finding the second value in the "start" column using fdisk -l
and multiplying it by 512. Here's a 1 liner which works at least with fdisk 2.35.2 for calculating the value we need and storing it:
let imgstart=$(fdisk -l 2020-05-27-raspios-buster-lite-armhf.img -o device,start | tail -1 | awk '{print ($2 * 512)}')
After we have the start point, we can mount the image:
sudo mkdir -p /mnt/sd
sudo mount -o loop,offset=$imgstart 2020-05-27-raspios-buster-lite-armhf.img /mnt/sd
Now we've mounted our base image, we can add our files for bootstrapping and then unmount the image:
You can get setup.sh
from this blog's sister repo. It also includes a script - mykeys.sh
- which takes your GitHub username as input, retrieves the SSH keys from your GitHub account and saves them as "authorized_keys".
git clone [email protected]:SgtCoDFish/winfra-bootstrap.git
cd winfra-bootstrap
./mykeys.sh <github-username-here>
sudo rm -f /mnt/sd/etc/init.d/resize2fs_once
sudo mkdir -p /mnt/sd/etc/winfra-bootstrap
sudo cp authorized_keys setup.sh /mnt/sd/etc/winfra-bootstrap/
sudo umount /mnt/sd
Our next step might seem a little unusual, but has some logic behind it: we emulate a Raspberry Pi using the bootstrap image we've just created, and run setup.sh
inside the emulator. The reasoning behind using an emulator is twofold:
We proceed again similarly to Azeria's guide for emulating a Pi.
First we need to install QEMU with support for ARM - this varies by distro and I can't cover them all; for Arch Linux you'll want qemu-headless-arch-extra
- basically, whatever provides the command qemu-system-arm
Next, we've got to download a kernel and a dtb file which we need to pass into QEMU; these are available in dhruvvyas90/qemu-rpi-kernel. Be sure to get the kernel and dtb files which reference "buster" - they need to match the version of Raspberry Pi OS we're using.
Next, we can actually run the emulated Pi. You might need to change the "-hda" parameter to match the name of your img file, and possibly the name of the "-dtb" or "-kernel" parameters.
qemu-system-arm \
-M versatilepb \
-cpu arm1176 \
-m 256 \
-hda 2020-05-27-raspios-buster-lite-armhf.img \
-dtb versatile-pb-buster.dtb \
-kernel kernel-qemu-4.19.50-buster \
-append 'root=/dev/sda2 panic=1' \
-serial stdio \
-no-reboot
This will drop us into our emulated system, and after the brief startup process we'll be able to log in using the username "pi" and the password "raspberry". Then we can run our setup commands inside the emulated Pi:
$DNSSERVERS
now.
sudo /etc/winfra-bootstrap/setup.sh myhostname.example.com myuser 192.168.0.100 192.168.0.1
The arguments you pass will, of course, vary based on your local network. Whatever you pass, the script will force you to set a new password for "pi", and will make various changes which are detailed elsewhere in this post. In addition, all of these steps are documented in the script.
After the script finishes run sudo poweroff
and wait for the emulated Pi to shut down.
Finally, burn the img file onto an SD card the same way you normally would for any Pi image. I use dd
:
sudo dd if=2020-05-27-raspios-buster-lite-armhf.img of=/dev/mmcblk0 conv=sync,noerror status=progress bs=1k
(I believe there's a problem with my SD card reader that forces me to use small values for "bs" - you might be able to use larger values for faster transfers)
Put the SD card into your pi, plug in the network cable and power it on; it'll take a while to come fully online but you should be able to SSH into your pi using the static IP and username you configured, e.g. ssh -p5541 [email protected]
Now you just need to run sudo ./newuser.sh
to delete the pi user and to resize the root FS - if you don't resize, you'll likely run out of disk space on your root FS very quickly.
You're all set up and you've avoided: