Deploy the UniFi Controller on an hardened Raspberry Pi for enhanced security.
This article describes how to configure an hardened Raspberry Pi to run the UniFi Controller in a Docker container. This solution is a secure alternative to buying a Cloud Key from Ubiquiti.
- Raspberry Pi 4 Model B 2019 8GB
- SanDisk Extreme 32GB MicroSDHC UHS-3 Card
Security-Enhanced Linux (SELinux) is a mandatory access control (MAC) security
mechanism implemented in the kernel. By default under a strict enforcing
setting,
everything is denied and then a series of exceptions policies are written that
give each element of the system (a service, program or user) only the access
required to function. If a service, program or user subsequently tries to access
or modify a file or resource (e.g. memory) not necessary for it to function,
then access is denied and the action is logged.
A more in-depth descirption of SELinux is available here.
As of August 2020, the linux kernel shipped with the Raspberry Pi OS does not include the security module SELinux. Here we are going to cross-compile the kernel with SELinux enabled using the tool tschaffter/raspberry-pi-kernel-hardened.
This single command builds the kernel for a Raspberry Pi 4. This command can be run on any computer that has the Docker installed. Note that this tool uses all the CPU cores available to the container to speed up the cross-compilation of the kernel, which by default are all the CPU cores of the host.
mkdir -p output && docker run \
--rm \
-v $PWD/output:/output \
tschaffter/raspberry-pi-kernel-hardened \
--kernel-branch rpi-5.4.y \
--kernel-defconfig bcm2711_defconfig \
--kernel-localversion $(date '+%Y%m%d')-hardened
Moving .deb packages to /output
SUCCESS The kernel has been successfully packaged.
INSTALL
sudo dpkg -i linux-*-20200804-hardened*.deb
sudo sh -c "echo 'kernel=vmlinuz-5.4.51-20200804-hardened+' >> /boot/config.txt"
sudo reboot
ENABLE SELinux
sudo apt-get install selinux-basics selinux-policy-default auditd
sudo sh -c "sed -i '$ s/$/ selinux=1 security=selinux/' /boot/cmdline.txt"
sudo touch /.autorelabel
sudo reboot
sestatus
See the GitHub repo of this tool to learn how to customize the build for other versions of the Pi.
Install Raspberry Pi OS Lite (preferred) or the distribution of your choice by
following the instructions given here. After installing the OS on
the SD card, create an empty file named ssh
on the boot partition to enable
remote connection to the Pi using SSH.
On Mac OS:
cd /Volumes/boot && touch ssh
On Windows 10 using Windows Subsystem for Linux (WSL):
sudo mkdir /mnt/d
sudo mount -t drvfs D: /mnt/d
touch /mnt/d/ssh
sudo umount /mnt/d/
After placing the SD card into the Pi and connecting it to the network using an Ethernet cable:
- SSH into the Pi:
ssh pi@<ip_address>
(default password is "raspberry") - Change the password:
passwd
- Update the Pi:
sudo apt update && sudo apt -y upgrade
The default user on the Pi is named pi
. It is recommended to create a new user
before removing the user pi
for better security.
-
Create the new user (replace
bob
by your own username).sudo -s export user=bob useradd -m ${user} \ && usermod -a -G sudo ${user} \ && echo "${user} ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/${user} \ && chmod 0440 /etc/sudoers.d/${user} passwd ${user}
-
Logout of the Pi and reconnect using the new user.
-
Delete the default user
pi
:sudo deluser -remove-home pi
.
-
Create the folder
/home/<user>/kernel
on the Pi and place the*deb
packages of the hardened kernel there (e.g. usingscp
). -
Install the new kernel (copy/paste the commands given by the kernel builder).
sudo dpkg -i ~/kernel/linux-*-20200804-hardened*.deb sudo sh -c "echo 'kernel=vmlinuz-5.4.51-20200804-hardened+' >> /boot/config.txt" sudo reboot $ uname -a Linux raspberrypi 5.4.51-20200804-hardened+ #1 SMP Wed Aug 5 04:37:44 UTC 2020 armv7l GNU/Linux
-
Install SELinux (copy/paste the commands given by the kernel builder). The reboot will take some time as the label of all the files are updated.
sudo apt-get install -y selinux-basics selinux-policy-default auditd sudo sh -c "sed -i '$ s/$/ selinux=1 security=selinux/' /boot/cmdline.txt" sudo touch /.autorelabel sudo reboot sestatus
-
Set SELinux mode to
enforcing
in/etc/selinux/config
. -
Check the active configuration of SELinux with
sestatus
.
Docker is needed to run the UniFi Controller in a Docker container. Run the following command to install the Docker engine on the Pi.
curl -fsSL get.docker.com -o get-docker.sh && sudo sh get-docker.sh
The command below enables the Docker service to start automatically at boot:
sudo systemctl enable docker.service
We add the current user to the group docker
so that we can run docker commands
without having to prefix them with sudo
.
sudo usermod -aG docker $(whoami)
Clone this GitHub repository on the Pi, then run ./unifi-controller.sh
as the
current (non-root) user for enhanced security. This command start the UniFi
Controller in a Docker container and creates a systemd service. This service
ensures that the controller is started at boot and properly stopped when the Pi
is turned off.
After running ./unifi-controller.sh
, check that the controller has successfully
started by looking at the logs of the Docker container (stdout).
$ docker logs unifi-controller
...
[cont-init.d] done.
[services.d] starting services
[services.d] done.
See the section Known issues if any error messages show up.
The controller uses a MongoDB instance to manage its data. These data may become corrupted if the controller is not stopped properly. The command below can be used to shut down the system now and then halt it.
sudo shutdown -h now
- Update the version of the controller in
unifi-controller.sh
./unifi-controller.sh
docker exec -it unifi-controller tail -f /usr/lib/unifi/logs/server.log
docker exec -it unifi-controller tail -f /usr/lib/unifi/logs/mongod.log
Loosing the controller or its data means that you will no longer be able to manage your network. Consider implementing one or more of the following backup strategies:
- Save the content of the Docker volume
unifi-controller
to a remote location - Create a copy of the SD card
Install and configure UFW (Uncomplicated Firewall) to protect the Pi against unauthorized remote connections. The ports opened below include SSH and the ports required by the UniFi controller.
sudo apt-get install -y ufw
sudo ufw status
sudo ufw allow ssh
sudo ufw allow 3478,10001,8080,8443 # required by the controller
sudo ufw enable
This excellent article from raspberrypi.org provides additional tips to secure your Pi.
The web interface of the UniFi controller should now be available at the addresses:
- https://<controller_address>:8443
- http://<controller_address>:8080
- "OpenJDK Client VM warning: INFO: os::commit_memory" (solved)
- "OpenJDK Client VM warning: libubnt_webrtc_jni.so" (solved)
Please read the CONTRIBUTING.md
for details on how to
contribute to this project.