FreeBSD quark server on the Raspberry Pi
- Get a preinstalled image
- How to find your SD card’s device
- Get a shell into the installed environment
- Setting up the root and freebsd users
- Serving a directory with quark
- Daemonizing quark
- A word on SSH security
- Public key authentication
- SSH configuration
- Packet filtering
- Setting CPU frequency limits
- Hardening your specific application, and liftoff
Get a preinstalled image
Download an SD card image from www.freebsd.org. At the time of
writing, it was available under the Get FreeBSD portion, in the
directory where the aarch64
releases are located. Look for
the image that has RPI
in its name.
Working from preinstalled images is common in this scenario. The reason for this mainly lies in only having one bootable SD card slot, which means you would have to plug in another SD card reader to install to (then swap them around once it’s ready to boot), or an SSD/HDD the pi can reliably power (in which case the hassle is in doing the firmware update which makes booting possible from the microUSB port).
How to find your SD card’s device
BEWARE, for all who don’t verify their device IDs
before using it as an output of dd
are subject to the whims
of fate. Make sure that when you pass /dev/sdX
to any
command that writes to it, it is exactly the device you believe it to
be, so that you don’t accidentally wipe your system.
To find the name of your SD card, you can either list your block
devices with lsblk
or devices with ls /dev
,
before and after you plug in your SD card. Compare their outputs, and
choose the new name in its topmost/simplest form (not any partitions,
but the device itself).
Get a shell into the installed environment
One method is to run the OS on the raspberry itself (as opposed to
running it inside an emulator such as QEMU). In this case, we’ll need to
copy the image to bootable media. As always, be careful when using
/dev/sdX
as the destination of a write operation (a safer
way would be to use disk IDs directly).
# dd if=<preinstalled>.img of=/dev/sdX status=progress
If you have a .img.xz
compressed archive, you can either
extract it to an image with
xz --decompress <your-image>.img.xz
, or you can pipe
the output of xzcat
into dd
, like so.
# xzcat <your-image>.img.xz | sudo dd of=/dev/<your-device> status=progress
Grab a cup of your beverage of choice while this completes, gaze out your window, or pet your pet. Contemplate the marvelous microlith, the molten glass into which you’re etching this vast array of symbols.
After completion, plug in your device to your target hardware, and boot it. To get access, you can either hook up your own peripherals, or you could do the setup completely headless (over an ethernet cable plugged into your router, for example).
By default, the configuration should start listening for SSH
connections (after a minute-long boot, in my case). Next, we should try
to find the machine on the local network with the nmap
network exploration tool. The address block 192.168.0.0/24
refers to the 256-wide sub-block of 192.168.0.0/16
defined
as “private use” in the IANA
IPv4 Special-Purpose Address Registry.
# nmap -sn 192.168.0.0/24
-sn (No port scan)
This option tells Nmap not to do a port scan after host discovery, and only print out the available hosts that responded to the host discovery probes.
I’ve read that the Pi boards should have a common MAC address prefix,
which could help with determining their assigned IP address, but mine
did not happen to have the prefix that I would have expected. To show
MAC addresses, nmap
needs to run as root, and your machines
have to be on the same subnet. In my case, I found the target address
based on the (Raspberry Pi Trading)
bit present on the same
line.
Regardless, you don’t necessarily need nmap
to find
devices on the local network. If you have to resort to sticks and
stones, you could also try to ping
every address on the
local network (with a shell script, possibly) to see if they respond,
and then attempting to log in if they do.
Once you’ve determined the address in the form
192.168.0.<0-255>
, you should be able ssh onto the
machine with the default non-root user freebsd
with the
same password.
$ ssh freebsd@192.168.0.<0-255>
Setting up the root and freebsd users
Change the password for freebsd
.
$ passwd
Afterwards, you can become root with su (with the same
root
password).
$ su
Change the password for root
.
# passwd
Serving a directory with quark
Install git
to be able to clone repositories.
# pkg install git
Clone the quark repository.
$ git clone git://git.suckless.org/quark
Build quark, and install it as root.
$ cd quark
$ make all
# make install
Under your home directory, create a directory for quark to serve, and
an index.html
with some test text of your choosing inside
it.
$ cd ~
$ mkdir <your-site>
$ vi <your-site>/index.html
Start quark as root. This will run it interactively; you can exit it
with ^C
(ctrl-c).
# /usr/local/bin/quark -h 0.0.0.0 -p 80 -d /home/freebsd/<your-site>
On your usual machine, use curl
to fetch your site, and
bask in the beauty of the text you just sent yourself.
$ curl 192.168.0.<0-255>
Daemonizing quark
To start quark as part of the boot process, we’ll use the
rc
and daemon
utilities. Check out their
manual pages with man <name>
.
The rc utility is the command script which controls the automatic boot process after being called by init.
The daemon utility detaches itself from the controlling terminal and executes the program specified by its arguments.
Create a script, /usr/local/etc/rc.d/quark
with the
following contents.
#!/bin/sh
# PROVIDE: quark
# REQUIRE: LOGIN
. /etc/rc.subr
name="quark"
rcvar=quark_enable
command="/usr/sbin/daemon"
command_args="-r /usr/local/bin/${name} -h 0.0.0.0 -p 80 -d /home/freebsd/<your-site>"
load_rc_config $name
run_rc_command "$1"
Make the script executable.
# chmod +x /usr/local/etc/rc.d/quark
For a decent introduction to rc
, check out Practical rc.d
scripting in BSD. Below, I’ll briefly mention the main points of
interest in this script.
PROVIDE
is an rcorder
keyword, and its line
is meant to signify that this script provides a condition named
quark
.
LOGIN
refers to a placeholder script that, among others,
helps with ordering operations.
It’s sourcing /etc/rc.subr
, where a number of sh
functions are defined for an rc.d script to use.
The command executed by this script is
/usr/sbin/daemon
.
-r, --restart
Supervise and restart the program after a one-second delay if it has been terminated.
Enable the newly created quark
service to be started at
boot time, with the same rcvar
name specified in the script
by adding a new line to /etc/rc.conf
containing
quark_enable="YES"
.
# vi /etc/rc.conf
Start the quark
service.
# service quark start
You can restart or stop it with those exact words as arguments.
Check the status of the service.
# service quark status
A word on SSH security
When changing SSH configurations, you should ensure that you either don’t test them on a live system, and that you have another way to access your machine, or you could end up locking yourself out otherwise.
If you are unacquainted with the basics of opsec, I strongly recommend that you read up on this section on your own time. In my case, I didn’t realize at first how bad of an idea is leaving a password-authenticated SSH port open on the internet.
There are some mitigations against brute force attacks, such as:
- Using a port different than 22 to eliminate most unsophisticated worms,
- Restricting access to known IP addresses,
- Rate limiting SSH sessions using iptables,
- Using an IPS (intrusion prevention system) such as fail2ban,
- Disabling root logins and restricting access of the users that can log in this way.
However, all of these are susceptible to a man-in-the-middle attack (or some kind of DNS hijacking), if you don’t check the SSH trust-on-first-use-style ECDSA key fingerprint carefully every time you first try to connect from another computer. Another problem is that as long as password authentication is enabled, the knocking will forever continue, leeching resources such as bandwidth, disk writes & space. This is why disabling password authentication completely is recommended, switching to keypairs instead.
An SSH key passphrase is a secondary form of security that gives you
a little time when your keys are stolen, so this option is still not
immune to brute forcing in that case. If your RSA key has a strong
passphrase, it might take your attacker a few hours to guess by brute
force. That extra time should be enough to log in to any computers you
have an account on, delete your old key from the
~/.ssh/authorized_keys
file, and add a new key. This shifts
the focus of security to the machines you keep your keys on. Note,
however, that if your keys are stolen and you are unaware of it, the
playing field is essentially reduced to brute-force (or dictionary)
attacks again.
Public key authentication
On the host system(s) where you will be connecting to the target
system from via SSH you will need to generate a private and public
keypair, and then add the public key of the host system to the target
system. Generate a key using ssh-keygen, a tool that “generates, manages
and converts authentication keys for ssh”. If # ssh-keygen
is invoked without any arguments, it will generate an RSA key of 3072
bits with 16 rounds of key derivation when saving the private key.
You’ll also need to use a passphrase to protect the key. Look into
diceware for an easy way of generating easy-to-remember,
cryptographically strong passwords.
Now add the key to the machine’s authorized keys using
# ssh-copy-id -i <pubkey> <username>@<hostname>
,
where <pubkey>
is the name of the public key on the
host system, and <hostname>
is where you originally
ssh’d onto the machine, which in my case, was the
192.168.0.<0-255>
local address.
To enable public key authentication, add the following to your ssh configuration file.
PubkeyAuthentication yes
If the key was added successfully to the machine (should be visible
in ~/ssh/authorized_keys
), test it out by attempting to log
in using it by
# ssh -i <path-to-pubkey> <username>@<hostname>
.
If it all seems to work, disable password authentication by adding the
following to your configuration. This isn’t strictly necessary, because
the default seems to be PasswordAuthentication no
.
PasswordAuthentication no
After editing, reload the configuration file.
# sh /etc/rc.d/sshd reload
SSH configuration
Edit /etc/ssh/sshd_config
, adding the following
configuration changes, if you deem them appropriate. Most of the
defaults seemed sane, except for password authentication, which is
something we’ll disable soon. For now, I would add the
Protocol 2
and UsePAM no
lines somewhere.
# SSHv2 is usually the default, but it is worth making sure.
Protocol 2
# If you don't need pluggable authentication module, disable it.
UsePAM no
Packet filtering
Enable the pf
service to be started at boot time by
adding pf_enable="YES"
to /etc/rc.conf
.
# vi /etc/rc.conf
Create /etc/pf.conf
, the packet filter configuration
file which will contain rules or definitions that configure the
pf
packet filtering utility.
# touch /etc/pf.conf
Here’s where things get a bit specific, and where you’ll have to exercise caution and your own judgement to compose your packet filtering rules. Instead of giving you a specific firewall configuration, I would recommend getting acquainted with the OpenBSD user’s guide for pf.
Still, here are some ideas on what to look for while configuring your packet filter for a purpose similar to what’s outlined in this guide:
- block all traffic by default, and only allow exactly what you need
- figure out whether
return
ordrop
is the appropriate block policy for you - allow TCP traffic to port 80 for your webserver, and look into rate-limiting that connection
- allow TCP traffic to your SSH port from the local network for headless local access
- consider limiting state table sizes
- look into: antispooof, synproxy
- contemplate whether outgoing connections should always pass or only when necessary (e.g. updates)
- keep some peripherals at hand for when you inevitably lock yourself out of SSH access
Start the packet filtering service.
# service pf start
After changing the packet filtering rules, load them again, and test the expected connections.
# pfctl -f /etc/pf.conf
Setting CPU frequency limits
For my system, running at the maximum frequency for extended periods does not constitute normal operation, as it’s supposed to be a low-traffic HTTP server. Instead of trying to service requests as fast as possible, I would lean towards a slight increase in latency in exchange for not having to worry about wasting power in the pathological case that the cores are pinned for any reason while not performing useful work.
If these highly specific and subjective criteria apply to your system
as well, you can configure powerd
in your
/etc/rc.conf
to set the maximum frequency to throttle up
to. First, you should check out the possible frequency levels your cores
can operate at with sysctl
.
$ sysctl dev.cpu.0.freq_levels
Add the following to your /etc/rc.conf
, substituting
<frequency-in-mhz>
.
powerd_enable="YES"
powerd_flags="-M <frequency-in-mhz>"
In my case, the RPi4 I had could operate at 600Mhz and 1500Mhz, so I
passed the argument -M 600
.
Hardening your specific application, and liftoff
Once you made sure that the general configuration of the server is reasonably safe for your purposes, it might be time to look into the security of whatever service it is going to provide. If you are serving a static website on port 80, that’s fairly simple, but if you’re running something more complicated, such as a git, email, or game server, it might be time to look into the security of that.
I hope you’ve found this resource useful, and that it will be of help in configuring your system for whatever cool purpose you have in mind for it.
created
modified