r-pufky/wireguard-initramfs

Handshake problem on Raspberry Pi

Sanevent opened this issue · 4 comments

Motivation

I want to run an encrypted Raspberry Pi, which uses a wireguard endpoint during boot to automatically fetch a password from a server to decrypt itself.

Setup

disclaimer: I'm executing all commands in my booted up system as root.

I have a LUKS encrypted /home partition which I set up with the help of this cryptmypi project.

I'm running Raspberry Pi OS on a version from 2024-03-12 - which is a debian 12 (kernel version 6.6.20) on a Raspberry Pi 4 with 8 GB RAM.

I downloaded this project and rebuilt my initramfs along with some other files I needed in there such as the .key files for the wg connection.

For rebuilding I could not use the way this project suggested it, because the command "update-grub" is not available on the PI (due to it not having a BIOS and not having a bootloader available).

Instead I rebuilt the initramfs with the commands:

# pre step - due to mkintiramfs looks in the /boot
cp /boot/firmware/config-6.6.20+rpt-rpi-v8 /boot/

# download and extract by following the wireguard-initramfs install section
cd /home/pi/wireguard-initramfs-2023-10-21

# cyclic dev steps to rebuild the initramsfs
# modify mainly hooks, init-premount files
make uninstall && make install
mkinitramfs -o /boot/firmware/initramfs.gz -v 6.6.20+rpt-rpi-v8

After that, the initramfs i built and you would be able to reboot.

The automatic way of connecting to a wg server with the scripts provided here did not work. I know that because I added a ping command after establishing a wg connection automatically. If the connection was ok, I would have been able to see a successful handshake between my server and my client onmy server but there was none.

That is why I added more files, such as keyfiles and a working wg config, to the hooks section and rebuilt the initramfs.
After letting the cryptsetup fail during boot (because I do not provide the correct password right now) the system falls back to the initramfs and I can work with a basic shell.

From there I was able to verify that wg was indeed successfully installed, and I was able to set up a wg interface using the key files i added to the initramfs before. I tried this both, the automatic way of setting up a wg interface with a conf file, and the manual way with entering the ip-tables commands (as stated in the wg documentation).

It only works half way.
In both cases I could establish a connection, and bytes are sended through the interface when I try to ping one of my other devices. The problem is I get no incoming traffic back. I tried several things until now but I suspect, that the general network settings have something to do with it, as the wg config I'm using works fine on every normal system I tested it on.

After that I took a look on the commands the wg-quick script does when running on a booted up machine, I realized that this would not be possible to do, because it uses NFT which is not available in initramfs. So I tried to replicate this behavior with the basic tools I had in the initramfs environment.
My idea was to set up the firewall with the standard "ip" utility like wireguard did in their documentation.
But that made no change either.

I'm running out of ideas and my knowledge about networking processes on linux is very limited, but I can not accept that there would be no solution for that.

I hope that there may be a suggestion on what I did wrong or what the problem could be.

We seem to have found the issue and two things were contributing to it.

  1. The Raspberry Pi has no BIOS and therefore no RTC that keeps track of the system time while the Pi is powered off.
  2. Wireguard is very time sensitive. If the client and server do not have the same time setting the connection can not be made.

If the PI boots, the initramfs do not have the correct time setting (we checked it, and it is 01.01.1970). The system time will be updated much later in the boot process if the PI is connected to the internet. If a WG connection attempt is made before the time is set correctly, the handshake will fail. WG client and server will not be able to get into sync with each other.

To solve this and check the theory behind it we needed to manually update the time setting. To do this, we need to fetch the time from a server first. Then we wrote a simple Go script go-date-converter that converts the received data into a format that the initramfs can process. (The script needs to be added into the initramfs to use it). Then we set the time manually.

We added the following line into the init-premount script of this repo - which does all of it at once:

date -s $(dtconv "$(wget -qSO- google.com 2>&1 | sed -n 's/^ *Date: *//p')")

NOTE: google.com can be replaced with a similar website

Alternatively, if no internet connection is available, we could add an RTC to the Pi and get the system time from there if an offline solution is preferred - but then there is no point in using wireguard anyway.

Thanks for the update and work on the PI issue. This seems like something that we should be including in the build; even if it is an edgecase -- at worse it doesn't hurt anything and at most it adds support for PI's.

I was thinking that we can detect platform on build and include that if ARM/PI platforms are detected, which if I understand correctly, will provide what you need for it.

Can you make a pull request with the changes in it @Sanevent ?

We could take this on. It will not be done instantly (because there is a lot of other stuff to do at the moment) but we can totally work on it.