Skip to content

Running as Unprivileged User

Traditionally, device management agents run as the root user to perform system-wide configuration changes, software updates, and service management. However, running long-lived background processes with full root privileges increases the attack surface of a device.

To mitigate this risk and adhere to the Principle of Least Privilege, the qbee-agent can be configured to run as an unprivileged (standard) user. In this mode, the agent runs with restricted permissions and can be given elevated privileges if and when necessary (e.g., via sudo) to perform specific tasks.

This guide details how to configure qbee-agent to run as a non-root user while maintaining its ability to manage the system, orchestrate containers, and access hardware security modules like TPM.

Architectural Approach

There are several ways to set up permissions for unprivileged users in Linux. By combining two of the main approaches it is possible to achieve a secure setup configuration which is also fairly easy to set up.

  • systemd Amending the service description to limit the scope of what the service can do
  • sudo Give an unprivileged user the ability to execute certain host utilities as a privileged user

In this guide we will show how to combine the two for use with qbee-agent.

Prerequisites

  • sudo installed and configured.
  • A configured qbee-agent installation, eg. by running the qbee-agent setup script on your device
    wget -qO - https://setup.qbee.io | sudo bash -s -- --bootstrap-key <bootstrap_key>
    
  • A dedicated unprivileged user (e.g., qbee-agent) created on the system. E.g.
    sudo useradd -m qbee-agent -s /sbin/nologin
    

Initial Configuration Steps

By default, the systemd service for qbee-agent is configured to run as root. To change this, you must apply a systemd drop-in override file which modifies the [Service] section of the systemd unit. It explicitly sets the User and Group to the unprivileged user (e.g., qbee-agent).

sudo chown -R qbee-agent:qbee-agent /etc/qbee /var/lib/qbee
sudo mkdir -p /etc/systemd/system/qbee-agent.service.d/
cat <<EOF | sudo tee -a /etc/systemd/system/qbee-agent.service.d/unprivileged.conf
[Service]
User=qbee-agent
Group=qbee-agent
RuntimeDirectory=qbee
StateDirectory=qbee
ConfigurationDirectory=qbee
ConfigurationDirectoryMode=0700
EOF
sudo systemctl daemon-reload
sudo systemctl restart qbee-agent

Change ownerships

Since we assume that the agent has already been bootstrapped using the setup.qbee.io-script, we also assume that we are using default configuration and state directories (/etc/qbee and /var/lib/qbee respectively). The ownership of these needs to be recursively changed so that the unprivileged user can manage them.

Additional Functionality

The agent now runs as an unprivileged user. However, since qbee-agent is a system management platform, it will in many cases need extra permissions to provide the needed functionality

1. Configuring Remote Console

One of the first steps was to create a user qbee-agent which the agent would run as. This user was created using /sbin/nologin as login shell according to best practices for system users. However, this also disables the often useful Remote Console functionality. In order to enable this for the unprivileged user we need to amend our service configuration for qbee-agent by adding an environment variable.

cat <<EOF | sudo tee -a /etc/systemd/system/qbee-agent.service.d/unprivileged.conf
Environment=SHELL=/bin/bash
EOF
sudo systemctl daemon-reload
sudo systemctl restart qbee-agent

2. Configure Privilege Elevation

Since the agent is no longer root, it cannot execute system commands (like apt, yum, or systemctl) directly. You must configure a "privilege elevation command"—typically sudo—which the agent will prefix to commands that require higher permissions.

Step A: Configure Sudoers You must allow the unprivileged user to execute commands as root without a password prompt (interactive password entry is not possible for a background service). The sudoers file grants the qbee-agent user the ability to run any command as any user (usually root) without a password. The most permissive version of this is the following

qbee-agent ALL=(ALL) NOPASSWD: ALL

This will allow the qbee-agent to run any command on the system with elevated privileges, which might not be what you want from a security standpoint. You might want to tailor the sudoers-file to your needs instead. The necessary permissions per qbee-functionality is documented here: GitHub - 99-qbee-agent. The sudoers file would usually be placed in /etc/sudoers.d.

