The technology & security foundations of PiCockpit
I was asked to elaborate a bit about the security & technology foundations of PiCockpit.
The parts which are involved
PiCockpit consists of several parts:
- picockpit-client
- picockpit-frontend
- picockpit-backend
- picockpit-api (“papi”)
- the database
- the MQTT server
- the picockpit Package repository
The MQTT server
Data between the picockpit-frontend and picockpit-client is exchanged using the MQTT server (called “broker”), via Websockets. We use VerneMQ for this purpose.
MQTT is a communications protocol, similar to HTTP. It is based on the publish / subscribe metaphor, unlike HTTP (which is based on request / response).
In MQTT, clients can connect to the broker, and subscribe to topics – in which case they will receive messages which are published on these topics. They can also publish messages on topics.
Messages can be published as retained – in this case the broker will keep the message, and send it to any client which subscribes on the particular topic the message was published to.
Periodic keep-alives are sent from broker to client and back, to keep the connection running & ensure that the client is still online.
Additionally, a Last Will & Testament message can be set up, which will be published if the broker looses connection to the client.
Each Raspberry Pi gets its own message topic path, the Raspberry Pi’s serial number is used for this purpose.
According to my research VerneMQ is a perfect choice to serve as MQTT broker for PiCockpit:
- it is focused strictly on MQTT (unlike RabbitMQ)
- it has a focus on high-performance and high availability
VerneMQ is written in Erlang. Erlang is a good language / environment for telecommunications applications – it is designed to be highly concurrent, scalable, even across several nodes. It is also very lightweight for processing individual messages, allowing it to scale to millions of messages. The VerneMQ blog makes for fascinating reading.
Security features
VerneMQ allows us to use different mountpoints, to completely separate traffic for individual users.
A user’s picockpit-client and picockpit-frontend will only be able to see messages in that user’s mountpoint.
The mountpoint is assigned based on the authentication data, which identifies a Rasberry Pi (picockpit-client) or a JavaScript connection (picockpit-frontend) as belonging to that particular user.
Why Websockets?
Websockets allow the traffic to traverse Firewalls & is the only way the JavaScript client can communicate.
The picockpit-client
The picockpit-client is written in Python, using numerous libraries. It is packaged as a Debian package (.deb), and deployed to our public repository, signed with our key.
For MQTT communication, we use Paho. Paho is an open source library. We use Paho in the picockpit-frontend as well.
The picockpit-client is connected to PiCockpit in a setup process (sudo picockpit-client connect).
In this process, the client communicates with the API using an API key, to obtain MQTT credentials.
Both the API key and the MQTT credentials are saved on the user’s Raspberry Pi, in an encrypted form (including salt!).
The picockpit-client is designed to be constantly online, if the user’s Pi is online and connected to the Internet. This way, the Pi’s data can be read & it can be controlled by the user in a safe way.
By disabling the picockpit-client service, picockpit.com will not be able to connect to the Pi anymore & receive data / send control commands.
The picockpit-client saves local configuration files in the folder /etc/picockpit-client
You see a sample structure in the directory tree given above.
The main configuration file is picockpit-client.config.json. This file contains the necessary credentials for picockpit-client to connect to PiCockpit.com.
Additionally, you see two directories apps/com.picockpit/picontrol/modules and apps/com.picockpit/pidoctor/modules
These contain .json files, definition files for the commands you can actively run from the frontend in these particular apps.
In case, for example, you do not want to show the reboot or shutdown buttons in PiCockpit.com & disallow PiCockpit using that functionality, you would edit
apps/com.picockpit/picontrol/modules/core.json
and restart the picockpit-client service
As you can see, these files contain definitions for the commands which are shown in the web-frontend.
Similarly, if you want to expand the functionality and add additional commands or modules (groups of commands), you can do it here by editing the .JSON files or creating new ones.
Commands are run using the user “pi” by default. The .JSON syntax allows you to specify which user the command should run under.
picockpit-client runs as root, for executing commands a new Thread is spun off with the user rights being changed to the specified user. This change for that particular thread is irreversible, so the application can not reclaim root rights.
The same JSON files, with a somewhat different syntax, can be created for PiDoctor.
Security features
As mentioned above, user rights can be modified for PiControl setting the user. PiDoctor’s tests are currently all run as root.
No commands can be defined by the user in the Webinterface, these commands have to exist as JSON files on the specific Pi you want to control.
This is designed to prevent security issues, even in the case someone gets access to your webfrontend.
The user can control the amount of exposure they are willing to give.
By default only “poweroff”, “reboot” and “update picockpit-client” control actions have been added to the webinterface.
The other modules (GPIO & PiStats) do not allow to run arbitrary commands. PiStats is currently hardcoded to the information which is displayed in the webinterface:
And GPIO allows to control Raspberry Pi pins (including Software PWM), and read them. Care is taken in allocating the pins (for picockpit-client only, it is not aware of other applications – so you have to be careful there), and synchronizing states across multiple webinterfaces accessing the same Pi (e.g. if the user simultaneously uses several browser windows or devices).
All this information is transmitted using the MQTT publish / subscribe algorithm.
Finally, websocket communication runs over a secure port, so all data going out or into the Pi in communication with the broker is encrypted.
picockpit-backend & database
The picockpit backend is written in the Crystal programming language, using Kemal as a framework.
Crystal was chosen for it’s performance, developer friendliness and type-safety.
Crystal talks to the database. We use MongoDB as database.
Security features
- passwords are hashed and salted
- the user can create several API keys, and revoke (delete them)
- the API key is also only saved in a hashed and salted form – it can’t be reverse engineered, even if someone gets access to the database
On both the picockpit-client’s side, and in the database, only hashed derivatives of the API key are saved.
And – no – they do not match directly (this would be kind of missing the point ), they need further processing in order to validate the user’s credentials / the Pi’s access.
Deleting the API key also deletes the MQTT credentials for a particular Pi.
picockpit-frontend
The picockpit frontend is now increasingly based on JavaScript also for rendering purposes.
A choice was made to move PiCockpit more towards being a SPA (single page application).
Further integration of the pages currently rendered by the backend will be made into the frontend down the line.
We use Vue.js and Vuetify.js, and some other libraries for the frontend, including Paho for the MQTT connection using Websockets.
Depending on which Pi you are looking at, the webfrontend subscribes to the appropriate MQTT paths.
If you, for example, press the “shutdown” button, a message will be published on a defined MQTT path, which picockpit-client is listening on.
Security features
The frontend forces the use of an HTTPS connection. Similarly to the picockpit-client, data is transmitted to the broker via websockets using only encrypted connections (https://).
In PiControl, executing “dangerous” actions like reboot or shutdown is protected by additional confirmation dialogues: