Best Practices with Peripheral Access
Introduction
This article aims show the best practices when configuring the necessary permissions to access hardware devices and peripherals with Docker containers.
Prerequisites
- Basic knowledge of Torizon, by going through the Quickstart Guide and reading the Torizon OS Technical Overview.
- Basic knowledge of containers, especially in the context of Torizon. Recommended articles:
Hardware Access
Container technology is very popular in domains where direct access to the hardware is usually forbidden, like servers and cloud-based solutions. On the other side, software running on an embedded device will probably need to access hardware devices, for example, to collect data from sensors or to drive external machinery.
Docker provides ways to access specific hardware devices from inside a container, and often one needs to grant access from both a permission and a namespace perspective.
From a permission perspective, there are two ways to grant access to a container. They are presented below:
- Privileged: running a container in privileged mode is unnecessary, and when you do it, you lose the protection layer inherent to using containers. The entire host system is accessible from the container. You can most likely work around it using more granular capabilities and control group rules.
- Capabilities: using the
--cap-add
and--cap-drop
flags, you can add or drop capabilities such as the ability to modify network interfaces. Learn more about it in the Docker run reference - Runtime privilege and Linux capabilities. - Control group (cgroup) rules: those rules give more granular access to some hardware components, solving the permission issue. If you've read our articles Debian Containers for Torizon or Using Multiple Containers with Torizon OS that are focused on the command-line, you might have already come across those.
It's strongly advised to avoid at any cost using Privileged mode for the execution of your container. That can lead to security flaws. Please search for ways to expose the required resource through cgroup, bind mounts or devices. If you are facing issues in setting up your resource from a container, feel free to contact us at Toradex Community.
From a namespace perspective, there are also two ways to grant access to a container. You must give them access on a per-peripheral basis.
- Bind Mounts: you can share a file or a directory from the host to the container. Since devices are abstracted as files on Linux, bind mounts can expose devices inside the containers. When using pluggable devices, you might not know the exact mount point in advance and thus bind mount an entire directory. You can learn more about bind mounts in a previous section of this article about data storage.
- Devices: this is a more granular method for adding devices to containers, and it is preferred over bind mounts. It is better for security since you avoid exposing, for instance, a storage device that may be erased by an attacker.
Torizon uses a coherent naming for the most commonly used hardware interfaces. For instance, a family-specific interface will have a name corresponding to the family name used in the datasheet and tables across our other articles. This helps you write containers and applications that are pin-compatible in software - if you switch the computer on module for another.
Inside Torizon base containers, there is a user called torizon
mapped to several groups associated with hardware devices, including dialout
, audio
, video
, gpio
, i2cdev
, spidev
, pwm
and input
. That means using the torizon
user, it's not necessary to be root to access different hardware interfaces like sound cards, displays, serial ports, gpio controllers, etc. So when developing your application to run inside a container, run it with the torizon
user so the access to most hardware interfaces will work without requiring any additional privileges.
Torizon OS User Groups
The device will be mapped to the same path inside the container and use the same access rights specified for the host. Since the default user on the Toradex containers is torizon
, and you should avoid using root
as much as possible to limit potential security issues, you may have to add your user to specific groups to enable access for different kinds of devices. Those groups are mirrored between the host OS and our Debian-based container images, making things more intuitive.
The groups that are currently supported are listed in the table below:
group | description |
---|---|
gpio | allow access to the GPIO character device (/dev/gpiochip*), used by libgpiod |
pwm | allow access to PWM interfaces |
dialout | allow access to UART interfaces |
i2cdev | allow access to I2C interfaces |
spidev | allow access to SPI interfaces |
audio | allow access to audio devices |
video | allow access to graphics and backlight interfaces |
input | allow access to input devices |
Sharing a Pluggable Device
Suppose your application needs to access devices that may be plugged/unplugged at runtime. In this situation, the static mapping will not work. There is no way, at the moment, to map a device into a running container. If you need to access this kind of device, the only solution is to mount the /dev
folder as a volume.
Hardware Access through Control Group Rules (cgroup)
For devices that are not exposed through user groups, you can add its access through cgroup.
Each device that is handled through cgroup is referenced (or whitelisted) by the following fields:
- Type: a (all), c (char), or b (block). 'all' means it applies to all types and all major and minor numbers
- Major and Minor: Major and minor are either an integer or * for all. They reference the code for the device being whitelisted. The code for each device can be verified in the devices list at devices.txt in Kernel Documentation.
- Access: a composition of r (read), w (write), and m (mknod).
You can check more details about Device Whitelist Controller in the Kernel Documentation.
In case you want to check the major and minor numbers of a given device at /dev, you can do that by typing ls -l /dev/<device>
.
See below a few examples:
# ls -l /dev/tty0
crw--w---- 1 root tty 4, 0 Oct 6 09:32 /dev/tty0
# ls -l /dev/tty7
crw--w---- 1 root root 4, 7 Feb 23 19:43 /dev/tty7
# ls -l /dev/input/
total 0
drwxr-xr-x 2 root root 80 Oct 6 09:32 by-path
crw-rw---- 1 root input 13, 64 Oct 6 09:32 event0
crw-rw---- 1 root input 13, 65 Oct 6 09:32 event1
Exceptions: When You Must Run as Root Inside the Container
There may be situations that cannot be easily worked around, and you need to run the application inside the container as root
. However, notice that you should still avoid running the container as privileged, especially in this scenario. Here are some examples:
- How to Use CAN on Torizon OS: the CAN interface is abstracted as a network interface. Since NetworkManager does not support configuring the CAN, you must use iproute2 and therefore run as
root
.