Step B: Configure the Agent You must tell qbee-agent to use this elevation capability. This is done by defining the elevation_command for qbee-agent to use when running commands that require elevated privileges.

Example qbee-agent.json snippet:

{
  "elevation_command": ["/usr/bin/sudo","-n"]
}
The command needs to be provided with absolute path. It is also possible to provide the privilege elevation command as part of agent bootstrap, but you would then have to run the bootstrap command manually and not throught setup.qbee.io-script. Once the elevation command is added to the agent, we need to restart the agent process.
sudo systemctl restart qbee-agent

3. Orchestrating Docker (Rootful)

If your goal is to use qbee-agent to manage standard (rootful) Docker containers, the unprivileged user needs access to the Docker socket (usually located under /var/run/docker.sock). The socket is usually owned by the docker group and we can give the agent access by using the SupplementaryGroups setting in systemd.

cat <<EOF | sudo tee -a /etc/systemd/system/qbee-agent.service.d/unprivileged.conf
SupplementaryGroups=docker
EOF
sudo systemctl daemon-reload
sudo systemctl restart qbee-agent

4. Running Rootless Podman

Podman allows running containers without root privileges. However, for a service to run rootless containers persistently (even when the user is not logged in), "lingering" must be enabled for the unprivileged user. This is can be done by deploying a tmpfiles.d configuration.

cat <<EOF | sudo tee -a /etc/tmpfiles.d/linger-qbee-agent.conf
# path                                   mode user group age argument
f /var/lib/systemd/linger/qbee-agent     0644 root root  -
EOF
sudo systemd-tmpfiles --create /etc/tmpfiles.d/linger-qbee-agent.conf
sudo systemctl restart systemd-logind  qbee-agent

5. TPM Access

If you are using the Trusted Platform Module (TPM) for device identity or security (e.g., via tpm2-tools), the unprivileged agent needs access to the TPM device, typically located at /dev/tpmrm0 or /dev/tpm0. These devices usually have read write for the tss group, and we againg use the SupplementaryGroups setting to provide access. On Debian-based systems you might need to intsall the tpm2-tools package

sudo apt-get install tpm2-tools -y 

Once that is done, the supplementary group can be added to the qbee-agent service.

cat <<EOF | sudo tee -a /etc/systemd/system/qbee-agent.service.d/unprivileged.conf
SupplementaryGroups=tss
EOF
sudo systemctl daemon-reload
sudo systemctl restart qbee-agent

Feature Limitations in Unprivileged Mode

When running as a non-root user, certain qbee-agent features that rely on direct filesystem access or specific kernel capabilities will be restricted. Unless you grant specific capabilities via systemd (e.g., AmbientCapabilities) or AppArmor profiles, the following features will not work:

  1. User Passwords: The agent cannot read /etc/shadow to verify if a user's password hash matches the policy, nor can it list the hashing algorithm for user inventory.
  2. Configuring SSH Keys for Other Users: The agent can only manage SSH keys for the qbee-agent user. It cannot write to ~/.ssh/authorized_keys for other users (e.g., root or admin) due to permission restrictions.
  3. Service Configuration Files: The configuration management system (templating) cannot write files to directories that are not owned by the qbee-agent user (e.g., writing a config file to /etc/nginx/ will fail).
  4. Open Ports: The agent cannot read /proc/<pid>/fd to resolve which process currently owns listening network ports.

Note: You can overcome some of these file limitations by using the "File Distribution" feature combined with the privilege elevation command configured in Step 2.

Conclusion

Running qbee-agent as an unprivileged user significantly hardens the security posture of your IoT fleet. By stripping the agent of inherent root privileges, you contain potential breaches and ensure that administrative actions are strictly controlled through the defined privilege elevation mechanism.

While this setup requires additional initial configuration—specifically regarding systemd overrides and group memberships for Docker/TPM access—it provides a robust solution for production environments where security is paramount. The flexibility to use sudo allows the agent to retain full management capabilities without exposing the system to the risks of a perpetually privileged background process.

If you need assistance configuring rootless qbee-agent in other installation modes than what is supported by the setup.qbee.io script (eg. Yocto and the meta-qbee layer), please reach out to support.